天天看點

Activity、Window、View、ViewRootImpl的了解

概述:

對于題目中的幾個對象,我們可以簡單地了解為Activity封裝了Window,而Widnow又封裝了View,而View又是通過ViewRootImpl把它添加WindowManagerService中去的。

要較詳細的分析,我們可以先從Activity的啟動開始分析。

源碼分析:

一個Activity開始啟動的時候,會在ActivityThread中調用handleLaunchActivity

handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
	
	//應用全局windowmanager的初始化
	 WindowManagerGlobal.initialize();
	
	//執行建立Activity的方法
	final Activity a = performLaunchActivity(r, customIntent)
	
	}
           

這裡初始化了WindowManagerGlobal,它使用單例模式,在整個應用存在。同時WindowManagerGlobal這個類後面将作為WindowManagerImpl政策模式的内部成員變量,代理其大部分功能。

接下來跟進performLaunchActivity方法:

performLaunchActivity(ActivityClientRecord r, Intent customIntent){
		 
		 //建立Context
		 ContextImpl appContext = createBaseContextForActivity(r); 
		
		//類加載器建立Activity
		java.lang.ClassLoader cl = appContext.getClassLoader();
		activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
					
		//建立Application			
		Application app = r.packageInfo.makeApplication(false, mInstrumentation);
		
		//activity關聯到系統服務上
		activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
		
	}
           

從上面代碼可以看到,在ActivityThread中建立了activity的執行個體,并調用activity的attach方法。一看整個方法的名字就可以猜到,activity肯定是要把自己和某些重要的東西關聯起來了。

來看看activity中對應的代碼段:

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
		
		//建立phonewindow
		mWindow = new PhoneWindow(this, window, activityConfigCallback);
		
		//UiThread就是ActivityThread
		 mUiThread = Thread.currentThread();
		 //這裡把activity的window和windowmanager關聯起來
		mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
				
		//給activity中的windowmanager指派		
		 mWindowManager = mWindow.getWindowManager();
	
		}
           

這個方法裡面出現了PhoneWindow,它是抽象類Window的唯一實作類。可以看出PhoneWindow是作為activity的一個成員變量出現的。

執行個體化PhoneWindow之後,接着給它設定了WindowManager,這樣activity的window和windowmanager就關聯了起來。

這裡可以拓展一下,其實這個mWindowManager并不是系統服務的全局WindowManager,而是一個相當于隻屬于此activity的“localWindowManager”,僅作了解。

分析到這裡,隻是做了一些前期的準備工作,繼續吧。

ActivityThread的performLaunchActivity在調用了activitity的attach方法之後,還有動作哦:

不用說,這個方法跟下去肯定會調用到activity的onCreate方法,這個方法我們再熟悉不過了。

于是乎,就來到了了setContentView():

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
           

我們上面分析了,這個getWindow()擷取到的對象就是PhoneWindow,于是來看看PhoneWindow對應的方法:

//成員變量
	private DecorView mDecor;

	ViewGroup mContentParent;
	
	 public void setContentView(int layoutResID) {
       
        if (mContentParent == null) {
			//初始化decorview以及對應的mContentParent
            installDecor();
        } 
		
		//把内容資源布局檔案加載到mContentParent控件上
		mLayoutInflater.inflate(layoutResID, mContentParent)
        
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
		
    }
           
private void installDecor() {
 
  if (mDecor == null) {
            mDecor = generateDecor(-1);
           ......         
        }
  if (mContentParent == null) {
		mContentParent = generateLayout(mDecor);
  }
 }
           

這裡又一個關鍵類DecorView出現了,它作為PhoneWindow的成員變量,在這裡是作為Actiity的頂級根View,準确說是ViewGroup。

這裡的mContentParent則是DecorView内部的一個子View,它同樣是一個ViewGroup,并且我們設定的xml檔案就會被加載到這個ViewGroup上。上面的代碼中已經标注了出來。

當然,DecorView雖然是繼承FrameLayout,但是看它的内部,同樣還有一個頂級的根View:

ViewGroup mContentRoot;
	//由phonewindow調用
  void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {

        final View root = inflater.inflate(layoutResource, null);
		// Put it below the color views.
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 
        mContentRoot = (ViewGroup) root;
    }
           

這裡的layoutResource會根據不同的主題Thme來設定不同的xml,例如:

常用的screen_progress.xml
           
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
>
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />

    <RelativeLayout android:id="@android:id/title_container" 
        style="?android:attr/windowTitleBackgroundStyle"
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
    >
    <ProgressBar android:id="@+android:id/progress_circular"
            style="?android:attr/progressBarStyleSmallTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="5dip"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:visibility="gone"
            android:max="10000"
        />
        <ProgressBar android:id="@+android:id/progress_horizontal"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="2dip"
            android:layout_alignParentStart="true"
            android:layout_toStartOf="@android:id/progress_circular"
            android:layout_centerVertical="true"
            android:visibility="gone"
            android:max="10000" 
        />
        <TextView android:id="@android:id/title"
            style="?android:attr/windowTitleStyle"
            android:layout_width="match_parent" 
            android:layout_height="match_parent"
            android:layout_alignParentStart="true"
            android:layout_toStartOf="@android:id/progress_circular"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:scrollHorizontally="true" 
        />
    </RelativeLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay"
    />
</LinearLayout>
           

nice,現在DecorView也執行個體化了,設定的布局檔案也加載進mContentParent了,接下來應該就是繪制顯示出來了吧。

沒錯,接下來就是ActivityThread調用handleResumeActivity方法了:

handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
		final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
		
			r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
			l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
			
			//windowmanager關聯decorview
			wm.addView(decor, l);
	
	}
           
performResumeActivity(IBinder token, boolean finalStateRequest,
            String reason) {
			//這裡就會去調用我們熟悉的onResume生命周期方法
			 r.activity.performResume(r.startsNotResumed, reason);
	}	
           

這段代碼中,最關鍵的就是wm.addView這個方法了,我們可以聯想到以前在開發時,實作一個懸浮窗的功能,就是調用的這個方法。

從方法名可以看出,這是把decorview添加到windowmanager上去,那我們去看看實作的方法:

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
           

之前說過,WindowManagerImpl裡面的大部分功能時交由WindowManagerGlobal實作的,跟進去:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
			
		ViewRootImpl root;
        View panelParentView = null;	
	
		root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
		
		root.setView(view, wparams, panelParentView, userId);
	
	}

           

這裡又出現了一個ViewRootImpl,并且這裡把要顯示的View、ViewRootImpl還有對應的LayoutParams按照統一的順序存儲了起來。

下面我們看一下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}.
 *	
 */  
           

從注釋可以看出ViewRootImpl實作了WindowManagerGlobal裡面的大部分功能。

ViewRootImpl是視圖層級的頂部,但它不同于DecorView。DecorView是View樹的根,而它是ViewRootImpl的一個成員變量,并且通過ViewRootImpl把自己加載到顯示服務上去(ViewRootImpl不是嚴格意義上的View,隻是實作了ViewParent接口,更像是一個橋梁)

看它的setView方法:

/**
     * We have one child 從注釋可以看出,一個ViewRootImpl隻有一個子view
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
			
			mView = view;
			
			mAttachInfo.mRootView = view;
			
            requestLayout();
					
			//這裡就是真正把window添加到windowManagerService服務裡面去
		    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
	}
	
	public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
			
			//檢查是否是在主線程繪制
            checkThread();
            mLayoutRequested = true;
			
			//異步執行測量,布局,繪制的操作
            scheduleTraversals();
        }
    } 
           

這裡的變量mWindow類型是内部類W,它是一個binder接口的實作類,并且包含了ViewRootImpl,如下:

static class W extends IWindow.Stub {
        private final WeakReference<ViewRootImpl> mViewAncestor;
        private final IWindowSession mWindowSession;

        W(ViewRootImpl viewAncestor) {
            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
            mWindowSession = viewAncestor.mWindowSession;
        }
           

裡的scheduleTraversals方法裡面,去調用了View(這裡是DecorView)的measure、layout、draw方法。也就是說View的繪制流程其實是在ViewRootImpl中被調用的。

最後可以看到,是mWindowSession把這次建立的根View(DecorView)添加到WindowManagerService中去的。mWindowSession的實作類型是Session,繼承自IWindowSession,是一個Binder對象,是和WindowManagerService通信的接口,addToDisplayAsUser是一次IPC的過程。

自此,一個Activity的視圖便添加到了系統服務中去了,我們的代碼就先分析到這裡了。

總結:

Activity與Window的關系:

Activity包含一個Window,這個Window實際是PhoneWindow。PhoneWindow其實是對View的進一步封裝,内部負責與WindowManager的互動并且顯示。而Activity除了封裝了顯示的功能之外,它更大的意義是作為Android的四大元件之一,為開發者的開發提供更加準确便捷的API,比如它提供生命周期的回調、提供設定各種主題的接口、便捷地設定内容、豐富的啟動模式等等。

Window與View的關系:

Window是對View的進一步封裝。View可以說隻是負責單純地顯示自己的View樹,而Window還制定了一系列UI的顯示規則,如背景色,标題欄顯示,沉浸式等等。Android對視窗的管理是按照Window為機關的,除了直接使用WindowManager.addView的方式。這也是為什麼顯示系統服務叫WindowManagerService,而不是叫ViewManagerService。Window就像是一本書的一頁紙,而View就像是紙上附的一些畫、寫的一些字,我們隻能是一頁一頁地翻書,然後看書裡面的内容。

View與ViewRootImpl的關系:

ViewRootImpl負責調用View的繪制流程,并且它負責把View添加到WindowManagerService中去。一個視窗即一個Window需要顯示出來,是需要ViewRootImpl把這個Window的DecorView添加到WindowManagerService中去的。

DecorView與一般的View有什麼差別:

一個Window上的View是以樹的形式存在的,DecorView就是這棵View樹的樹根。

ViewRootImpl個數:

一個視窗會對應一個ViewRootImpl,這裡的視窗包含Activity、Dialog、Toast、還有懸浮窗。比如一個程式中打開了兩個activity,那麼就會有兩個PhoneWindow,也就是兩個ViewRootImpl。

繼續閱讀