关闭

为什么子线程不能更新UI的完全解析!!

340人阅读 评论(0) 收藏 举报
分类:

前言

————————————————————————————————————————————————————

为什么Andorid不能更新子线程更新UI?这里我们从几个方面来探究;

———————————————————————————————————————————————————

1:Android开发人员设计角度


由于Android是典型的GUI(图形用户界面)模型,而现代GUI模型都是单线程设计思想(读者可自行查询相关资料)。大意就是为了避免获取数据时候的竞争导致死锁。多线程问题已经够麻烦了,如果GUI也设计程多线程,肯定是会更加麻烦。


2:SDK源码角度


1:如果子线程更新了UI,我们会得到异常提示,跟进异常抛出类查看,此方法是ViewRootImpl类内部的方法:

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

通过mThread 与 Thread.currentThread()相等来判断是否异常,,

所以重点来了Thread.currentThread()是当前线程,那么mThread是什么线程呢?


     2:我们跟进查看构造方法

public ViewRootImpl(Context context, Display display) {
    ......

    mThread = Thread.currentThread();
    ......
}
知道当ViewRootimpl第一次构造时候就分发给了我们一个currentThread(). 那么创建ViewRootimpl与更新checkThread的线程就必须是一样的才不会有异常。那么我们创建ViewRootImpl的线程是什么线程呢?如下分析


ViewRootImpl分析
官方对于此类解释:

The top of a view hierarchy, implementing the needed protocol between View* and the WindowManager.
 This is for the most part an internal implementation* detail of {@link WindowManagerGlobal}.



ViewRootImpl他是View树的顶级视图,但它却又不是View,只是实现了View与WindowManager之间的通信协议(2.2版本之前使用ViewRoot此后版本更改为ViewRootImpl)。

这里我们要了解就是ViewRootImpl并不是一个View,只是一个将View与WindowManager关联起来的类;分析得:ViewRootImpl也是依附于View与windowManager存在的,再通过此ViewRootImpl类注释分析:就是WindowManagerGlobal具体细节上的一个内部实现“This is for the most part an internal implementation detail of @link WindowManagerGlobal” ——————>跟进

WindowManagerGlobal分析:

通过官方注释得

这个类的关系:是底层类与窗口之间的上下文联系类,我们并不需要使用它,直接使用WindowManager;

类注释叫我们 see windowmanager

WindowManager与windowManagerimpl

然而WindowManager是一个接口 实现了ViewManager,ViewManager也是一个接口。

所以我们要看其实现类 windowMangerImpl,

WindowManagerImpl并没有实现WindowManager的三大操作,而是全部交给WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。就是说WindowManagerImpl将所有的操作全部委托给WindowManagerGlobal来实现。这是一种桥接模式。

通过windowManagerImpl发现其内部就是对WindowManagerGlobal 的封装,各种addview,removeview之类的调用本意就是WindowManagerGlobal 的调用如

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
 @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
@Override
 public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }


ViewManager分析

ViewManager就一个简单的接口,定义了addview,removeview,updateview,三个方法,

小结

通过一系列的跟源码与分析大致流程:ViewManager  -->> WindowManager(继承自ViewManager) -->> WindowManagerImpl(继承自WindowManager) -->>WindowManagerGlobal(暴露实例给WindowManagerImpl调用处理) -->> ViewRootImpl (WindowManagerGlobal的内部具体细节实现)

接下来简单分析Activity的启动

Activity的其实很复杂的,本人也不是完全理解但是我们抓住其入口ActivityThread类即可 (android.app.ActivityThread

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { 
    //...
    ActivityClientRecord r = performResumeActivity(token, clearHide); // 这里会调用到onResume()方法

    if (r != null) {
        final Activity a = r.activity;

        //...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow(); // 获得window对象
            View decor = r.window.getDecorView(); // 获得DecorView对象
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager(); // 获得windowManager对象
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                wm.addView(decor, l); // 调用addView方法
            }
            //...
        }
    }
}
根据注释可以看到:回调onResume方法后  内部得到windowmanager与Window并且addView,加入了一个Decorview,

总结

我们开发是基于Activity,activity的setcontentview一执行 ,系统就给与了我们一个基础的window视图PhoneWIndow实例,

PhoneWindow内部就管理了一个顶级View,DecorVew,层级如图


我们之后的操作都是建立在DecorView上来实现UI的更新。因为创建最顶级DecoView是ActivityThread的window.add进来的,同时因为ViewRoolImpl管理DecoView与window窗体之间的联系,所以DecoView任何子视图ViewRoolImpl的currentThread()其实就是ActivityThread(UI线程),当在非UI线程更新时候checkThread()就出现错误。(有点拗口但是道理就是这么个道理。)

延伸:::

1:子线程不能更新UI是错误的,严格意义上讲是(只有创建了View的顶级父视图的线程才能更新UI。其他线程不能干预,干预了ViewRootImpl就给你异常错误提示,目的是为了避免GUI多线程的其他错误)。

2:子线程如果在onResume之前更新UI(onCreate时候),此时的view只是一个对象还没有与phonewindow之间建立联系。所以可以更新。 但是onResume之后也就是View被主线程添加进window后,ViewRoolImpl此类就能管理view与window之间的联系,此时其他线程不能干预了。


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:27885次
    • 积分:822
    • 等级:
    • 排名:千里之外
    • 原创:55篇
    • 转载:11篇
    • 译文:0篇
    • 评论:8条
    最新评论