Android Handler 消息机制

Handler,MessageQueue,Looper 三者的初始化。

这是Handler构造函数,当我们new一个Handler,Looper和MessageQueue就跟着初始化了。

Looper:Looper myLooper() / Looper prepare()

可以看到Looper是通过sThreadLocal.get()去拿这个对象,ThreadLocal是一个以线程为作用域的存储容器,后面细说。既然这里有get,那么必然有个地方去set。

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

这个set的地方就在Looper.prepare() –> sThreadLocal.set(new Looper(quitAllowed));

    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));
    }

MessageQueue: mQueue = new MessageQueue(quitAllowed) / mQueue = mLooper.mQueue ;

我们看Looper的构造方法代码:


 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

3个异常

异常1: Can’t create handler inside thread that has not called Looper.prepare()

譬如这么一段代码:

      new Thread(new Runnable() {
            @Override
            public void run() {
                new Handler();
            }
        }).start();
 FATAL EXCEPTION: Thread-3
    Process: zj.com.playhandler, PID: 9584
    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:200)
        at android.os.Handler.<init>(Handler.java:114)
        at zj.com.playhandler.SplashActivity$1.run(SplashActivity.java:19)
        at java.lang.Thread.run(Thread.java:760)

异常2: android.view.ViewRootImpl$CalledFromWrongThreadException


FATAL EXCEPTION: Thread-3
    Process: zj.com.playhandler, PID: 26566
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6920)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1081)

这个异常说的是我们不能再子线程中更新UI,他是从哪儿抛出的呢?

是的,是从ViewRootImpl这个类的checkThread方法内抛出来的。

但这里我要多说几句android建议我们不要在子线程内更新UI,但假设我硬要在子线程内改改UI还是可以的,但前提是,在ViewRootImpl初始化完成之前,譬如Activity的onCreate和onResume内,我们在子线程内进行一个UI的更新,代码是可以正常运行的,但是假如我们要是延迟执行个200ms或者将代码写在onPause,onStop内,那么,就会报出这个异常。因为这个时候ViewRootImpl已经初始化完成了

Ps:具体的ViewRootImple流程分析本文暂时不展开讲。等我更新了这部分内容我贴到此处

测试代码:

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv = findViewById(R.id.onetv);

        //不出错
        setUiInSubThread();
    }

    @Override
    protected void onResume() {
        super.onResume();
        //不报错
        setUiInSubThread();
    }

    @Override
    protected void onStop() {
        super.onStop();

        //报错
        setUiInSubThread();
    }

    private void setUiInSubThread() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                tv.setText("哈哈");

            }
        }).start();
    }

总结:

  • 创建Handler的时候,Handler会去调Looper.mylooper方法,Looper.mylooper方法是在自己的threadLocal内get自己的对象,如果能get到,说明在之前Looper已经通过静态方法prepare,构造了一个Looper对象,并set到了当前线程的sThreadLocal对象里,在构造Looper对象的同时,创建了一个MessageQueue对象。

  • 俩条规则:
    不能在主线程中做耗时操作。
    原因:ANR。UI线程阻塞。
    建议在主线程中更新UI,不建议在子线程中更新UI。
    注意:注意我是用的“建议”俩字,为什么说是“建议”,而没说“不能”在子线程中更新,这个问题上面已经刨析过了,主要是围绕ViewRootImpl的初始化问题来讨论这个问题。
    **原因:**UI控件并不是同步的,在一个并发修改UI的环境下,我们无法得到一个预期的UI效果,然而为UI控件加锁得不偿失。所以android将此设计为一个单线程模型来更新UI的方式。

Handler,MessageQueue,Looper 三者为一整体构成的Android消息机制是如何运转起来的。

在很久很久以前...

  • 我们知道,当我们在主线程内使用Handler的时候,并不需要去调用Looper.prepare(),这就说明,在主线程初始化的时候,Looper已经初始化了。

  • 我们知道我们一个APP进程被Fork出来的时候,是一个进程,一个进程内必然包含一个线程,这个线程就是主线程,也就是我们的ActivityThread。

从AMS,ActivityThread,ApplicationThread说起

  • ApplicationThread 是 ActivityThread的一个内部类

    它的主要作用是,作为AMS和ActivityThread的通信器。

  • ActivityThread是整个App的入口,在ActivityThread的main方法内能看到Looper的初始化

消息循环的建立


   /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

可以看到在这个事件死循环内,一直在执行queue.next,if(msg == null) {return }

Thanks

https://blog.csdn.net/u011068702/article/details/53207039
https://www.cnblogs.com/muhe221/articles/4893915.html
https://www.cnblogs.com/younghao/p/5126408.html
https://blog.csdn.net/zhangfei2018/article/details/46518615

猜你喜欢

转载自blog.csdn.net/user11223344abc/article/details/80229023