美文网首页
从SystemUI源码学习子线程更新UI

从SystemUI源码学习子线程更新UI

作者: 浪里_个郎 | 来源:发表于2020-08-06 15:46 被阅读0次

1,原生SystemUI更新状态栏图标


原生SystemUI中,PhoneStatusBarPolicy中通过StatusBarIconList提供的setIcon、removeIcon等方法在主线程直接操作UI,简单直接。
但这里存在一个问题,如果有一些Icon更新的通知来自于子线程,就需要把消息传递到主线程完成UI操作,否则会报错。SystemUI对这套传递机制做了一个封装,我们往下看。

2,其他应用更新状态栏图标

在应用中,我们不能直接访问StatusBarIconList的实例,需要通过StatusBarManager,以Binder的形式向系统服务请求修改状态栏Icon。CommandQueue作为IStatusBar的实现类,向IStatusBarService服务注册了回调,这样服务接收到请求,会把请求发送到CommandQueue。

CommandQueue本身既实现了IStatusBar回调接口,也作为回调的发起者。StatusBarIconList的实现类就实现了CommandQueue.Callbacks接口,并将自己向CommandQueue注册。这样CommandQueue接收到回调后,又向StatusBarIconControllerImpl发送请求,从而实现了其他应用调用StatusBarIconList提供的setIcon、removeIcon等方法操作状态栏的目的。

3,线程什么时候完成了切换?

为什么PhoneStatusBarPolicy更新状态栏Icon需要在主线程,而从StatusBarManager过来的运行在Binder线程的请求可以直接操作状态栏Icon?
细心的人一定看到了,在上图,有一个大大的Handler!没错,CommandQueue接到异步请求后,通过Handler,将消息发往了主线程:

//CommandQueue.java
    private Handler mHandler = new H(Looper.getMainLooper());

    public void setIcon(String slot, StatusBarIcon icon) {
        synchronized (mLock) {
            // don't coalesce these
            mHandler.obtainMessage(MSG_ICON, OP_SET_ICON, 0,
                    new Pair<String, StatusBarIcon>(slot, icon)).sendToTarget();
        }
    }

CommandQueue的setIcon函数中并没有更新UI,而是将消息通过Handler发往了主线程的MessageQueue。自己投的篮自己抢篮板,发消息的mHandler接收到消息后,就可以通知回调真正处理UI了,因为这时来自Binder线程的请求已经洗白,在主线程中执行了。

//CommandQueue.java
    private final class H extends Handler {
        private H(Looper l) {
            super(l);
        }

        public void handleMessage(Message msg) {
            final int what = msg.what & MSG_MASK;
            switch (what) {
                case MSG_ICON: {
                    switch (msg.arg1) {
                        case OP_SET_ICON: {
                            Pair<String, StatusBarIcon> p = (Pair<String, StatusBarIcon>) msg.obj;
                            for (int i = 0; i < mCallbacks.size(); i++) {
                                mCallbacks.get(i).setIcon(p.first, p.second);
                            }
                            break;
                        }
                        case OP_REMOVE_ICON:
                            for (int i = 0; i < mCallbacks.size(); i++) {
                                mCallbacks.get(i).removeIcon((String) msg.obj);
                            }
                            break;
                    }
                    break;
                }

最后总结下其他应用操作状态栏Icon的流程:


4,android中异步更新UI有几种方式?

一般我们会说有4种方式:
1,Handler发送Message
2,AsycTask
3,Activity.runOnUiThread
4,Handler.post(Runnable)
看起来选择很丰富嘛!但实际上,这四种方式本质上都是第一种方式!让我们逐一分析。

4.1,Handler发送消息

这种方法不多说了,原理请阅读Handler源码。前面SystemUI的例子用的就是Handler发送消息实现子线程通知主线程更新UI。

4.2,AsycTask

AsycTask本质是对Handler的封装。使用方法如下。
创建继承自AsycTask的类,并重写以下方法:

//耗时方法,运行在子线程
@WorkerThread
protected abstract Result doInBackground(Params... params);
//在doInBackground之前调用,在UI线程内执行
@MainThread
protected void onPreExecute() {
}
//在执行中,且在调用publishProgress方法时,在UI线程内执行,用于更新进度
@MainThread
protected void onProgressUpdate(Progress... values) {
}
//在doInBackground之后调用,在UI线程内执行
@MainThread
protected void onPostExecute(Result result) {
}

需要关注的,就是子线程转向主线程是如何做到的,可以看到就是通过Handler:

//子线程中通过Handler向主线程发送进度信息,从而在主线程执行onProgressUpdate函数
    @WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

其他关于AsycTask的细节就不展开了。

4.3,Activity.runOnUiThread

这个就简单了,直接上代码:

    @Override
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);  //又是Handler
        } else {
            action.run();
        }
    }

4.4,Handler.post(Runnable)

其实就是把Runnable封装成了Message,本质上还是通过Handler发Message:

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

5,深度思考

1,Android为什么不让子线程更新UI
因为android并没有对UI更新做并发处理(没有加锁),强行并发会导致UI显示混乱。

2,Android怎么控制不让子线程更新UI
Activity进行UI操作时,会是用ViewRootImpl做了线程检测:

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

ViewRootImpl会用保存的主线程(mThread )与当前执行线程作比较,如果当前不是主线程,则抛异常。

3,有没有办法在子线程更新UI?
有。Activity中的ViewRootImpl对象是在onResume方法回调之后才创建的,在这之前的onCreate方法,甚至是onResume方法里都可以实现子线程更新UI,因为此时还没有创建ViewRootImpl对象, 可以跳过线程检测的流程。

相关文章

  • 从SystemUI源码学习子线程更新UI

    1,原生SystemUI更新状态栏图标 原生SystemUI中,PhoneStatusBarPolicy中通过St...

  • 子线程更新UI全解

    1 子线程更新 UI 异常设计理念及简单源码解析 初学者可能会犯在子线程更新 UI 的错误,例如: 一旦运行,应用...

  • Android 为什么不能再子线程更新UI

    作为android开发人员,总是被要求着不能再子线程去更新UI,必须得再主线程更新UI,由于好奇,也由于看这些源码...

  • 如何做到在子线程更新 UI?

    一般来讲,子线程是不能更新 UI 的,如果在子线程更新 UI,会报错。 但在某种情况下直接开启线程更新 UI 是不...

  • Android在线程中更新UI和在协程中更新UI

    1、在子线程里面更新UI 我们都知道Android只能在主线程里面对UI更新,所以谷歌提供了很多在子线程里面更新U...

  • 线程通讯详解

    关于子线程能否更新UI的思考线程通讯详解线程池-多线程的高效使用姿势 上文我们说到了关于子线程中能否更新UI的问题...

  • Android View.post()

    View.post()方法使用场景 子线程中更新ui。 onCreate()中调用获取view宽高。 下面看看源码...

  • Handler源码分析

    Handler主要用于线程切换,一个典型的应用场景是:子线程通过Handler更新主线程UI本文将从源码上来介绍H...

  • 多线程跨线程操作UI如何做?

    多线程如何切换线程更新UI操作? 来看看runOnUiThread()方法的源码实现: 通透!!!

  • 子线程更新UI的方法

    子线程中不能直接更新UI,如果直接更新的话会发生崩溃所以要在主线程中更新UI,总计三种回到主线程更新UI的方式 1...

网友评论

      本文标题:从SystemUI源码学习子线程更新UI

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