Android系列:彻底了解Handler

2,113 阅读7分钟

一、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多路复用机制

www.jianshu.com/p/c1ae21d36…

==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:www.jianshu.com/p/31cdfd6f5…

这里采用的epoll机制,是一种==IO多路复用机制==,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。