Android控件系統(五)——Activity窗口建立和顯示


Android版本:7.0(API27)java

[TOC]android


performLaunchActivity

從ActivityClientRecord中獲取ComponentNamewindows

ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
    r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
            Context.CONTEXT_INCLUDE_CODE);
}

ComponentName component = r.intent.getComponent();
if (component == null) {
    component = r.intent.resolveActivity(
        mInitialApplication.getPackageManager());
    r.intent.setComponent(component);
}

if (r.activityInfo.targetActivity != null) {
    component = new ComponentName(r.activityInfo.packageName,
            r.activityInfo.targetActivity);
}
複製代碼

建立activitybash

java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
複製代碼

建立appContext和Application對象app

ContextImpl appContext = createBaseContextForActivity(r);
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
複製代碼

上述信息準備完成後就開始Activity的初始化和啓動:ide

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()) {
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
    mInstrumentation.callActivityOnCreate(activity, r.state);
}

if (!r.activity.mFinished) {
    activity.performStart();
    r.stopped = false;
}
複製代碼
  • Activity調用了attach對Activity進行初始化,其中最重要的就是PhoneWindow了;
  • callActivityOnCreate實際上最後調用的是Activity的onCreate,而onCreate中會有setContentView,這樣咱們本身的視圖就會被設置到PhoneWindow的Decorview控件樹中;
  • activity.performStart最終調用onStart;

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);
    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的建立和設置Callback回調,這個Callback的實現是由Activity實現的,經過這一回調能夠將窗口中發生的變化通知到Activity。同時,將PhoneWindow賦值給內部變量mWindow。oop

mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setCallback(this);
複製代碼

callActivityOnCreate

public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
    prePerformCreate(activity);
    activity.performCreate(icicle, persistentState);
    postPerformCreate(activity);
}

final void performCreate(Bundle icicle) {
    performCreate(icicle, null);
}

final void performCreate(Bundle icicle, PersistableBundle persistentState) {
    mCanEnterPictureInPicture = true;
    restoreHasCurrentPermissionRequest(icicle);
    if (persistentState != null) {
        onCreate(icicle, persistentState);
    } else {
        onCreate(icicle);
    }
    mActivityTransitionState.readState(icicle);

    mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
            com.android.internal.R.styleable.Window_windowNoDisplay, false);
    mFragments.dispatchActivityCreated();
    mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
}
複製代碼

最終就是調用onCreate,onCreate中會調用Activity.setContentView()post

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
複製代碼

getWindow()得到的是mWindow,從前面的分析能夠知道mWindow是PhoneWindow,那最終又調用到PhoneWindow內部的setContentView方法。ui

  到這裏咱們尚未看到Activity窗口建立的具體過程,只是看到了咱們在onCreate中調用的setContentView設置的layout在PhoneWindow中被使用,其實這個祕密都隱藏在PhoneWindow中。this

PhoneWindow

  Android提供了com.view.Window類以一個更高的級別操做窗口,Window類中有三個最核心的組件:WindowManager.LayoutParams、控件樹以及Window.Callback。

  • 針對WindowManager.LayoutParams,Window類提供了一些列set方法用於設置LayoutParams屬性。這項工做看似沒什麼用處,還不如開發者自行管理LayoutParams來的方便。不過Window類的優點在於它能夠根據用例初始化LayoutParams中的屬性,例如窗口令牌、窗口名稱以及FLAG。
  • 針對控件樹,Window爲使用者提供了多種多樣的控件樹模板。這些模板能夠爲窗口提供形式多樣卻又風格統一的展現方式以及輔助功能,例如標題欄、圖標、頂部進度條、動做欄等,甚至它還爲使用者提供了選項菜單的實現。使用者僅需將顯示其所關心內容的控件樹交給它,它就會將其嵌套在模板的合適位置。這一模板就是最終顯示時的窗口外觀。Window類提供了接口用於模板選着、指按期望顯示的內容以及修改模板的屬性(如標題、圖標、進度條進度等)。
  • Window.Callback類是一個接口,Window的使用者能夠實現這個接口並註冊到Window中,因而每當窗口中發生變化時均可以獲得通知。關於這一點,我想你們已經有必定了解了,Activity就是PhoneWindow的使用者,而且註冊了回調接口Callback。

  簡單來講,Window類是一個模板,它大大簡化了一個符合使用習慣的控件樹的建立過程。使得使用者僅須要關注控件樹中其真正感興趣的部分,而且僅需少許的工做就可使這部分嵌套在一個漂亮而專業的窗口外觀之下,而不用關心這一窗口外觀的控件樹的構成。

  目前Window的惟一實現是PhoneWindow。Window類中提供了用於修改LayoutParams的接口等通用功能,而PhoneWindow類則負責具體的外觀模板的實現。

選擇窗口外觀與設置顯示內容

  相信你們對Activity.requestWindowFeature()和Activity.setContentView()兩個方法不會陌生,前者負責指定Activity窗口的特性,如是否擁有標題欄,是否存在一個進度條,程序圖標的位置等。換而言之,Activity.requestWindowFeature()決定了窗口的外觀;然後者則設置開發者所指望顯示的控件樹,將這個控件樹填充到Activity的窗口中。

public final boolean requestWindowFeature(int featureId) {
    return getWindow().requestFeature(featureId);
}

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
複製代碼

Activity中的這兩個方法都是將請求直接轉發給了PhoneWindow。

[PhoneWindow.requestWindowFeature()]

public boolean requestFeature(int featureId) {
    /*requestFeature調用必須在setContentView以前,由於setContentView調用後,整個窗口就以及肯定了,此時是不容許再修改窗口的外觀的*/
    if (mContentParentExplicitlySet) {
        throw new AndroidRuntimeException("requestFeature() must be called before adding content");
    }
    
    /*這部分會作一系列的檢查,由於PhoneWindow容許使用者設置對個feature,可是不一樣的feature之間可能存在互斥,因此須要進行檢查*/
    ........
    
    return super.requestFeature(featureId);
}
複製代碼

再看Window.requestFeature

public boolean requestFeature(int featureId) {
    final int flag = 1<<featureId;
    mFeatures |= flag;
    mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
    return (mFeatures&flag) != 0;
}
複製代碼

可見設置窗口特性仍是十分簡單的。請記住窗口的特性被保存在Window.mFeatures成員之中。requestFeature方法並無馬上建立外觀模板,可是mFeatures成員將會爲建立外觀模板提供依據。

[PhoneWindow.setContentView()]

public void setContentView(int layoutResID) {
    // 1. 首先爲窗口準備外觀模板
    if (mContentParent == null) {
        /*mContentParent爲null時,代表外觀模板還沒有建立,此時會經過installDecor方法建立一個外觀模板。建立完成以後mContentParent便會被設置爲模板中的一個ViewGroup而且隨後它會做爲使用者提供的控件樹的父控件     
        */
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        /* 2. 將開發者給定的layout實例化爲一顆控件樹,而後做爲子控件保存在mContentParent中。完成這個操做後,PhoneWindow便完成了整棵控件樹的建立*/
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    
    /*3.Callback接口被Window用來向使用者通知其內部所發生的變化。此時通知使用者Window的控件樹發生了改變。做爲Window的使用者,Activity類實現了這一接口,所以開發者能夠經過重寫Activity的這一方法從而對這些變化作出反應*/
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    /*4. 這個與咱們前面requestFeature中的分析是一致的*/
    mContentParentExplicitlySet = true;
}
複製代碼

[PhoneWindow.installDecor()]

installDecor中的關鍵代碼以下

private void installDecor() {
    //建立根控件DecorView
    mDecor = generateDecor(-1);
    .....
    //建立外觀模板
    mContentParent = generateLayout(mDecor);
    .....
}
複製代碼

generateDecor方法中進行外觀模板的建立。外觀模板的建立是一個繁瑣的過程,由於它不只受前面所述窗口特性的影響,並且還須要考慮窗口的樣式設置、Android的版本等。

  • 解析窗口樣式表
TypedArray a = getWindowStyle();

mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
        & (~getForcedWindowFlags());
if (mIsFloating) {
    setLayout(WRAP_CONTENT, WRAP_CONTENT);
    setFlags(0, flagsToUpdate);
} else {
    setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}

...............
................

if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
    if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
    a.getValue(R.styleable.Window_windowFixedHeightMajor,
            mFixedHeightMajor);
}
if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
    if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
    a.getValue(R.styleable.Window_windowFixedHeightMinor,
            mFixedHeightMinor);
}
if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
    requestFeature(FEATURE_CONTENT_TRANSITIONS);
}
if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
    requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
}
複製代碼
  • Android版本判斷
final Context context = getContext();
final int targetSdk = context.getApplicationInfo().targetSdkVersion;
final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
final boolean targetHcNeedsOptions = context.getResources().getBoolean(
        R.bool.target_honeycomb_needs_options_menu);
final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);

if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
    setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
} else {
    setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
}

if (!mForcedStatusBarColor) {
    mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
if (!mForcedNavigationBarColor) {
    mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
    mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
            0x00000000);
}

WindowManager.LayoutParams params = getAttributes();

......................
......................

// The rest are only done if this window is not embedded; otherwise,
// the values are inherited from our container.
if (getContainer() == null) {
    if (mBackgroundDrawable == null) {
        if (mBackgroundResource == 0) {
            mBackgroundResource = a.getResourceId(
                    R.styleable.Window_windowBackground, 0);
        }
        if (mFrameResource == 0) {
            mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
        }
        mBackgroundFallbackResource = a.getResourceId(
                R.styleable.Window_windowBackgroundFallback, 0);
        if (false) {
            System.out.println("Background: "
                    + Integer.toHexString(mBackgroundResource) + " Frame: "
                    + Integer.toHexString(mFrameResource));
        }
    }
    if (mLoadElevation) {
        mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
    }
    mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
    mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
}
複製代碼
  • 建立外觀模板
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
    layoutResource = R.layout.screen_swipe_dismiss;
    setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
    if (mIsFloating) {
        TypedValue res = new TypedValue();
        getContext().getTheme().resolveAttribute(
                R.attr.dialogTitleIconsDecorLayout, res, true);
        layoutResource = res.resourceId;
    } else {
        layoutResource = R.layout.screen_title_icons;
    }
    // XXX Remove this once action bar supports these features.
    removeFeature(FEATURE_ACTION_BAR);
    // System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
        && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
    // Special case for a window with only a progress bar (and title).
    // XXX Need to have a no-title version of embedded windows.
    layoutResource = R.layout.screen_progress;
    // System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
    // Special case for a window with a custom title.
    // If the window is floating, we need a dialog layout
    if (mIsFloating) {
        TypedValue res = new TypedValue();
        getContext().getTheme().resolveAttribute(
                R.attr.dialogCustomTitleDecorLayout, res, true);
        layoutResource = res.resourceId;
    } else {
        layoutResource = R.layout.screen_custom_title;
    }
    // XXX Remove this once action bar supports these features.
    removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
    // If no other features and not embedded, only need a title.
    // If the window is floating, we need a dialog layout
    if (mIsFloating) {
        TypedValue res = new TypedValue();
        getContext().getTheme().resolveAttribute(
                R.attr.dialogTitleDecorLayout, res, true);
        layoutResource = res.resourceId;
    } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
        layoutResource = a.getResourceId(
                R.styleable.Window_windowActionBarFullscreenDecorLayout,
                R.layout.screen_action_bar);
    } else {
        layoutResource = R.layout.screen_title;
    }
    // System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
    layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
    // Embedded, so no decoration is needed.
    layoutResource = R.layout.screen_simple;
    // System.out.println("Simple!");
}

mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
    throw new RuntimeException("Window couldn't find content container view");
}
複製代碼
  • 首先是選着一個模板,並賦值給layoutResource;
  • mDecor.onResourcesLoaded加載layoutResource模板;
  • findViewById找到id爲ID_ANDROID_CONTENT的ViewGroup,並賦值給contentParent;
  • 最後contentParent返回被賦值給mContentParent;
mLayoutInflater.inflate(layoutResID, mContentParent);
複製代碼

開發者所設置的layout又經過上面的方式唄添加到mContentParent中。這樣一來,整棵DecorView樹就被完成的建立出來了。 這個過程對比「Android Framework概述」一文,完成了以下圖所示關係的創建:

image

Activity窗口顯示

  經過前面的分析,此時PhoneWindow和完整的控件樹DecorView都已經準備好了,如今咱們須要將DecorView經過WMS顯示出來(這一過程所須要使用的知識點請參考「Window與WindowMananger」一文)。   在「淺析Activity啓動過程」咱們說過,ActivityThread.handleResumeActivity完成Activity窗口的顯示: [ActivityThread.handleResumeActivity]

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward,           boolean reallyResume, int seq, String reason) {
    .....
    /*1. 調用onResume回調
    */
    r = performResumeActivity(token, clearHide, reason);
    .....
    
    final Activity a = r.activity;
    
    ......
    
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        /*2. 設置decor爲INVISIBLE
        */
        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 (r.mPreserveWindow) {
            a.mWindowAdded = true;
            r.mPreserveWindow = false;
            // Normally the ViewRoot sets up callbacks with the Activity
            // in addView->ViewRootImpl#setView. If we are instead reusing
            // the decor view we have to notify the view root that the
            // callbacks may have changed.
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                /*3. 調用WindowManager的addview方法顯示decor,WindowManager的原理參考「Window與WindowMananger」一文
                */
                wm.addView(decor, l);
            } else {
                // The activity will get a callback for this {@link LayoutParams} change
                // earlier. However, at that time the decor will not be set (this is set
                // in this method), so no action will be taken. This call ensures the
                // callback occurs with the decor set.
                a.onWindowAttributesChanged(l);
            }
        }
    } 
    
    .....
    
    if (r.activity.mVisibleFromClient) {
        /*4. 設置decor爲VISIBLE
        */
        r.activity.makeVisible();
    }
}
複製代碼

  可見,當Activity.onResume被調用時,Activity的窗口其實還沒有顯示,也就是說Activity可見發生在onResume()以後。   第二步中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)起來了。

相關文章
相關標籤/搜索