Android 进阶答疑:Handler机制的十个问题

1.Handler是什么?

  Handler机制主要为线程间通信而生,是Android中定义的一套消息传递机制。

  主要是为了解决子线程执行完耗时操作后,怎么回调到主(UI)线程的问题。

2.Handler机制的主要成员有哪些?

  Handler、Looper、Message和MessageQueue

  Handler:负责发送Message到MessageQueue,同时负责接收消息

  Looper:负责从MessageQueue读取消息,并通过Message的target,调用handler的消息分发处理

  Message:消息载体

  MessageQueue:消息队列

3.handler消息机制的主要流程是什么?

  消息发送过程:Handler在子线程发送Message,由于Handler初始化的时候有持有当前线程的Looper和Looper中的                      MessageQueue,而这个Handler在主线程初始化,也就是该Handler持有主线程Looper和MessageQueue,发送普通消息会给    Message的target赋值Handler本身,发送消息会最终会调用MessageQueue的enqueueMessage方法。

  消息插入过程:根据系统相对时间+延迟时间 按照顺序 插入到单链表MessageQueue中,如果在表头,会判断是否有阻塞,有    阻  塞会调用native层的nativeWake方法唤醒线程,如果在表中间或表尾会判断该消息是否是异步消息,且阻塞的话,也会调用    native层的nativeWake方法唤醒线程。

  消息取出过程:Looper循环loop方法,会调用MessageQueue的next方法,这个方法在源码上面显示有可能会阻塞,                    MessageQueue next 也有一个死循环操作,如果上一个取出的消息有阻塞时间,会先调用native层的nativePollOnce方法进行      阻塞,然后会判断是否是屏障Message,如果是屏障消息先过滤掉普通消息,先获取异步消息。如果Message消息的when大于    当前系统相对时间则会设置需要阻塞的时间,如果Message消息的when小于或者等于当前系统相对时间,会取出这个消息。如    果有阻塞的情况,还会调用IdleHandler的回调。通过MessageQueue取出Messgae后,会通过Message的target(target就是发    送该  Message的Handler)调用 Handler的dispatchMessage方法处理消息分发。

  消息分发过程:如果Message callback不为空,代表是通过post的方式发送消息,调用Runnable的 run方法;如果Handler中的    mCallback不为空,调用mCallbcak的 handleMessage;否则会调用handleMessage回调。

扫描二维码关注公众号,回复: 11646766 查看本文章

4.延时消息的实现 是在插入MessageQueue的时候,还是Looper的时候?

  取消息的时候实现的!

  插入的时候会根据相对时间+延迟时间作为message的属性,并根据该属性进行排序。

  真正的延时是在Looper取消息的时候进行判断是否为延时消息,然后调用native层的nativePollOnce方法进行阻塞。

5.发送消息用send 和 post 有什么区别?

  send方式和post方式本质没有区别,都是发送Message,只不过post方式会把Runnable对象赋值给Message的callback,在      最后消息分发的时候会回调Runnable的run方法。

6.为什么建议用obtain方法创建Message?

  Message 本身包含两个Message对象,一个是sPool,一个是next,但通过看源码可知道sPool是一个static对象,是所有对象共    有,Message sPool就是一个单链表结构,Message就是单链表中的一个节点。

  使用obtain方法,取的是Message 的 sPool ,改变sPool指向sPool的next,取出sPool本身,并清空该Message的flags和next。    这样的好处是是可避免重复创建多个实例对象,可以取消息池子之前已经存在的消息

7.子线程能直接新建Handler并使用吗?

(1)直接创建会报错!

    在创建Handler方法中,Handler会持有该线程的Looper 和 Looper中的MessageQueue,Handler的使用必须是结合                  Looper,而子线程中并没有绑定Looper,所以会报错。

    因为Handler的构造方法中可以传递Looperhandler对象所绑定的线程其实并不取决于该handler对象由哪个线程构建,而        是取决于该handler对象所绑定的Looper属于哪个线程

    handleMessage 方法执行的线程一定是在创建Handler的线程吗?答案是否定的,是在绑定Looper所属于的那个线程。

(2)想要创建需要干嘛

    1. 在创建Handler前首先调用Looper.prepare()创建Looper

    2. 然后调用Looper.loop()开启循环(只要保证发消息之前调用过该方法即可,否则Looper不运转,

    注意:不必纠结这一步是在new Handler前 还是在 new Handler后)

    两步缺一不可!   

8.Looper是如何确保一个线程只能创建一个的?MessageQueue呢?

  1. 利用 ThreadLocal特性。(1)判断ThreadLocal里面是否为空(2)把Looper放到ThreadLocal中,

  2. 利用私有构造方法 只能在类里面被访问,无法被类外访问。

  3. ThreadLocal 的详细解释:https://blog.csdn.net/wsq_tomato/article/details/82390262

    public static void prepare() {
        prepare(true);
    }
 
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
 
  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

9.HandlerThread和普通线程有何区别?

  其实就是一个自己封装好Looper的线程,源码如下

  构造方法:

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

  run方法:

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

10.Handler同步屏障

(1)Message消息主要分为三种:同步消息、屏障消息、异步消息

(2)同步屏障就是通过屏障消息来屏蔽所有同步消息,处理后续异步消息

核心代码:

  首先是 MessageQueue 取消息的代码:

    @UnsupportedAppUsage
    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
 
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
 
            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) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    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;
                }
 
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
 
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
 
            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
 
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
 
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
 
            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;
 
            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

最核心的是的判断是这段代码:

    if (msg != null && msg.target == null) {
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
        do {
                prevMsg = msg;
                msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
    }

屏障消息主要是起一个屏蔽作用,所以不需要有handler 处理消息分发,所以target为null。

如果有屏障消息,且消息不是异步的则继续取下一个,直到遇到异步消息。

异步消息如何设置?:

  (1) 可以直接调用Message的setAsynchronous方法

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

  (2) Handler的构造方法也可以设置 mAsynchronous,但是是hide标签修饰的方法,我们可以通过反射去调用,也是可以的。

同步屏障的应用?:

  比如:屏幕刷新机制

  Android 每隔16.6ms会刷新一次屏幕,每个Activity对应一个 DecorView 根布局View树。

  初始化过程中DecorView会被添加到viewRootImp(根视图);

  根视图setView的过程:

    viewRootImp.setView() —> 
    viewRootImp.requestLayout() —> 
    viewRootImp.scheduleTraversals() —> 
    viewRootImp.doTraversal() —>                 
    viewRootImp.performTraversals()—>

主要看scheduleTraversals这个方法:

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //设置同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //发送异步消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

其中“ mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();  ”这行代码就是设置同步屏障。

然后咱们对这行代码进行跟踪:

    mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
    @TestApi
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }
 
     @TestApi
    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }
 
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }
    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }
 
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
 
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

到最后发送了一个异步消息。

因为 同步屏障的作用,所以 异步消息的执行优先于同步消息,保证了屏幕刷新的及时性优先性

完毕,如果哪些地方写错了,请各位指出,深海谢过各位同行。

猜你喜欢

转载自blog.csdn.net/qq_39731011/article/details/107905939