1. 程式人生 > >Android視窗機制(三)Window和WindowManager的建立與Activity

Android視窗機制(三)Window和WindowManager的建立與Activity

Android視窗機制系列

Android視窗機制(一)初識Android的視窗結構
Android視窗機制(二)Window,PhoneWindow,DecorView,setContentView原始碼理解
Android視窗機制(三)Window和WindowManager的建立與Activity
Android視窗機制(四)ViewRootImpl與View和WindowManager
Android視窗機制(五)最終章:WindowManager.LayoutParams和Token以及其他視窗Dialog,Toast

前兩篇文章跟大家介紹了Window,PhoneWindow,DecorView他們間的聯絡,以及他們之間的理解。講到Window大家肯定會想到常見的WindowManager,兩者肯定是發生過關係的。此外對於Window和WindowManager的建立問題,正是下面要介紹的。

瞭解他們前,我們先來看個結構

Paste_Image.png

ViewManager

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

ViewManager介面定義了一組規則,也就是add、update、remove的操作View介面。也就是說ViewManager是用來新增和移除activity中View的介面,可以通過Context.getSystemService()獲取例項。
我們看下ViewManager的實現類

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    private static final String TAG = "ViewGroup";
    ...
    public void addView(View child, LayoutParams params) {
        addView(child, -1, params);
    }
    
     /*
     * @param child the child view to add
     * @param index the position at which to add the child or -1 to add last
     * @param params the layout parameters to set on the child
     */
    public void addView(View child, int index, LayoutParams params) {
        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }
    ...

可以看到ViewGroup裡面實現了ViewManager介面,View通過ViewGroup的addView方法新增到ViewGroup中,而ViewGroup層層巢狀到最頂級都會顯示在在一個視窗Window中

WindowManager

我們還是看下原始碼說明

/*   The interface that apps use to talk to the window manager.
Use Context.getSystemService(Context.WINDOW_SERVICE) to get one of these.
*/
public interface WindowManager extends ViewManager {
    public static class BadTokenException extends RuntimeException{...}
    public static class InvalidDisplayException extends RuntimeException{...}
    public Display getDefaultDisplay();
    public void removeViewImmediate(View view);
    public static class LayoutParams extends ViewGroup.LayoutParams
        implements Parcelable

可以看到WindowManager是一個介面,而且它繼承與ViewManager。WindowManager字面理解就是視窗管理器,每一個視窗管理器都與一個的視窗顯示繫結。獲取例項可以通過
Context.getSystemService(Context.WINDOW_SERVICE)獲取。既然繼承了ViewManager,那麼它也就可以進行新增刪除View的操作了,不過它的操作放在它的實現類WindowManagerImpl裡面。成員變數裡面

  • BadTokenException:則是addView時它的LayoutParams無效則會被丟擲,或是新增第二個View的時候沒有移除第一個View則會被丟擲
  • InvalidDisplayException:如果一個視窗是在一個二級的顯示上而指定的顯示找不到則會被丟擲
  • getDefaultDisplay:返回當前WindowManager管理的顯示Display
  • removeViewImmediate:表示從視窗上移除View,一般是當View呼叫了onDetachedFromWindow也就是從Window上分開後,把它移除。
  • LayoutParams:靜態內部類。顯然是Window的佈局引數,裡面定義了一系列的視窗屬性。

WindowManagerImpl

WindowManager的實現類

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;
     @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    ...
     @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
}

可以看到WindowManagerImpl裡面有一個成員變數WindowManagerGlobal,而真正的實現則是在WindowManagerGlobal了,類似代理,只不過WindowManagerGlobal是個沒有實現WindowManager的類的,自己定義了套實現。

public final class WindowManagerGlobal {
    private static final String TAG = "WindowManager";
     public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
            ...
     }
}

聯絡

大概瞭解了上述類的分類和各自的作用,那麼他們之間如何聯絡,Window如何建立如何與WindowManager繫結與Activity繫結呢,這個時候就需要一個場景來逐一理解。我們都知道每一個Activity都是與一個Window繫結一起的,那麼Window的建立以及WindowManager的繫結會不會在建立啟動Activity的過程中就繫結的呢。

對於Activity的啟動過程,是有兩種,一種是點選程式進入啟動的Activity,另一種而是在已有的Activity中呼叫startActivity,啟動期間通過Binder驅動ActivityWindowService,ActivityThread,ApplicationThread,ActivityStack ,Activity之間進行通訊,為當前Activity建立程序分配任務棧後啟動Activity。這裡就跳過前面很多步驟,直接到了ActivityThread.handleLaunchActivity去檢視Activity的建立

 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       ...

        // Initialize before creating the activity
        WindowManagerGlobal.initialize();

        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
            ...
    }

可以看到 WindowManagerGlobal.initialize()則通過WindowManagerGlobal建立了WindowManagerServer,接下來呼叫了performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        Activity activity = null;
        try { //Activity通過ClassLoader創建出來
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);  
        } ...
        try {
            //建立Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            ...
            if (activity != null) {
                //建立Activity所需的Context
                Context appContext = createBaseContextForActivity(r, activity);
                ...
                //將Context與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);
                ...
                    //呼叫activity.oncreate
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                ...
                     //呼叫Activity的onstart方法
                     activity.performStart();
                           //呼叫activitu的OnRestoreInstanceState方法進行Window資料恢復 
             mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                    r.persistentState);
                ...
        return activity;
    }

先通過呼叫 activity = mInstrumentation.newActivity建立Activity,可以看到裡面是通過ClassLoader來載入的

public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }

接著建立Activity所需的Application和Context,再呼叫到activity.attach

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) {
        //ContextImpl的繫結
        attachBaseContext(context);
        //在當前Activity建立Window
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ...
        //為Window設定WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        //建立完後通過getWindowManager就可以得到WindowManager例項
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }

可以看到在Activity建立到attach的時候,對應的Window視窗也被建立起來,而且Window也與WindowManager繫結。而mWindow,和mWindowManager則是Activity的成員變數。可以看到這裡WindiwManager的建立是context.getSystemService(Context.WINDOW_SERVICE)

  public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        ...
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

接著建立WindowManager的實現類,我們平時在Activity中使用getWindow()和getWindowManager,就是返回對應這兩個成員變數。

回到前面那個方法,呼叫了activity.attach後建立了Window和WindowManager,之後呼叫了

mInstrumentation.callActivityOnCreate(activity, r.state ...);

該方法則是呼叫activity.oncreate方法的

   public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        activity.performCreate(icicle);
        postPerformCreate(activity);
    }
 final void performCreate(Bundle icicle) {
        onCreate(icicle);
        mActivityTransitionState.readState(icicle);
        performCreateCommon();
    }

之後直接呼叫了

  activity.performStart();

來呼叫activity.onstart()方法
同樣之後也呼叫了

mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state...);

看到onRestoreInstanceState是不是很熟悉,沒錯就是Activity資料恢復呼叫的方法

public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
        activity.performRestoreInstanceState(savedInstanceState);
    }
 final void performRestoreInstanceState(Bundle savedInstanceState) {
        onRestoreInstanceState(savedInstanceState);
        restoreManagedDialogs(savedInstanceState);
    }

裡面通過Bundle來儲存恢復Window視窗資訊
performLaunchActivity呼叫完後回到handleLaunchActivity

 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       ...

        //初始化WindowManagerGlobal,為之後addView準備
        WindowManagerGlobal.initialize();

        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
            ...
    }

呼叫了performLauncherActiviy來建立Activity以及Activity所需要的Context,Window,呼叫了Activity的onCreate,onStart方法,而接下來呼叫了handleResumeActivity方法

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {

        //呼叫activity.onResume,把activity資料記錄更新到ActivityClientRecord
        ActivityClientRecord r = performResumeActivity(token, clearHide);
 
        if (r != null) {
            final Activity a = r.activity;
            //activity.mStartedActivity是用來標記啟動Activity,有沒有帶返回值,一般我們startActivity(intent)是否預設是startActivityForResult(intent,-1),預設值是-1,所以這裡mStartedActivity = false
            boolean willBeVisible = !a.mStartedActivity;
            ...
            //mFinished標記Activity有沒有結束,而r.window一開始activity並未賦值給ActivityClientRecord,所以這裡為null
            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();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //把當前的DecorView與WindowManager繫結一起
                    wm.addView(decor, l);
                }

            ...
            if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
             //標記當前的Activity有沒有設定新的配置引數,比如現在手機是橫屏的,而之後你轉成豎屏,那麼這裡的newCofig就會被賦值,表示引數改變
                if (r.newConfig != null) {
                    r.tmpConfig.setTo(r.newConfig);
                    if (r.overrideConfig != null) {
                        r.tmpConfig.updateFrom(r.overrideConfig);
                    }
                    //然後呼叫這個方法回撥,表示螢幕引數發生了改變
                    performConfigurationChanged(r.activity, r.tmpConfig);
                ...
                WindowManager.LayoutParams l = r.window.getAttributes();
                ...//改變之後update更新當前視窗的DecorView
                    if (r.activity.mVisibleFromClient) {
                        ViewManager wm = a.getWindowManager();
                        View decor = r.window.getDecorView();
                        wm.updateViewLayout(decor, l);
                    }
                }
                //引數沒改變
                r.activity.mVisibleFromServer = true;
                mNumVisibleActivities++;
                if (r.activity.mVisibleFromClient) {
                //由於前面設定了INVASIBLE,所以現在要把DecorView顯示出來了
                    r.activity.makeVisible();
                }
            }

           //通知ActivityManagerService,Activity完成Resumed
             ActivityManagerNative.getDefault().activityResumed(token);
           
    }

handleResumeActivity方法一開始就呼叫了activity = performResumeActivity()方法

  public final ActivityClientRecord performResumeActivity(IBinder token,
            boolean clearHide) {
        ActivityClientRecord r = mActivities.get(token);
      ...
                r.activity.mStartedActivity = false;
                r.activity.onStateNotSaved();
                r.activity.mFragments.noteStateNotSaved();
       ...    //Activity呼叫onResume,就不再貼出來了,裡面還有判斷要不呀奧onReStart,這個想必知道Activity生命週期的人就秒懂了
                r.activity.performResume();
       ...
                r.paused = false;
                r.stopped = false;
                r.state = null;
                r.persistentState = null;
         
        return r;
    }

performResumeActivity則是讓Activity呼叫onResume方法,同時把Activity的資訊記錄在ActivityClientRecord
之後進入到這裡這個判斷方法。前面程式碼註釋也有說到一些,這裡再說詳細一點。

boolean willBeVisible = !a.mStartedActivity;
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();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //把當前的DecorView與WindowManager繫結一起
                    wm.addView(decor, l);
                }

r.window一開始null的,activity並沒有把它的window賦值給它
a.finished表示Activity是否結束
activity.mStartedActivity是用來標記啟動Activity需不需要帶返回值,一般我們startActivity(intent)是否預設是startActivityForResult(intent,-1),預設值是-1,所以這裡mStartedActivity = false

if (requestCode >= 0) {
                mStartedActivity = true;
            }

接著獲取當前Activity的Window進而獲取到DecorView,再獲取當前Activity的WindowManager,將DecorView與WindowManager繫結一起。
注意:
這裡的DecorView是setContentView之後的DecorView,也就是裝載我們的佈局內容的。前面講到在handleLaucheActivity中,它會先呼叫performLaunchActivity,再呼叫handleResumeActivity方法,而在performLaunchActivity方法中先建立Activity物件,接著呼叫activity.attach方法,來繫結Context,同時在attach中建立了PhoneWindow以及WindowManager,attach之後,就呼叫了activity.oncreate方法,要知道,我們的setContentView是放在onCreate方法中的。有看過上一篇setContentView的原始碼這裡應該就會懂。DecorView是PhoneWindow的成員變數,所以setContentView可以說是將DecorView建立新增到Window上面的,呼叫setContentView後已經是把你的佈局檔案新增到DecorView了。
回到前面,因為是onCreate之後的,所以這裡呼叫

 View decor = r.window.getDecorView();

便可以得到當前Activity.Window下的DecorView,接下來通過建立好的WindowManager將DecorView與它繫結到一起.

之後到了newConfig引數這裡,前面註釋已經解釋很清楚

  if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
             //標記當前的Activity有沒有設定新的配置引數,比如現在手機是橫屏的,而之後你轉成豎屏,那麼這裡的newCofig就會被賦值,表示引數改變
                if (r.newConfig != null) {
                    r.tmpConfig.setTo(r.newConfig);
                    if (r.overrideConfig != null) {
                        r.tmpConfig.updateFrom(r.overrideConfig);
                    }
                    //然後呼叫這個方法回撥,表示螢幕引數發生了改變
                    performConfigurationChanged(r.activity, r.tmpConfig);
                ...
                WindowManager.LayoutParams l = r.window.getAttributes();
                ...//改變之後update更新當前視窗的DecorView
                    if (r.activity.mVisibleFromClient) {
                        ViewManager wm = a.getWindowManager();
                        View decor = r.window.getDecorView();
                        wm.updateViewLayout(decor, l);
                    }
                }

這裡我們看下performConfigurationChanged,可以先大膽猜測下這個方法肯定是來通知Activity引數改變的一個方法

private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) {
      
        Activity activity = (cb instanceof Activity) ? (Activity) cb : null;
        if (shouldChangeConfig) {
            cb.onConfigurationChanged(config);
             ...
        }
    }

果然,這裡呼叫到了一個介面回撥,注意ComponentCallbacks2這個引數,看它傳進來的引數,是r.activity,也就是說Activity裡面應該是實現了這個介面,接著通過回撥去通知引數更改

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {...}

果然如此。
之後來到了最後一步,handleResumeActivity方法裡的最後

 if (r.activity.mVisibleFromClient) {
                //由於前面設定了INVASIBLE,所以現在要把DecorView顯示出來了
                    r.activity.makeVisible();
                }

要知道,前面我們的DecorView可是設定了invisible(不知道是不是為了防止更新閃爍的問題),之後可能是要把它設定回來,就是在makevisible方法中

   void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

可以看到如果當前DecorView還未新增到WindwManager的話,則重新新增,最後設定為VISIBLE。
而我們平常在activity中使用setVisibility,也就是在設定DecorView是VISIBLE還是INVASIBLE

  public void setVisible(boolean visible) {
        if (mVisibleFromClient != visible) {
            mVisibleFromClient = visible;
            if (mVisibleFromServer) {
                if (visible) makeVisible();
                else mDecor.setVisibility(View.INVISIBLE);
            }
        }
    }

至此,Activity被啟動起來,檢視(DecorView)也被建立(Window)管理(WindowManager)起來了。

小結

  • 一個流程圖總結上面流程

Paste_Image.png

  • ViewManager介面定義了一組規則,也就是add、update、remove的操作View介面。ViewGroup實現了該介面
  • WindowManager的實現類是WindowManagerImpl,而它則是通過WindowManagerGlobal代理實現。WindowManager用來在應用與Window之間的介面、視窗順序、訊息等的管理

小感言

在看Window和WindowManager的時候,一直不知道要如何根據原始碼去學這兩個類。結合以前自己學習的情況,覺得還是得根據裡面的使用例子,再結合原始碼起來學,才是最有效的。想到Activity啟動會與Window有關,就著手去找,找著找著就越有思路講。這也算是閱讀原始碼的方法之一吧。

上面把DecorView新增到WindowManager,呼叫到的是WindowManagerGlobal.addView方法,而
該方法中真正把View傳遞給WindowManager的是通過ViewRoot的setView()方法,ViewRoot實現了View和WindowManager之間的訊息傳遞。下篇文章將介紹ViewRoot與View和WindowManager之間的聯絡。

上面有什麼口誤或者思路不正確的話還麻煩各位讀者糾正。

Android視窗機制(四)ViewRootImpl與View和WindowManager:http://www.jianshu.com/p/9da7bfe18374



作者:Hohohong
連結:https://www.jianshu.com/p/6afb0c17df43
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授