Android中Activity、Window、ViewRootImpl与子线程更新UI

三者层级关系

在这里插入图片描述

1、Window

  • Window是一个抽象类,唯一的实现类是PhoneWindow
  • Window分为三种类型应用Window、子Window、系统Window。子Window无法独立存在必须依赖父级Window,例如Dialog必须依附于Activity
  • Window分层,在显示时层级高的窗口会覆盖在在层级低的窗口
类型 层级(z-ordered) 例子
应用 Window 1~99 Activity
子 Window 1000~1999 Dialog
系统 Window 2000~2999 Toast

2、WindowManager

  • WindowManager是Window的管理者,WindowManager是一个接口它的实现类是WindowMangerImpl
  • WindowManager只提供是对View的add、update、remove操作
  • WindowManager的addView()方法最终会创建新的ViewRootImpl而addContentView()直接把View添加到DecorView上

3、WindowManagerGlobal

  • WindowManagerGlobal是单例模式,进程唯一
    在这里插入图片描述

  • WindowManager的add、update、remove操作最终会调用WindowManagerGlobal的方法

  • WindowManagerGlobal下管理了4个List

ArrayList<View> mViews;   //——所有Window的根View
ArrayList<ViewRootImpl> mRoots;  //所有根View对应的ViewRootImpl
ArrayList<WindowManager.LayoutParams> mParams;  //所有根View(Window)对应的布局参数
ArraySet<View> mDyingViews;  //待销毁的根View(Window)
  • WindowManagerGlobal负责对ViewRootImpl进行统一管理,而具体实现是在ViewRootImpl

4、ViewRootImpl

关系图
在这里插入图片描述

  • 一个Window(PhoneWindow)可能存在多个ViewRootImpl链,所以需要一个Window将所有链管理起来
  • WindowManager.addView()调用WindowManagerGlobal.addView(),最终在WindowManagerGlobal.addView()方法中创建RootViewImpl
//WindowManagerGlobal.addView()

public void addView(View view, ViewGroup.LayoutParams params,
       Display display, Window parentWindow, int userId) {
    
    
   //检查参数是否合法
   if (view == null) {
    
    
       throw new IllegalArgumentException("view must not be null");
   }
   if (display == null) {
    
    
       throw new IllegalArgumentException("display must not be null");
   }
   if (!(params instanceof WindowManager.LayoutParams)) {
    
    
       throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
   }
   //子Window需要调整部分布局参数
   final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
   if (parentWindow != null) {
    
    
       parentWindow.adjustLayoutParamsForSubWindow(wparams);
   } else {
    
    
       final Context context = view.getContext();
       if (context != null
               && (context.getApplicationInfo().flags
                       & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
    
    
           wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
       }
   }
  
   ViewRootImpl root;
   View panelParentView = null;

   synchronized (mLock) {
    
    
       ...
       //创建ViewRootImpl对象
       root = new ViewRootImpl(view.getContext(), display);
   	//设置View的布局属性
       view.setLayoutParams(wparams);
   	//将相关信息保存到对应集合
       mViews.add(view);
       mRoots.add(root);
       mParams.add(wparams);

       try {
    
    
           root.setView(view, wparams, panelParentView, userId);//调用ViewRootImpl对象的setView方法(这里也是View绘制的根源)
       } catch (RuntimeException e) {
    
    
           ...
       }
   }
}
  • 一个Window可能调用多次WindowManager.addView(),所以可能有多个ViewRootImpl,例如调用3次WindowManager.addView()就存在1个Activity创建时新建的ViewRootImpl和3个后续创建的ViewRootImpl共4个
  • ViewRootImpl是链接WindowManager和DecorView的纽带。ViewRootImpl有很多作用,它负责Window中对View的操作,是View的绘制流程和事件分发的发起者
  • ViewRootImpl负责与WMS通信

5、WindowManagerService(WMS)

在这里插入图片描述

  • WMS负责实际的窗口绘制工作
  • ViewRootImpl中的mWindowSession参数是IWindowSession的实例,用于WMS通讯的代理
public ViewRootImpl(Context context, Display display, IWindowSession session,boolean useSfChoreographer) {
    
    
    mContext = context;
    mWindowSession = session;//从WindowManagerGlobal中传递过来的IWindowSession的实例,它是ViewRootImpl和WMS进行通信的代理。
    mDisplay = display;
    mThread = Thread.currentThread();//保存当前线程
    mFirst = true; //true表示第一次添加视图
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
    ...
}

子线程更新UI

我们一般在主线程更新UI,如果将更新UI的操作放在子线程中会程序崩溃,提示错误信息

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6581)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)
……

通过错误信息可以看到问题出在ViewRootImpl.checkThread()方法

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

可以看出在更新UI时会将当前线程与mThread做对比,不相同则报错,我们顺着mThread看看它是怎么来的

public ViewRootImpl(Context context, Display display, IWindowSession session,
            boolean useSfChoreographer){
    
    
            	...
            	mThread = Thread.currentThread();
            	...
            }

mThread是在ViewRootImpl初始化时记录的调用初始化的线程,也就是说再更新UI时并没有判断是否是主线程,而是判断更新UI的线程与创建UI的线程是否一致
Activity的ViewRootImpl是在Activity.onResume()中创建的,而Activity的创建必须在主线程中,所以一般情况下只能在主线程更新UI
顺着这个思路想,我们完全可以在子线程中更新UI,分为两种情况

  1. 在ViewRootImpl创建之前,也就是在onResume之前子线程中更新UI
  2. 将ViewRootImpl初始化过程放在子线程,这样我们也能在这个子线程中更新UI

第一种方法可以在onCreat中更新UI,但由于线程的不确定性运行起来不稳定
我们可以利用第二种方法,将ViewRootImpl初始化过程放在HandlerThread中

//子线程创建ViewRootImpl
Runnable runnable = new Runnable() {
    
    
        @Override
        public void run() {
    
    
        TextView tv = new TextView(context);
        tv.setBackgroundColor(Color.GRAY);  //背景灰色
        tv.setGravity(Gravity.CENTER);  //居中展示
        tv.setTextSize(30);
		
        WindowManager manager = context.getWindowManager();
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                0,0,
                WindowManager.LayoutParams.FIRST_SUB_WINDOW,
                WindowManager.LayoutParams.TYPE_TOAST,
                PixelFormat.TRANSPARENT);
        //最终调用WindowManagerGloble.addView中创建ViewRootImpl
        manager.addView(tv, params);
        }
    };

//子线程中每隔1秒更新UI
Runnable runnable2 = new Runnable() {
    
    
        @Override
        public void run() {
    
    
            textView.setText(String.valueOf(time));
            Log.d("textViewThread : ", String.valueOf(Thread.currentThread()));
            time++;
            handler.postDelayed(this, 1000);
        }
    };
   
thread = new HandlerThread(THREAD_ID);
thread.start();
handler = new Handler(thread.getLooper());

handler.post(runnable);
handler.post(runnable2);

通过上面在子线程中创建ViewRootImpl的方式,可以在子线程中自由的更新UI不用担心报错

参考文章

Window和WindowManager和ViewRootImpl 作者:zhan_haoyu
Activity、Window、ViewRoot、DecorView的关系 作者:元浩875
Android WMS(一)-窗口管理 作者:stan_Z

猜你喜欢

转载自blog.csdn.net/weixin_41046681/article/details/125617916