Activity布局加载流程源码分析(I)

最近阅读Android源码,似乎有点发现新大陆的感觉。以前经常接触Android知识,在阅读源码中,开始变得豁然开朗。前两天才写完两篇博文Activity启动流程源码分析(应用中)Activity启动流程源码分析(Launcher中),今天,就急不可耐的想写写Activity布局加载流程,其实,也就是想趁热打铁,好好梳理梳理这部分知识。

在开始梳理之前,我们需要了解一些概念,如:

  • Window: 是一个抽象类,表示是一个窗口。Android系统中的界面,也都是以窗口的形式存在的。
  • PhoneWindow: 是Window类具体实现类,Activity中布局加载逻辑主要就是在此类中完成的。
  • WindowManager: 是Window的管理类,管理着Window的添加、更新和删除。
  • WindowManagerService(WMS): 是系统窗口管理服务类,具体管理着系统各种各样的Window.
  • DecorView: 是Window的顶级View,主要负责装载各种View。

一、Activity布局加载分析

我们知道,设置Activity布局内容,主要是在Activity的onCreate()中调用setContentView()方法,下面让我们来看看此方法

   
    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);//核心代码
        initActionBar();
    }

这里主要调用了getWindow().setContentView()方法,我们来看看Activity中getWindow()

    public Window getWindow() {
        return mWindow;
    }

由此知mWindow是Activity一个属性变量,在前面Activity启动流程介绍中,我们知道在Activity启动前都会先调用attach(),而这mWindow就是在attach初始化的时候赋值的,我们来看看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) {
        attachBaseContext(context);
        mFragments.attachActivity(this);
        
        mWindow = PolicyManager.makeNewWindow(this);//核心代码

        ......

        mUiThread = Thread.currentThread();
        
        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;

        mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }

这里我们来关注一下PolicyManager.makeNewWindow(this)方法,创建Window,我们来看看PolicyManager类

public final class PolicyManager {  
     
   private static final String POLICY_IMPL_CLASS_NAME =  
        "com.android.internal.policy.impl.Policy";  
  
    private static final IPolicy sPolicy;  
  
    static {  
        try {  
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);  
            sPolicy = (IPolicy)policyClass.newInstance();//反射初始化Policy

        } catch (ClassNotFoundException ex) {  
            throw new RuntimeException(  
                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);  
        } catch (InstantiationException ex) {  
            throw new RuntimeException(  
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);  
        } catch (IllegalAccessException ex) {  
            throw new RuntimeException(  
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);  
        }  
    }  
   
    public static Window makeNewWindow(Context context) {  
        return sPolicy.makeNewWindow(context); //核心方法
    }  
    .......
}

由上易知,这里主要是通过反射初始化Policy,然后利用设计模式里氏替换原则调用Policy的makeNewWindow()方法,我们继续来看Policy中的方法

public class Policy implements IPolicy {  
   
    ........

    public PhoneWindow makeNewWindow(Context context) {  
        return new PhoneWindow(context);//核心代码
    }  
    ......
}  

我们可以发现mWindow其实就是PhoneWindow,在Activity中getWindow().setContentView()方法,就是调用PhoneWindow中的setContentView方法,所以我们这里来看看PhoneWindow中的setContentView()方法

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null){
            installDecor();//1.安装装饰器
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);//2.填充我们的布局文件
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

从注释2,我们知道布局填充器mLayoutInflater向mContentParent填充我们的布局内容,而mContentParent是一个ViewGroup,它是怎么赋值的呢?这里我们要来看注释1,当mContentParent为空时,会安装装饰器,我们继续来看phoneWindow中installDecor()方法

  private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();//1.生成装饰器
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//2.对填充我们布局的ViewGroup赋值

            mDecor.makeOptionalFitsSystemWindows();

            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                  .......
                } else {
                    mTitleView.setText(mTitle);//设置Activity的title
                }
            } else {
                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
                if (mActionBar != null) {
                   .......mActionBar的处理
                }
            }
        }
    }

首先,我们先来看看注释1装饰器的生成方法generateDecor()

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

这里主要就是装饰器DecorView的初始化,我们再来看一下DecorView的源码

   private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
        ......

        public DecorView(Context context, int featureId) {
            super(context);
            mFeatureId = featureId;
        }
       .......

DecorView类包括内容还有许多,这里就不介绍了,我们只需知道DecorView是PhoneWindow的内部类,DecorView继承于FrameLayout,实现RootViewSurfaceTaker接口。下面我们来看一下mContentParent的生成,即generateLayout(mDecor)方法

 protected ViewGroup generateLayout(DecorView decor) {
        
        .......//初始化一些window属性

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();

        //通过判断Activity的不同feature加载不同的系统默认布局
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title_icons;
            }
            
            removeFeature(FEATURE_ACTION_BAR);
           
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            //带进度系统布局
            layoutResource = com.android.internal.R.layout.screen_progress;
            
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {      
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = com.android.internal.R.layout.screen_custom_title;
            }
           
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            //无Titile系统布局
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
                    layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
                } else {
                    layoutResource = com.android.internal.R.layout.screen_action_bar;
                }
            } else {
                layoutResource = com.android.internal.R.layout.screen_title;
            }
            
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;
        } else {
           
            layoutResource = com.android.internal.R.layout.screen_simple;//1.一般系统布局
           
        }

        mDecor.startChanging();

        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));//2.向装饰View加入系统布局View

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//3.获取我们Activity能填充的内容ViewGroup
        ......
        }

        mDecor.finishChanging();

        return contentParent;
    }

这里我们来看一下,系统默认的几种Activity的头部布局xml文件,布局文件的源码位置为:android4.1.1_r1\frameworks\base\core\res\res\layout,我们挑两个文件来看一下:

第一个screen_title.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <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" />
    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

第二个,screen_simple.xml

<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" />
    <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>

通过对比,我们发现这两个布局文件都有一个共同id为@android:id/content的FrameLayout,其实这也就是我们Activity布局填充容器。我们还发现,这两个布局父布局都是一个线性布局LinearLayout,并且方向都是垂直的,这也验证了我们Activity内容布局一般都是状态栏的下边的模式。我们再来看后面的代码

  View in = mLayoutInflater.inflate(layoutResource, null);
  decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));//2.向装饰View加入系统布局View

  ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//3.获取我们Activity能填充的内容ViewGroup

这里向装饰器添加了系统布局View,并从系统布局View中获取了Activity填充内容的容器ViewGroup。其中ID_ANDROID_CONTENT就是com.android.internal.R.id.content,通过(ViewGroup)findViewById(ID_ANDROID_CONTENT)就获取了布局文件中的FrameLayout,即Activity内容填充布局的ViewGroup。这样我们再回到PhoneWindow的setContentView方法

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);//核心代码
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

现在mContentParent已经赋完值了,再通过布局填充器mLayoutInflater的inflate()方法,这样我们就把Activity的布局文件添加到装饰器上了。然而,现在虽然装饰器DecorView上已经有了Activity布局内容,但是是什么时候添加到Window上的呢?这里就需要了解Activity的启动流程,在Activity的启动流程最后几步会执行ActivityThread中handleLaunchActivity()方法,我们接着此方法继续分析

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
      
        .......

        Activity a = performLaunchActivity(r, customIntent);//1.创建Activity实例

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;

            handleResumeActivity(r.token, false, r.isForward);//2.调用Activity onResume方法

            .......
        } else {
            .......
        }
    }

在注释1处,已经建立Activity的实例,并且执行Activity生命周期的attach()和onCreate()方法。我们知道setContentView()也就在onCreate()方法中调用的,所以这个时候,我们Activity布局文件内容已经装入了装饰器DecorView中,接下来就是把DecorView和Window关联起来,所以下面我们继续来看handleResumeActivity()方法

 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();

        ActivityClientRecord r = performResumeActivity(token, clearHide);//1.执行Activity的onResume方法

        if (r != null) {
            final Activity a = r.activity;

            ........
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(a.getActivityToken());//2.Activity显示可见
                } catch (RemoteException e) {
                }
            }
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                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 (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//3.通过WindowManager将DecorView加入Window,从而显示Window,Activity变为可见。
                }
            } else if (!willBeVisible) {
                r.hideForNow = true;
            }

            cleanUpPendingRemoveWindows(r);

            if (!r.activity.mFinished && willBeVisible&& r.activity.mDecor != null && !r.hideForNow) {
             
                WindowManager.LayoutParams l = r.window.getAttributes();
                if ((l.softInputMode
                        & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                        != forwardBit) {
                    l.softInputMode = (l.softInputMode
                            & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                            | forwardBit;
                    if (r.activity.mVisibleFromClient) {
                        ViewManager wm = a.getWindowManager();
                        View decor = r.window.getDecorView();
                        wm.updateViewLayout(decor, l);//4.更新DecorView,更新Activity界面
                    }
                }
                r.activity.mVisibleFromServer = true;
                mNumVisibleActivities++;
                if (r.activity.mVisibleFromClient) {
                    r.activity.makeVisible();
                }
            }

          .......

        } else {
          .......
        }
    }

在注释1处,调用performResumeActivity(token, clearHide)方法,实际上就是调用activity生命周期的onResume()方法。注释2处,通过Binder跨进程通信,调用ActivityManagerService中willActivityBeVisible()获取显示Activity的控制开关,从而在注释3处,通过WindowManager添加装饰器DecorView到Window,然后,再调用相关View的绘制流程,这样一个有布局的Activity就被加载出来了。

到这里,Activity布局加载流程就是梳理完了。

注:源码采用android-4.1.1_r1版本,建议下载源码然后自己走一遍流程,这样更能加深理解。

二、参考文档

Binder通信机制原理解析

Activity启动流程源码分析(应用中)

Activity启动流程源码分析(Launcher中)

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页