一、Handler
1.1 Android为什么==非ui线程==不能==更新ui==
- UI线程的机制
- 为什么UI不设计成线程安全
- 非ui线程一定不能更新ui吗
1.1.1 ui线程机制
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
//主线程会一直死循环运行 也就是阻塞
Looper.loop();
//如果死循环退出了,程序就会崩掉抛出异常
throw new RuntimeException("Main thread loop unexpectedly exited");
}
ui线程就是zygote直接fork出来的进程中的线程,就是Activty中的main。
1.1.2 为什么ui不设计成线程安全的
如果在子线程子线程中去更新ui,必定要对view更新的操作加锁,加锁的效率很受影响,不能接受;
- ui具有可变性,甚至是高频可变性
- ui对响应时间非常敏感
- ui组件必须批量绘制,保证效率
1.1.3 非ui线程一定不能更新ui吗?
答案是否定的,不能直接更新,但是可以间接去更新,比如post,postinvalidate。还有SurfaceView是直接在子线程中去更新ui的。在Activity的oncreat生命周期中也可以在子线程中更新ui。
本质:更新ui的时候会去做线程检测,检测创建这个ui的线程是不是要更新ui的线程,更新ui就是在ViewRootImpl中做requestLayout,做线程检测;但是ViewRoot是在onResume中addDecView去实现的,如果在oncreat中创建子线程更新ui 也许这时候还没有创建Viewroot,这时候就绕开了检测。在子线程弹Toast是可以的--因为toast是在windowmanager上弹的,与Activity无关,也就与ViewRoot无关,但是由于Toast里面用到了handler,所以在子线程中要Looper.prepare()一下;所以,在子线程中是可以更新ui的
1.2 Handler post delay的时间一定靠谱吗?
答案:肯定是不靠谱的,因为有时候消息处理不过来(Looper传送带负载过高),在主线程做太多耗时操作,消息分发根本来不及,这就造成了卡顿,既然卡顿了,延迟就不靠谱了。
消息的插入
//入队 就是链表的插入操作
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
//msg 是插入的message
msg.when = when;
//MessageQueue维持的消息队列(链表的数据结构),刚开始p肯定是空的
Message p = mMessages;//mMessages 就是当前所维持的前链表
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//向表头插入
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 从链表的中间插入 插入点是根据时间大小来决定的
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
这里的操作就是链表的插入操作,MessageQueue选择链表作为数据对象,主要是因为链表的插入和删除非常高效。 接下来看看消息唤醒的native代码
==nativeWake==
void NativeMessageQueue::wake() {
mLooper->wake();
}
void Looper::wake() {
uint64_t inc = 1;
// 向管道 mWakeEventFd 写入字符1 , 写入失败仍然不断执行
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
它仅仅是向管道写入字符1,然后唤醒
消息的取出
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
//当没有消息的时候 nextPollTimeoutMillis = -1 阻塞程序不会再往下走,除非有新消息或者等待时间到了,调用
//nextPollTimeoutMillis值是多少就阻塞多长时间,比如200ms,就是200ms后自动唤醒。
nativePollOnce(ptr, nextPollTimeoutMillis);
//如果没有阻塞就执行下面的
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.到了当前之间,直接取出来,并且想从链表中删除
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
//立马执行的就直接循环结束
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
handler消息延时是怎么实现的?
- 消息延时是做了什么特殊处理吗
- 消息延时是发送延时是处理延时
- 消息延时精度怎么样,靠谱吗?
这里,消息等待阻塞机制主要是在nativePollOnce中去实现,当前消息时间还没有到,或者没有消息了,这时候会进行等待休眠状态,不会消耗cpu,这是采用了Linux的IO多路复用机制
==nativePollOnce==
[-> android_os_MessageQueue.cpp]
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
//ptr消息队列地址
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
//调用Looper的pollOnce
mLooper->(timeoutMillis);
}
==mLooper 是native层的Looper对象,它是对epoll的封装,和java层面的Looper没有任何关系。==
[-> /system/core/libutils/Looper.cpp]
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
...
if (result != 0) {
...
return result;
}
// 再处理内部轮询
result = pollInner(timeoutMillis);
}
}
int Looper::pollInner(int timeoutMillis) {
...
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
mPolling = true; //即将处于idle状态
// fd最大个数为16
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// 等待事件发生或者超时,在 nativeWake() 方法,向管道写端写入字符,则该方法会返回;
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
return result;
}
Looper#loop()
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
//从queue中去取消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
//消息分发前后会打印log信息,这可以监控卡顿优化
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//分发消息
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
消息队列优化的一些思路:
- 消息过滤
- 消息互斥 比如消息执行到stop了,在它之前的所以消息都不需要再执行了,直接移除掉
void removeCallbacksAndMessages(Handler h, Object object
- 消息复用 对象池
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
- 消息空闲 IdleHandler
1.3 主线程的Looper为什么不会导致ANR
- ANR产生的条件是什么
- Looper的工作机制是什么
- Looper不会导致ANR的本质原因是什么
- Looper死循环会不会导致CPU占用率过高
1.3.1 ANR产生的条件
思路:预埋炸弹->执行操作->拆除或者引爆炸弹 下面举例service的anr产生条件,在ActivityServices类中
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
//预埋炸弹
bumpServiceExecutingLocked(r, execInFg, "create");
//执行service oncreate()操作
app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
//拆除炸弹
serviceDoneExecutingLocked(r, inDestroying, inDestroying);
}
预埋炸弹
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
scheduleServiceTimeoutLocked(r.app);
}
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
//发送一个带延迟的消息到MQ中去,但是不一定执行
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? 20*000 : 200 * 1000);
}
拆除炸弹
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
boolean finishing) {
//移除之前发的延迟消息
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
}
总结:==执行oncreate这些操作之前先发送一个延迟消息, 延迟时间为前台服务20s,后台服务200s,接着去执行oncreate操作,如果在这段时间内执行完毕了,延迟消息还没有来得及去执行就被remove掉了。==
1.3.2Looper会产生ANR吗
肯定不会,Looper是整个主线程的运行机制,而ANR仅仅是主线程中设计的一个耗时监控的细节,这两者根本就没有任何关系,也就当然不会产生ANR了。
1.3.3 Looper死循环会导致CPU占用率过高吗?
肯定不会,如果死循环一直在运行的话,也就是Looper传送带一直空转,这时候肯定会消耗cpu,但是事实上死循环并没有在空转,它采样了==epoll机制==,当没有消息来的时候它处于阻塞状态wait,也就不会一直在那里空转而消耗cpu了,当有消息来的时候,会wake Looper来继续工作。
这里采用的epoll机制,是一种==IO多路复用机制==,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
网友评论