Android控件系统(五)——Activity窗口创建和显示


Android版本:7.0(API27)

[TOC]


performLaunchActivity

从ActivityClientRecord中获取ComponentName

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);
}
复制代码

创建activity

java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
复制代码

创建appContext和Application对象

ContextImpl appContext = createBaseContextForActivity(r);
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
复制代码

上述信息准备完成后就开始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, 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。

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()

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
复制代码

getWindow()获得的是mWindow,从前面的分析可以知道mWindow是PhoneWindow,那最终又调用到PhoneWindow内部的setContentView方法。

  到这里我们还没有看到Activity窗口创建的具体过程,只是看到了我们在onCreate中调用的setContentView设置的layout在PhoneWindow中被使用,其实这个秘密都隐藏在PhoneWindow中。

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<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概述”一文,完成了如下图所示关系的建立:

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)起来了。

你可能感兴趣的:(移动开发,操作系统,java)