美文网首页
Activity的视图创建和显示过程

Activity的视图创建和显示过程

作者: timepp | 来源:发表于2017-12-28 00:25 被阅读0次

在android的四大基本组件中,activity可以说是使用频率最高的一个了。我们先来看一下官方文档中对于activity的介绍。

An activity is a single, focused thing that the user can do.Almost all activities interact with the user, so the Activity class takes care of creating a window for you in which you can place your UI with {@link #setContentView}.While activities are often presented to the user as full-screen windows, they can also be used in other ways: as floating windows (via a theme with {@link android.R.attr#windowIsFloating} set) or embedded inside of another activity (using {@link ActivityGroup}).

由此可见,我们需要通过setContentView方法将要展示的界面放到activity里去。那么在setContentView方法里到底做了什么呢?在activity里的源码中可以看到有3个setContentView的重载函数。

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

public void setContentView(View view) {
    getWindow().setContentView(view);
    initWindowDecorActionBar();
}

public void setContentView(View view, ViewGroup.LayoutParams params) {
    getWindow().setContentView(view, params);
    initWindowDecorActionBar();
}

而这三个重载函数中做的事情基本是一样的,都是先获取到window对象,然后调用它的setContentView方法。而在getWindow方法里返回的是activity的成员变量mWindow,而它是在activity里的attach方法里初始化的。

public class Activity extends ContextThemeWrapper
    implements LayoutInflater.Factory2,
    Window.Callback, KeyEvent.Callback,
    OnCreateContextMenuListener, ComponentCallbacks2,
    Window.OnWindowDismissedCallback, WindowControllerCallback {
    private Window mWindow;
    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) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        //在这里实例化了window对象,另不同版本的源码实例化window的方式不同。
        mWindow = new PhoneWindow(this, window);
        //设置各种回调
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);

        -- ignore some code --
        //在这里设置了windowManager,可以看到,windowManager的获取属于一种系统服务。
        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;
    }
}

因此,getWindow方法返回的实际是一个PhoneWindow对象,再看一下PhoneWindow类的setContentView方法。

public void setContentView(int layoutResID) {
     if (mContentParent == null) {
        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 {
         mLayoutInflater.inflate(layoutResID, mContentParent);
     }
     final Callback cb = getCallback();
     if (cb != null && !isDestroyed()) {
         cb.onContentChanged();
     }
}

public void setContentView(View view) {
    setContentView(view, new 
    ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

public void setContentView(View view, ViewGroup.LayoutParams params) {       
     if (mContentParent == null) {
         installDecor();
     } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
         mContentParent.removeAllViews();
     }

     if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
         view.setLayoutParams(params);
         final Scene newScene = new Scene(mContentParent, view);
         transitionTo(newScene);
     } else {
         //将contentView加到了contentParent里面。
         mContentParent.addView(view, params);
     }
     final Callback cb = getCallback();
     if (cb != null && !isDestroyed()) {
         cb.onContentChanged();
     }
 }

同样是有3个重载的setContentView方法,实现也基本一致,重点关注一下常用的setContentView(int layoutResID)的实现。先是判断了mContentParent是否为空,若为空则调用了installDecor方法,若不为空则判断当前的window是否有FEATURE_CONTENT_TRANSITIONS(用于标志当窗口内容更改时使用TransitionManager动画)标志,若无则直接移除所有的view,而mContentParent又是在installDecor方法里初始化的。

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor();
        ---ignore some code---
    }
    ---ignore some code---
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        ---ignore some code---
        //主要是设置actionBar和加载一些进入和退出的动画等
    }
}

再来看一下generateDecor方法。

protected DecorView generateDecor(int featureId) {
    ---ignore some code---
    return new DecorView(context, featureId, this, getAttributes());
}
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    DecorView(Context context, int featureId, PhoneWindow window,
        WindowManager.LayoutParams params) {
        super(context);
        ---ignore some code---
        //在这里保存了window变量
        setWindow(window);
        ---ignore some code---
    }
}

这就是DecorView的由来,这个问题应该困扰了不少同学吧(反正我没看源码之前就只知道有这么个东西,但是具体怎么来的并不知道)。接下来再看一下generatorLayout方法。

protected ViewGroup generateLayout(DecorView decor) {
    //主要是判断并设置了一堆的标志位,为之后加载基础布局做准备
    ---ignore some code---
    //这个是我们的界面将要采用的基础布局xml文件的id
    int layoutResource;

    //根据标志位,给layoutResource赋值,设置不同的主题和样式,会采用不同的布局文件
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    }
    ---ignore some code---
    else {
        layoutResource = R.layout.screen_simple;
    }
    //要开始更改mDecor啦~
    mDecor.startChanging();
    //将xml文件解析成View对象
    View in = mLayoutInflater.inflate(layoutResource, null);
    //decor和mDecor实际上是同一个对象,一个是形参,一个是成员变量
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
    //ID_ANDROID_CONTENT常量在前面有定义,值为com.android.internal.R.id.content;
    //而findViewById()会调用mDecor.findViewById(),因此可以判定无论我们采用什么主题和样式,layoutResource的布局文件里必然有一个id为ID_ANDROID_CONTENT的ViewGroup。
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }
    //主要是设置背景和标题
    ---ignore some code---
    mDecor.finishChanging();
    //最后把id为content的一个ViewGroup返回了
    return contentParent;
}

screen_simple布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
          android:inflatedId="@+id/action_mode_bar"
          android:layout="@layout/action_mode_bar"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

这也解释了为什么一个最简单的HelloWorld应用在使用Hierarchy查看view结点时会跟下图一样。


HelloWorld结点图

到现在为止,我们已经知道了DecorView是在什么时候实例化的,知道了setContentView方法会将传入的界面加到一个id为contentViewGroup里。但是这个时候DecorView还没有被添加到PhoneWindow中,因此这个时候用户还是无法看到界面。
那么什么时候用户才能看到我们在setContentView里设置的界面呢?即什么时候才会将DecorView加到PhoneWindow中呢?这个就和Activity的启动流程有关系了,来看一下ActivityThreadhandleLaunchActivity方法和handleResumeActivity方法。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ---ignore some code---
    //就是在这里调用了Activity.attach()呀,接着调用了Activity.onCreate()和Activity.onStart()生命周期,
    //但是由于只是初始化了mDecor,添加了布局文件,还没有把
    //mDecor添加到负责UI显示的PhoneWindow中,所以这时候对用户来说,是不可见的
    Activity a = performLaunchActivity(r, customIntent);
    ---ignore some code---
    if (a != null) {
        //这里面执行了Activity.onResume()
        handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed);
        ---ignore some code---
    }
}

final void handleResumeActivity(IBinder token,
     boolean clearHide, boolean isForward, boolean reallyResume) {
     //这个时候,Activity.onResume()已经调用了,但是现在界面还是不可见的
     ActivityClientRecord r = performResumeActivity(token, clearHide);
     if (r != null) {
         final Activity a = r.activity;
         if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            //decor对用户不可见
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
           
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                //终于被添加进WindowManager了,但是这个时候,还是不可见的
                wm.addView(decor, l);
            }
            
            if (!r.activity.mFinished && willBeVisible
                && r.activity.mDecor != null && !r.hideForNow) {
                 //在这里,执行了重要的操作!
                 if (r.activity.mVisibleFromClient) {
                        r.activity.makeVisible();
                 }
             }
         }
     }
}

从上面的源码中可以看到,其实在onResume方法执行之后,界面仍然是不可见的,而当执行了ActivitymakeVisible方法后,用户才能看到我们设置的界面。

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

到此,我们已经了解了Activity的界面显示的完整过程。

相关文章

网友评论

      本文标题:Activity的视图创建和显示过程

      本文链接:https://www.haomeiwen.com/subject/poxygxtx.html