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

2022-05-24  本文已影响0人  BlueSocks

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中高级面试题汇总
上一篇下一篇

猜你喜欢

热点阅读