1. 程式人生 > >Android Java層UI渲染實現 二 Window的建立

Android Java層UI渲染實現 二 Window的建立

在上一篇中我們看了Context的建立過程,我們知道,每一個Activity都有一個Context,同時它還有一個Window物件,用來描述一個具體的應用程式視窗。
現在我們就來分析分析它的建立

還記得在Activity初始化的時候,呼叫attach方法的時候,會建立一個PhoneWindow物件,我們就從這裡開始分析。

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)
{ attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.
getLayoutInflater().setPrivateFactory(this); ... ... 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()); } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; mWindow.setColorMode(info.colorMode); }

PhoneWindow繼承自Window:

public class PhoneWindow extends Window implements MenuBuilder.Callback

Window是一個抽象類,public abstract class Window,可以看看它的介紹:

Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.

它是一個頂層視窗look和behavior的抽象類,它的例項應該用於那些被添加了windowManager的頂層檢視。它提供了標準UI策略像background,title area ,預設金鑰處理等。

根據這個介紹,我們這個知道的是,它跟View有關。

然後我們看看它的建構函式:


 // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
	private LayoutInflater mLayoutInflater;
	
	 // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;


 public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }
    /**
     * Constructor for main window of an activity.
     */
    public PhoneWindow(Context context, Window preservedWindow,
            ActivityConfigCallback activityConfigCallback) {
        this(context);
        // Only main activity windows use decor context, all the other windows depend on whatever
        // context that was given to them.
        mUseDecorContext = true;
        if (preservedWindow != null) {//隱藏Widow。preservedWindow一般來說是null
            mDecor = (DecorView) preservedWindow.getDecorView();
            mElevation = preservedWindow.getElevation();
            mLoadElevation = false;
            mForceDecorInstall = true;
            // If we're preserving window, carry over the app token from the preserved
            // window, as we'll be skipping the addView in handleResumeActivity(), and
            // the token will not be updated as for a new window.
            getAttributes().token = preservedWindow.getAttributes().token;
        }
        // Even though the device doesn't support picture-in-picture mode,
        // an user can force using it through developer options.
        boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
                DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
        mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_PICTURE_IN_PICTURE);
        mActivityConfigCallback = activityConfigCallback;
    }

其實可以看到,在PhoneWindow的構造中,先是呼叫了父類的構造方法:

public Window(Context context) {
        mContext = context;
        mFeatures = mLocalFeatures = getDefaultFeatures(context);
    }

然後呼叫LayoutInflater.from方法建立一個LayoutInflate物件。這樣PhoneWindow就可以通過mLayoutInflate來建立應用程式視窗的檢視,這個檢視用型別用DecorVIew的成員變數mDecorView來描述。
PhoneWindow類中還有一個型別為ViewGroup的成員變數mContentParent,用來描述一個檢視容器,這個容器存放的就是mDecor所描述的檢視內容,不過這個容器也可能指向mDecor本身。

在attach方法中,我們知道,在PhoneWindow建立完後,就會呼叫mWindow.setCallback(this);


//Winodw.java
 /**
     * Set the Callback interface for this window, used to intercept key
     * events and other dynamic operations in the window.
     *
     * @param callback The desired Callback interface.
     */
    public void setCallback(Callback callback) {
        mCallback = callback;
    }

正在啟動的Activity元件會他創所實現的Callback介面設定到與它所關聯的PhoneWindow物件中,這樣當這個PhoneWindow物件接收到系統給它分發的IO輸入事件,例如,鍵盤和觸控事件,就可以轉發給它所關聯的Activity中去處理。

繼續看,在attach方法中,一般情況下,mWinodw會設定:mWindow.setSoftInputMode(info.softInputMode);

//Window.java
  /**
     * Specify an explicit soft input mode to use for the window, as per
     * {@link WindowManager.LayoutParams#softInputMode
     * WindowManager.LayoutParams.softInputMode}.  Providing anything besides
     * "unspecified" here will override the input mode the window would
     * normally retrieve from its theme.
     */
    public void setSoftInputMode(int mode) {
        final WindowManager.LayoutParams attrs = getAttributes();
        if (mode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            attrs.softInputMode = mode;
            mHasSoftInputMode = true;
        } else {
            mHasSoftInputMode = false;
        }
        dispatchWindowAttributesChanged(attrs);
    }

protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
        if (mCallback != null) {
            mCallback.onWindowAttributesChanged(attrs);
        }
    }

這個mode有下面六個取值:

  • SOFT_INPUT_STATE_UNSPECIFIED:沒有指定軟鍵盤輸入區域的顯示狀態。
  • SOFT_INPUT_STATE_UNCHANGED:不要改變軟鍵盤輸入區域的顯示狀態
  • SOFT_INPUT_STATE_HIDDEN:在合適的時候隱藏軟鍵盤輸入區域;例如:當用戶導航到視窗時
  • SOFT_INPUT_STATE_ALWAYS_HIDDEN:當視窗獲得焦點時,總是隱藏軟鍵盤輸入區域。
  • SOFT_INPUT_STATE_VISIBLE:在合適的時候顯示軟鍵盤輸入區域,例如,當用戶導航到當前視窗時。
  • SOFT_INPUT_STATE_ALWAYS_VISIBLE:當視窗獲得焦點時,總是顯示軟鍵盤輸入區域。

當引數mode的值不等於SOFT_INPUT_STATE_UNSPECIFIED時,就表示當前視窗被指定軟鍵盤輸入區域的顯示模式,這時候Window類的成員函式setSoftInputMode就會將成員變數mHasSoftInputMode的值設定為true,並且將這個顯示模式儲存在用來描述窗口布局屬性的一個WindowManager.LayoutParams物件的成員變數softInputMode中,否則的話,就會將成員變數mHasSoftInputMode的值設定為false。

設定完成視窗的軟鍵盤輸入區域的顯示模式之後,如果Window的成員變數mCallback指向了一個視窗回撥視窗,那麼就會呼叫mCallback.onWindowAttributesChanged(attrs);來通知與視窗所關聯的Activity元件,它的視窗屬性發生了變化。

繼續看attach方法,後面會呼叫mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);來設定應用程式視窗的本地視窗管理器:

//Window.java
/**
     * Set the window manager for use by this Window to, for example,
     * display panels.  This is <em>not</em> used for displaying the
     * Window itself -- that must be done by the client.
     *
     * @param wm The window manager for adding new windows.
     */
    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);
    }

引數appToken用來描述正在處理的視窗與哪一個Activity元件關聯,它是一個Binder代理物件,引用在ActivityManagerService這裡所建立的一個型別為ActivityRecord的Binder本地物件。在Activity啟動的過程中,每一個Activity元件在ActivityManagerSerivce這裡,都有一個對應的ActivityRecord物件,用來描述Activity元件的執行狀態。這個Binder代理物件會儲存在mAPPToken中,這樣當前正在處理的視窗就可以知道與它關聯的Activity是什麼。
引數appName用來描述當前正在處理的視窗所關聯的Activity的名稱。
引數wm用來描述一個視窗管理器,如果它等於null,就通過wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);得到一個預設的視窗管理器,然後再通過它視窗一個本地視窗管理器,用來維護當前正在處理的應用程式視窗。

//Context.java
@SuppressWarnings("unchecked")
    public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) {
        // Because subclasses may override getSystemService(String) we cannot
        // perform a lookup by class alone.  We must first map the class to its
        // service name then invoke the string-based method.
        String serviceName = getSystemServiceName(serviceClass);
        return serviceName != null ? (T)getSystemService(serviceName) : null;
    }
   
   
//ContextWrapper.java

@Override
    public String getSystemServiceName(Class<?> serviceClass) {
        return mBase.getSystemServiceName(serviceClass);
    }
//ContextImpl.java
 @Override
    public String getSystemServiceName(Class<?> serviceClass) {
        return SystemServiceRegistry.getSystemServiceName(serviceClass);
    }

//SystemServiceRegistry.java
 private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
            new HashMap<Class<?>, String>();
public static String getSystemServiceName(Class<?> serviceClass) {
        return SYSTEM_SERVICE_NAMES.get(serviceClass);
    }

//ContextWrapper.java
 @Override
    public Object getSystemService(String name) {
        return mBase.getSystemService(name);
    }

//ContextImpl.java
 @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

//SystemServiceRegistry.java
 /**
     * Gets a system service from a given context.
     */
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }


其實我們可以看到,得到ServiceManager都是通過mBase來完成的,這個mBase是在建立Activity之前,建立的ContextImpl物件,然後在ContextImpl中通過SystemServiceRegistry去得到ServiceManager。

回到setWindowManger方法中,在建立本地WindowManager之前,會將得到的wm強制型別轉換為WindowManagerImplWindowManagerImpl實現了WindowManager介面,因此它可以用來管理應用程式視窗

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;//其實就是所關聯的Activity中的ContextImpl
    private final Window mParentWindow;//其實就是所關聯的Activity中的PhoneWindow
    private IBinder mDefaultToken;
    public WindowManagerImpl(Context context) {
        this(context, null);
    }
    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }
... ... 
}

接下來,呼叫mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);建立本地應用程式視窗:

 public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

在WindowMangerImpl中有一個方法:

 @Override
    public Display getDefaultDisplay() {
        return mContext.getDisplay();
    }

之前我們有提到mContext其實就是所關聯的Activity的ContextImpl:

 @Override
    public Display getDisplay() {
        if (mDisplay == null) {
            return mResourcesManager.getAdjustedDisplay(Display.DEFAULT_DISPLAY,
                    mResources);
        }
        return mDisplay;
    }

Display就是用來描述系統螢幕屬性的,可以通過這個獨享,對正在處理的視窗配置一些可定義的螢幕屬性。

這樣,Window的建立就分析完了,我們可以總結下面幾點

  • 一個AActivity所關聯的應用程式視窗物件的型別是PhoneWinodw。
  • 這個型別為PhoneWindow的應用程式視窗是通過一個型別為WindowManagerImpl的本地視窗管理器來維護的。
  • 這個型別為WindowManagerImpl的本地視窗管理器是通過Context.getSystemManager()建立的WindowManagerImpl來維護的。
  • 這個型別為PhoneWindow的應用程式視窗內部有一個型別為DecorView的檢視物件,這個檢視物件才是真正用來描述一個Activity元件的UI的。

PhoneWindow和Window的關係

在這裡插入圖片描述

Activity和Window的關係

在這裡插入圖片描述

參考:
https://www.kancloud.cn/alex_wsc/androids/473771