Android_UIthread(UI线程原理以及和主线程的关系)

这个问题,先从我们在处理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是在主线程创建的,所以我们也就理解了下面的问题

  1. 为什么Android只有在主线程才能更新UI?
  2. 为什么一般情况下我们称主线程为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();
    }
发布了41 篇原创文章 · 获赞 35 · 访问量 4301

猜你喜欢

转载自blog.csdn.net/weixin_38140931/article/details/103404695