这个问题,先从我们在处理UI创建和更新时犯的错说起,下面两段就是我们在误操作情况打出的信息,
originally added here
at android.view.ViewRootImpl.<init>(ViewRootImpl.java:511)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:346)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
2019-12-05 19:07:43.578 793-811/com.example.mytestuithread E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.example.mytestuithread, PID: 793
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.widget.TextView.checkForRelayout(TextView.java:8908)
at android.widget.TextView.setText(TextView.java:5730)
at android.widget.TextView.setText(TextView.java:5571)
at android.widget.TextView.setText(TextView.java:5528)
我们先看看事故原因,Only the original thread that created a view hierarchy can touch its views.,这说了,只有在创建view hierarchy的最原始的线程中才能接触到它的view;我们的理解就是,只有创建UI的线程才能更新UI,其他线程不能更新UI.这应该是我们的开发常识;
1.1ViewRootImpl.checkThread()
这个view hierarchy就是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}.
*
* {@hide}
*/
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
}
我们顺着堆栈信息接着看;开始出现问题的是在ViewRootimpl的checkThread()方法中;我们到这个ViewRootImpl中一探究竟:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
果然,在checkThread()方法中,首先有一个check当前线程的动作,当当前线程不是mThread对象时,就会报出我们上面的错误,那我们就要去看看这个mThread是个什么东西了,他又是哪里被赋值初始化的.
1.2 ViewRootImpl.ViewRootImpl() // ViewRootImpl的创建
我们看到
public ViewRootImpl(Context context, Display display) {
...
mThread = Thread.currentThread();
...
}
我们找到在ViewRootImpl的构造方法中,有对mThread的赋值,可以看出就是将创建ViewRootimpl的线程对象赋值给了mThread.mThread就是创建ViewRootimpl的线程对象.既然知道了ViewRootimpl的线程才能跟新UI,那我们需要继续看看,ViewRootimpl是在哪里被创建的?
2.1 WindowManagerGlobal.addView()
确实,这么逆着代码运行顺序查找很难,我这里就直接给出ViewRootimpl是在哪里被创建的,是在WindowManagerGlobal.java 的addView()方法中创建的:
/frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
...
}
我们来看看这个WindowmanagerGlobal的定义,首先看看他的注解,这是我们了解一个类的最直接的方式,虽然不是很全面,但是为我们进一步阅读代码提供了方向.
/**
* Provides low-level communication with the system window manager for
* operations that are not associated with any particular context.
*
* This class is only used internally to implement global functions where
* the caller already knows the display and relevant compatibility information
* for the operation. For most purposes, you should use {@link WindowManager} instead
* since it is bound to a context.
*
* @see WindowManagerImpl
* @hide
*/
public final class WindowManagerGlobal {
...
}
根据注解,我们首先翻译成中文:这个类为任何非特定context相关的操作提供了和系统windowManager之间较低水平的交互,这个类只是用于内部实现全局函数,其中调用者已经知道显示和操作的相关兼容性信息,为了更多的信息,你应该去使用WindowManager,因为WindowManager绑定了context. [翻译的好烂,先凑合理解]-----------------最后google告诉我们去WindowManagerImpl中去看看;
3.1 WindowManagerImpl.addView()
我们一跳转到WindowManagerImpl中,就可以看到WindowManagerImpl中持有了一个WindowManagerGlobal对象;那我们就要了解了解这个WindowManagerImpl是个什么了,其实看名称,就知道他和WindowManager的具体实现有关系,先看注解:
/frameworks/base/core/java/android/view/WindowManagerImpl.java
/**
* Provides low-level communication with the system window manager for
* operations that are bound to a particular context, display or parent window.
* Instances of this object are sensitive to the compatibility info associated
* with the running application.
*
* This object implements the {@link ViewManager} interface,
* allowing you to add any View subclass as a top-level window on the screen.
* Additional window manager specific layout parameters are defined for
* control over how windows are displayed. It also implements the {@link WindowManager}
* interface, allowing you to control the displays attached to the device.
*
* <p>Applications will not normally use WindowManager directly, instead relying
* on the higher-level facilities in {@link android.app.Activity} and
* {@link android.app.Dialog}.
*
* <p>Even for low-level window manager access, it is almost never correct to use
* this class. For example, {@link android.app.Activity#getWindowManager}
* provides a window manager for adding windows that are associated with that
* activity -- the window manager will not normally allow you to add arbitrary
* windows that are not associated with an activity.
*
* @see WindowManager
* @see WindowManagerGlobal
* @hide
*/
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
...
}
这里就不全翻译了,我们只要知道WindowManagerImpl就是WindowManager的具体实现,然后
果然我们就在windowManagerImpl中看到windowManagerGlobal的addView()方法的调用,
现在我们就要开始查看,是在哪里调用了WindowManagerimpl的addView()方法的了
3.2 WindowManagerImpl.createLocalWindowManager()
我们从上面知道了WindowManagerImpl手机WindowManager的具体实现,这里让我想起了我们之前在分析Android_activity事件分发流程分析时的问题,就是phoneWindow是Window的具体实现,我们需要拿到PhoneWindow的对象,这里的问题也在于我们怎么接下来需要使用windowManageImpl的对象;我们先看看windowManagerImpl的对象的创建;
在这里我们需要关注其中的createLocalWindowManager()方法
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
这是windowManagerimpl为其他类拿到自身的对象而提供的一个方法,所以我们需要接着来要看看在哪里调用了createLocalWindowManager()
4.1 Window.setWindowManager()
我们发现,在Window中的setWindowManager()方法最终调用了createLocalWindowManager()方法,如下所示:
/frameworks/base/core/java/android/view/Window.java
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
在这里,WindowManagerImpl对象就赋值给了mWindowManager,对mWindowManager的操作最后会在WindowManagerImpl中执行,
4.2 Window.getWindowManager()
我们成功把WindowManagerImpl对象就赋值给了mWindowManager,接着就要看看,哪里引用了这个mWindowManager,经过搜索我们可以发现有一个getWindowManager()方法最后是向调用者返回了mWindowManager. 那我们搜索一下,是谁取到了这个mWindowManager;
5.1 Activity.attach()
最后我们发现在Activity中持有WindowManagerImpl的一个对象.如下所示.
/frameworks/base/core/java/android/app/Activity.java
private WindowManager mWindowManager;
...
final void attach(Context context, ActivityThread aThread...){
...
mWindowManager = mWindow.getWindowManager();
...
}
5.2 Activity.getWindowManager()
紧接着,Activity通过getWindowManager()将WindowManagerImpl对象传递给调用方,
/** Retrieve the window manager for showing custom windows. */
public WindowManager getWindowManager() {
return mWindowManager;
}
6.1 AvtivityThread.handleResumeActivity()
最中我们会发现.在ActivityThread的handleResumeActivity()方法中会调用getWindowManager().取得了WindowManagerImpl对象并调用了其addView()方法;如下所示:
/frameworks/base/core/java/android/app/ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
...
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
...
}
wm.addView(decor, l);
...
这个handleResumeActivity最终会走Activity的onResume()方法?哈哈,onResume()方法就很熟悉了.接下里就不用多说了
小结
我们以上就是从故障出发点逐步往后推,我们现在将这个过程再从前往后梳理一下,
–>Activitythread.handleResumeActivity()
–>WindowManagerImpl.addView()
–> WindowManagerGlobal.addView()
–>ViewRootImpl的创建
最后发现,ViewRootImpl是在主线程创建的,所以我们也就理解了下面的问题
- 为什么Android只有在主线程才能更新UI?
- 为什么一般情况下我们称主线程为UI线程?
注意
在这里我之前有个很大的误区,认为在加载布局的线程就是创建UI的线程,我在子线程调用了SetContentView(…)方法这个来加载布局,但是却不能在子线程中去更新UI,这可把我搞迷糊了.
所以一定要高清楚,这个UI线程指的是创建ViewRootimpl的线程,不是加载UI布局所在的线程,加载布局在哪里做都不会影响ViewRootImpl的创建
关于setContentView()的可以看我之前的梳理:Activity setContentView主要流程
如下是我在子线程加载布局的代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate: currentThread id ="+ Thread.currentThread().getId());
// setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Log.d(TAG, "run: currentThread id" + Thread.currentThread().getId());
getWindow().setContentView(R.layout.second_layout);
mShow = findViewById(R.id.show);
mClick = findViewById(R.id.button);
mClick.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "onClick: currentThread id" + Thread.currentThread().getId());
mShow.setText("更新UI的线程ID = " + Thread.currentThread().getId());
}
});
Looper.loop();
}
}).start();
}