美文网首页
Handler源码解析,Handler切换线程的本质是什么?

Handler源码解析,Handler切换线程的本质是什么?

作者: BlueSocks | 来源:发表于2022-05-24 21:03 被阅读0次

    Android在主线程以外访问UI,会得到一个异常。
    它是从ViewRootImpl类抛出的:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
    
    

    安卓之所以不允许在主线程外访问UI,考虑的是并发访问会对UI显示造成影响。
    如果加上锁机制,会产生两个问题:
    1. UI访问逻辑会变复杂
    2. 效率下降

    所以安卓选择在主线程更新UI,但是安卓建议不要在主线程进行耗时任务,可能会导致ANR,所以我们需要这样一个机制:
    1.在子线程执行耗时任务后,通知主线程更新UI
    如果子线程需要主线程更新UI时,主线程正在更新UI,或者有其他的子线程也想更新UI,就需要解决两个问题:
    1.子线程通知UI更新后,不想阻塞,要继续做自己的事
    2.哪个更新UI的通知先处理,哪个后处理?

    Android用一个MessageQueue来解决,我们看一下MessageQueue的入队代码:

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
        //msg.target是处理这个msg的Handler
            throw new IllegalArgumentException("Message must have a target.");
        }
        //...
            //when是UI更新的延迟时间
            msg.when = when;
            //链表头
            Message p = 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 {
                //...
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            //...
        }
        return true;
    }
    
    

    1.MessageQueue用的是单链表
    2.每个Message必须有个target属性,它是处理这个Message的Handler

    下面是出队方法MessageQueue.next() 的一部分:

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

    Message有个when属性,用来解决定时问题,入队时不管when都入队,出队判断now < msg.when时,取下一个消息。
    MessageQueue提供了出队入队方法,谁来调用这些方法呢?
    Looper:

    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 (;;) {
            Message msg = queue.next(); // 消息队列没有消息会在这里阻塞
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
    //...
            try {
                msg.target.dispatchMessage(msg);
    //...
    
            msg.recycleUnchecked();
        }
    }
    
    

    Looper.loop()是个死循环,不断从消息队列中取消息,然后调用 msg.target.dispatchMessage(msg); msg目标Handler的dispatchMessage(),也就是说调用了发送这条消息的Handler的dispatchMessage()方法:

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg); //用法1
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {//用法2
                    return;
                }
            }
            handleMessage(msg);//用法3
        }
    }
    
    

    整个流程如图所示:

    从上面的代码可以得出使用Handler的几种方式
    用法1. 用Handler.post(Runnable r)方法发送消息:

    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    

    创建消息:

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    
    

    dispatchMessage中满足msg.callback != null,将会调用

    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    

    用法2.在Handler构造方法中传入Callback

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }
    
    
    public Handler(@Nullable Callback callback, boolean async) {
    //...
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
    

    用法3.重写Handler的handleMessage()方法

    private Handler mHadler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1: {
                    //TODO:自己写处理消息的方法
                    break;
                }
            }
        }
    };
    
    

    我们看到:Handler中并没有切换线程的操作,Handler只负责发送消息和处理消息,那如何在子线程发送消息到UI线程呢?看看应用启动时调用的ActivityThread的main()方法:

    public static void main(String[] args) {
        //...
        Looper.prepareMainLooper();
        //...
        Looper.loop();
    }
    
    
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    

    prepareMainLooper() 做了2件事:
    1. prepare(false);
    2.sMainLooper = myLooper();

    看第一件事:

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
        //一个线程只能创建一个Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    

    sThreadLocal.set(new Looper(quitAllowed));新建了一个quitAllowed为false的Looper,并把它放在了 sThreadLocal中。在Looper中:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    

    ThreadLocal被设计为一种作用域为线程的变量,它的set方法会把变量放在只有与该方法调用线程相同的线程才能访问(用get()获得)。看下面的示例:

    public class ThreadLocalTest {
        ThreadLocal<Integer> mIntThreadLocal = new ThreadLocal<>();
        
        public void firstThread(int val) {
            new Thread("firstThread") {
                @Override
                public void run() {
                    super.run();
                    mIntThreadLocal.set(val);
                    printLog(mIntThreadLocal.get());
                }
            }.start();
        }
    
        public void secondThread(int val) {
            new Thread("secondThread") {
                @Override
                public void run() {
                    super.run();
                    mIntThreadLocal.set(val);
                    printLog(mIntThreadLocal.get());
                }
            }.start();
        }
    
        public void printLog(Integer integer) {
            System.out.println("thread name: "+ Thread.currentThread().getName()+ "\nval= " + integer );
        }
    }
    
    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        test.mIntThreadLocal.set(0);
        test.printLog(test.mIntThreadLocal.get());
        test.firstThread(1);
        test.secondThread(2);
    }
    
    

    结果:

    所以,在prepareMainLooper()中调用的Looper.prepare 就是为创建了一个当前线程独占的Looper,并且让sMainLooper指向这个Looper,然后我们就能在子线程调用 getMainLooper()中获取主线程的Looper:

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
    
    

    在Looper.prepare()创建了Looper,在Looper的构造函数中,创建了MessageQueue(这里也说明我们可以在调用Looper.prepare()后就发送消息,MessageQueue会保留这些Message,而不必等Looper.loop()):

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

    所以,应用在启动时,就新建了UI线程的Looper和MessageQueue。我们要在主线程更新UI,可以使用getMainLooper():

    Handler UIHandler = new Handler(getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    //TODO: change UI
            }
        }
    };
    UIHandler.obtainMessage(1).sendToTarget();
    
    

    下面测试一下在子线程使用Handler

    public class MainActivity extends AppCompatActivity {
        private Handler mHandler;
        Looper myLooper;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(), r -> {
                Thread thread = new Thread(r);
                thread.setName("myLooperThread");
                return thread;
            });
            executor.execute(() -> {
                Looper.prepare();
                myLooper = Looper.myLooper();
                Looper.loop();
            });
            //----------主线程运行代码---------
            if (myLooper != null) {
                mHandler = new Handler(myLooper) {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        switch (msg.what) {
                            case 1:
                                //在子线程处理消息
                                Toast.makeText(MainActivity.this, "收到消息1", Toast.LENGTH_SHORT).show();
                                break;
                        }
                    }
                };
                mHandler.obtainMessage(1).sendToTarget();
            } else {
                Toast.makeText(MainActivity.this, "myLooper == null!" , Toast.LENGTH_SHORT).show();
            }
        }
    
    

    运行结果:

    这是因为当执行到mHandler = new Handler(myLooper)的时候,myLooperThread的Looper可能还没有创建好。所以在子线程使用Handler,要注意Looper的创建时机,或者加访问锁,在子线程的获取Looper的方法中加上wait(),然后在创建完Looper之后notifyAll()。看看Android自带的HandlerThread的代码,果然加上了锁:

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();//阻塞获取Looper的其他线程
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    
    
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();//HandlerThread的run方法中包含prepare()
        synchronized (this) {
            mLooper = Looper.myLooper(); 
            notifyAll();//通知所有观察此对象的线程切换为运行态
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();//HandlerThread的run方法中包含loop()
        mTid = -1;
    }
    
    

    使用HandlerThread,并且测试它的quit()方法:

    public class MainActivity extends AppCompatActivity {
        private Handler mHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            HandlerThread handlerThread = new HandlerThread("myLooperThread") {
                @Override
                public void run() {
                    super.run();//调用父类的run()方法来prepare loop
                    //如果不调用quit()或quitSafely()方法,下面的代码将不会调用,因为loop()方法是个死循环
                    Toast.makeText(MainActivity.this, "Looper.loop()停止了", Toast.LENGTH_SHORT).show();
                }
            };
            handlerThread.start();
            mHandler = new Handler(handlerThread.getLooper()) { //当获取不到handlerThread的Looper
            //时,主线程阻塞在这里
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case 1:
                            //在子线程处理消息
                            Toast.makeText(MainActivity.this, "收到消息1", Toast.LENGTH_SHORT).show();
                            mHandler.obtainMessage(2).sendToTarget();
                            handlerThread.quit();
    //                        handlerThread.quitSafely();
                            break;
                        case 2:
                            Toast.makeText(MainActivity.this, "收到消息2", Toast.LENGTH_SHORT).show();
                            break;
                    }
                }
            };
            mHandler.obtainMessage(1).sendToTarget();
        }
    }
    
    

    运行结果:

    没有收到消息2,因为HandlerThread.quit()会让loop()结束:

    for (;;) {
        Message msg = queue.next(); // 没有消息会阻塞在这里
        if (msg == null) {
            // msg为null,说明MessageQueue正在退出,将跳出loop()循环
            return;
        }
        //...
    }
    
    

    使用quit()方法将来不及收到消息2,而使用quitSafely()方法,将会收到消息2. quitSafely()让Looper能在处理完MessageQueue中现存的所有消息后退出。

    最后,给大家分享一些大佬整理的学习资料,里面包括Java基础、framework解析、架构设计、高级UI开源框架、NDK、音视频开发、kotlin、源码解析、性能优化等资料,还有2022年一线大厂最新面试题集锦,都分享给大家,助大家学习路上披荆斩棘~ 能力得到提升,思维得到开阔~ 有需要的可以在我的【公众号】免费获取!!!

    Android framework开发揭秘 2022最新Android中高级面试题汇总

    相关文章

      网友评论

          本文标题:Handler源码解析,Handler切换线程的本质是什么?

          本文链接:https://www.haomeiwen.com/subject/drrsprtx.html