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();
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章