美文网首页
从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

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