1. 程式人生 > >Android AMS(四) Activity的視窗物件(Window)的建立過程分析

Android AMS(四) Activity的視窗物件(Window)的建立過程分析

Android AMS(二) App啟動過程之onCreate中講到,在activity到onCreate狀態前,會呼叫Activity.java-->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,
            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);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();
 
        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }
 
        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的應用程式視窗,並且儲存在Activity類的成員變數mWindow中。有了這個型別為PhoneWindow的應用程式視窗,函式接下來還會呼叫它的成員函式setWindowControllerCallback、setCallback、setSoftInputMode和setWindowManager來設定視窗回撥介面、軟鍵盤輸入區域的顯示模式和本地視窗管理器。

new PhoneWindow

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ......
    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) {
            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;
    }    
    ......
}

呼叫LayoutInflater的靜態成員函式from建立一個LayoutInflater例項,並且儲存在成員變數mLayoutInflater中,這樣,PhoneWindow類以後就可以通過成員變數mLayoutInflater來建立應用程式視窗的檢視

Window.setCallback

public abstract class Window {
    ......
 
    private Callback mCallback;
    ......
 
    /**
     * 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;
    }
  
    ......
}

我們看下這個Callback介面

 public interface Callback {

        public boolean dispatchKeyEvent(KeyEvent event);


        public boolean dispatchKeyShortcutEvent(KeyEvent event);


        public boolean dispatchTouchEvent(MotionEvent event);


        public boolean dispatchTrackballEvent(MotionEvent event);


        public boolean dispatchGenericMotionEvent(MotionEvent event);

        public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);


        @Nullable
        public View onCreatePanelView(int featureId);


        public boolean onCreatePanelMenu(int featureId, Menu menu);


        public boolean onPreparePanel(int featureId, View view, Menu menu);


        public boolean onMenuOpened(int featureId, Menu menu);


        public boolean onMenuItemSelected(int featureId, MenuItem item);


        public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);


        public void onContentChanged();


        public void onWindowFocusChanged(boolean hasFocus);


        public void onAttachedToWindow();


        public void onDetachedFromWindow();


        public void onPanelClosed(int featureId, Menu menu);


        public boolean onSearchRequested();

        public boolean onSearchRequested(SearchEvent searchEvent);


        @Nullable
        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback);


        @Nullable
        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type);


        public void onActionModeStarted(ActionMode mode);


        public void onActionModeFinished(ActionMode mode);


        default public void onProvideKeyboardShortcuts(
                List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) { };

        default public void onPointerCaptureChanged(boolean hasCapture) { };
    }

是不是看到了熟悉的dispatchTouchEvent dispatchKeyEvent......方法

Activity.java 實現了Window.Callback介面,這樣當這個PhoneWindow物件接收到系統給它分發的IO輸入事件,例如,鍵盤和觸控式螢幕事件,轉發給與它所關聯的Activity元件處理

 

Window.setWindowControllerCallback

這個介面也是Window.java裡,activity實現了WindowControllerCallback

    /** @hide */
    public interface WindowControllerCallback {
        /**
         * Moves the activity from
         * {@link android.app.ActivityManager.StackId#FREEFORM_WORKSPACE_STACK_ID} to
         * {@link android.app.ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} stack.
         */
        void exitFreeformMode() throws RemoteException;

        /**
         * Puts the activity in picture-in-picture mode if the activity supports.
         * @see android.R.attr#supportsPictureInPicture
         */
        void enterPictureInPictureModeIfPossible();

        /** Returns the current stack Id for the window. */
        int getWindowStackId() throws RemoteException;

        /** Returns whether the window belongs to the task root. */
        boolean isTaskRoot();
    }

從方法名兒可以看出,這個介面是為模式切換服務的,Freefor, PictureInPicture

Window.setOnWindowDismissedCallback

視窗消失的回撥視窗

    /** @hide */
    public final void setOnWindowDismissedCallback(OnWindowDismissedCallback dcb) {
        mOnWindowDismissedCallback = dcb;
    }
    /** @hide */
    public interface OnWindowDismissedCallback {
        /**
         * Called when a window is dismissed. This informs the callback that the
         * window is gone, and it should finish itself.
         * @param finishTask True if the task should also be finished.
         * @param suppressWindowTransition True if the resulting exit and enter window transition
         * animations should be suppressed.
         */
        void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition);
    }

Window.setSoftInputMode

public abstract class Window {
    ......
 
    private boolean mHasSoftInputMode = false;
    ......
 
    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);
    }
 
    ......
}

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

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

通過onWindowAttributesChanged通知activity視窗發生了變化

Window.setWindowManager

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
        setWindowManager(wm, appToken, appName, false);
    }

    /**
     * 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本地物件,在前面Android AMS(一) App啟動過程之Task,程序建立流程講到,每一個啟動起來了的Activity元件在ActivityManagerService這一側,都有一個對應的ActivityRecord物件,用來描述該Activity元件的執行狀態。這個Binder代理物件會被儲存在Window類的成員變數mAppToken中,這樣當前正在處理的視窗就可以知道與它所關聯的Activity元件是什麼。

引數appName用來描述當前正在處理的視窗所關聯的Activity元件的名稱,這個名稱會被儲存在Window類的成員變數mAppName中。

最後得到mWindowManager用來維護當前正在處理的應用程式視窗。

至此,我們就分析完成一個Activity元件所關聯的應用程式視窗物件的建立過程了。從分析的過程可以知道:

      1. 一個Activity元件所關聯的應用程式視窗物件的型別為PhoneWindow。

      2. 這個型別為PhoneWindow的應用程式視窗是通過一個型別為WindowManagerImpl的本地視窗管理器來維護的。

      3. 這個型別為PhoneWindow的應用程式視窗內部有一個型別為DecorView的檢視物件,這個檢視物件才是真正用來描述一個Activity元件的UI的。