1. 程式人生 > >Android的Window工作機制

Android的Window工作機制

在Android中Window表示一個視窗,我們日常使用的activity就會繫結一個window例項,用來管理activity中的相關檢視。window是一個抽象類,它的具體實現類是PhoneWindow。每一個檢視view的呈現都需要通過window來實現,所以說其實view才是window的實體存在。這樣每一個window都會對應著一個view和一個ViewRootImpl,ViewRootImpl是用來幫助window和view之間建立聯絡的。

view在window上的新增、刪除、更新操作需要通過WindowManager來實現,WindowManager是外界訪問window的入口,然後對window的操作又最終會在windowmanagerservice中實現,因為windowmanagerservice在Android中處於獨立的程序中,所以WindowManager和windowmanagerservice的互動是一個IPC過程。下面我們主要來分析一下window的新增過程。

Window的新增過程

window的新增過程需要通過WindowManager的addView來實現,WindowManager是一個介面,它的真正實現是WindowManagerImpl,我們來看它們的介面實現:

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

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }


    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

可以看到windowmanagerImpl並沒有真正實現window的三大操作,而是交給了WindowManagerGlobal來實現。下面來看看它的addView方法原始碼:


    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        //檢查引數的合法性
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
       
        ****************省略程式碼*********************

            //建立一個ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            //儲存相關引數
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                //利用ViewRootImpl的setView方法來完成view的繪製
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

在上面當中,mViews儲存的是所有window所對應的view,mRoots儲存是所有Window所對應的Viewrootimpl,mParams儲存的所有window所對應的佈局引數,而mDyingViews則儲存那些正在被刪除的view物件。後面還是通過viewrootimpl的setView來完成view的繪製過程,到這裡已經把新增view的任務轉移到viewrootimpl去了,我們繼續來看看:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                ...
                // Schedule the first layout -before- adding to the window  
                // manager, to make sure we do the relayout before receiving  
                // any other events from the system.
                requestLayout();
                ...
                try {
                ...
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } 
    }

在setView方法中,首先通過requestLayout()來完成view的非同步重新整理,接著會通過WindowSession來最終來完成window的新增任務。我們先來看看requestLayout方法

  @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //首先判斷執行緒
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

  void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

可以看到,首先會判斷執行緒是否正確,這個就是我們平時在子執行緒中更新UI會丟擲的異常。然後繼續scheduleTraversals()的執行:

  void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

在這裡會通過非同步去呼叫mTraversalRunnable介面

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

接著doTraversal():

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

後面的performTraversals這個方法很長,它是真正呼叫繪製的一個方法

private void performTraversals() {  
        ......  
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ......  
        performDraw();
        }
        ......  
    }

到這裡我們就大概清楚在window新增之前的一個view的繪製流程了,在上面的setView方法中我們知道,在完成view繪製以後會通過windowsession最終來完成window的新增,mWindowSession的一個Binder物件,真正的實現是Session,這也是window新增過程中的一個IPC呼叫。

   @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

可以看到,session內部會通過windowmanagerservice來實現window的新增,如此一來,window的新增請求就交給了windowmanagerservice了,具體它在裡面是怎麼新增我們就不深入分析了。

通過上面的分析我們知道了android的window機制是怎麼一回事了,而且我們詳細分析了window的新增過程,至於它的刪除和更新過程由於篇幅有限就不作分析了。我們知道,android中提供檢視的有Activity、Dialog、Toast等,有檢視的地方就有window,下面我們就來分析一下window在activity中的建立過程。

Activity的Window建立過程

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後,開始呼叫performLaunchActivity返回activity例項物件


    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {           
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //利用類載入器建立activity例項物件
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        ...
        }
        try {
              ...
               appContext.setOuterContext(activity);
                //呼叫attach方法
                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);
                ...
                if (r.isPersistable()) {
                    //呼叫oncreate方法
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    //呼叫oncreate方法
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                .....
                if (!r.activity.mFinished) {
                    //呼叫onstart方法
                    activity.performStart();
                    r.stopped = false;
                }

          //呼叫 activity的 OnRestoreInstanceState方法進行資料恢復                        
          mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                    r.persistentState);
        ...
        return activity;
    }

首先通過ClassLoader來建立一個activity物件,後面我們可以看到依次呼叫activity中我們熟知的生命週期方法,下面我們主要來看看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,
            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);
    }

在attach方法中,關鍵是建立了window物件並設定了相關回調介面,還設定了WindowManager,我們在activity中通過getwindow和getwindowmanager返回的例項就是在這裡建立的了。到這裡window已經建立完成,我們知道activity的檢視需要在onCreate方法中通過setContentView來提供,那我們通過原始碼來看看activity的檢視是怎麼新增到window中去的。

public void setContentView(View v) {
    //確保decor已經建立
    ensureSubDecor();
    // 獲取放置內容的ViewGroup
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); 
    contentParent.removeAllViews();
    // 將我們定義的那個內容view新增上去
    contentParent.addView(v);
    //通知activity檢視已經發生改變
    mOriginalWindowCallback.onContentChanged();
}

我們來看看建立decor的方法ensureSubDecor原始碼:

private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        mSubDecor = createSubDecor();

*******省略程式碼*******

    }
}

可以看到,如果decor沒有初始化,就呼叫createSubDecor建立

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

    if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
        a.recycle();
        throw new IllegalStateException(
                "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
    }

    if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
        requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
    }
    mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
    a.recycle();

    // Now let's make sure that the Window has installed its decor by retrieving it
    mWindow.getDecorView();

    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;


    if (!mWindowNoTitle) {
        if (mIsFloating) {
            // If we're floating, inflate the dialog title decor
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_dialog_title_material, null);

            // Floating windows can never have an action bar, reset the flags
            mHasActionBar = mOverlayActionBar = false;
        } else if (mHasActionBar) {
            /**
             * This needs some explanation. As we can not use the android:theme attribute
             * pre-L, we emulate it by manually creating a LayoutInflater using a
             * ContextThemeWrapper pointing to actionBarTheme.
             */
            TypedValue outValue = new TypedValue();
            mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

            Context themedContext;
            if (outValue.resourceId != 0) {
                themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
            } else {
                themedContext = mContext;
            }

            // Now inflate the view using the themed context and set it as the content view
            subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                    .inflate(R.layout.abc_screen_toolbar, null);

            mDecorContentParent = (DecorContentParent) subDecor
                    .findViewById(R.id.decor_content_parent);
            mDecorContentParent.setWindowCallback(getWindowCallback());

            /**
             * Propagate features to DecorContentParent
             */
            if (mOverlayActionBar) {
                mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
            }
            if (mFeatureProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
            }
            if (mFeatureIndeterminateProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
            }
        }
    } else {
        if (mOverlayActionMode) {
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_screen_simple_overlay_action_mode, null);
        } else {
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        }

        if (Build.VERSION.SDK_INT >= 21) {
            // If we're running on L or above, we can rely on ViewCompat's
            // setOnApplyWindowInsetsListener
            ViewCompat.setOnApplyWindowInsetsListener(subDecor,
                    new OnApplyWindowInsetsListener() {
                        @Override
                        public WindowInsetsCompat onApplyWindowInsets(View v,
                                WindowInsetsCompat insets) {
                            final int top = insets.getSystemWindowInsetTop();
                            final int newTop = updateStatusGuard(top);

                            if (top != newTop) {
                                insets = insets.replaceSystemWindowInsets(
                                        insets.getSystemWindowInsetLeft(),
                                        newTop,
                                        insets.getSystemWindowInsetRight(),
                                        insets.getSystemWindowInsetBottom());
                            }

                            // Now apply the insets on our view
                            return ViewCompat.onApplyWindowInsets(v, insets);
                        }
                    });
        } else {
            // Else, we need to use our own FitWindowsViewGroup handling
            ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
                    new FitWindowsViewGroup.OnFitSystemWindowsListener() {
                        @Override
                        public void onFitSystemWindows(Rect insets) {
                            insets.top = updateStatusGuard(insets.top);
                        }
                    });
        }
    }

    if (subDecor == null) {
        throw new IllegalArgumentException(
                "AppCompat does not support the current theme features: { "
                        + "windowActionBar: " + mHasActionBar
                        + ", windowActionBarOverlay: "+ mOverlayActionBar
                        + ", android:windowIsFloating: " + mIsFloating
                        + ", windowActionModeOverlay: " + mOverlayActionMode
                        + ", windowNoTitle: " + mWindowNoTitle
                        + " }");
    }

    if (mDecorContentParent == null) {
        mTitleView = (TextView) subDecor.findViewById(R.id.title);
    }

    // Make the decor optionally fit system windows, like the window's decor
    ViewUtils.makeOptionalFitsSystemWindows(subDecor);

    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);

    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        // There might be Views already added to the Window's content view so we need to
        // migrate them to our content view
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }

        // Change our content FrameLayout to use the android.R.id.content id.
        // Useful for fragments.
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);

        // The decorContent may have a foreground drawable set (windowContentOverlay).
        // Remove this as we handle it ourselves
        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }

    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);

    contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
        @Override
        public void onAttachedFromWindow() {}

        @Override
        public void onDetachedFromWindow() {
            dismissPopups();
        }
    });

    return subDecor;
}

上面方法比較長很多,其實是根據不同的activity主題去載入不同的佈局來建立decor。因為佈局的載入設定是一個非同步的過程,從setContentView方法原始碼中我們可以得知,建立好decorview的時候,會回撥activity實現的onContentChanged方法。此時代表可以顯示佈局view給前臺看了,我們來看看這個回撥方法:

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

此時可以看到如果檢視還沒新增到window上的話,就會呼叫WindowManager新增view,並且將decorview設定為可見。到這裡我們就完成了整個activity的檢視載入過程。至於Dialog和Toast的window的建立過程應該都是大同小異的,這篇文章就講到這裡吧!