三者层级关系
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,分为两种情况
- 在ViewRootImpl创建之前,也就是在onResume之前子线程中更新UI
- 将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