欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Handler 消息机制

程序员文章站 2022-03-09 21:04:15
...

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

Android Handler 消息机制

这是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,他是从哪儿抛出的呢?
Android Handler 消息机制

是的,是从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的一个内部类
    Android Handler 消息机制
    它的主要作用是,作为AMS和ActivityThread的通信器。

  • ActivityThread是整个App的入口,在ActivityThread的main方法内能看到Looper的初始化
    Android Handler 消息机制

消息循环的建立

Android Handler 消息机制


   /**
     * 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