Activity 从启动到布局绘制的简单分析

这篇文章主要是配合源码简单的介绍一下,程序的加载过程,Activity 中布局的加载过程,可以大致的了解整个过程。不过过分的追究细节,由于里面任何一个细节可能都够你研究一段时间的!先了解掌握大致过程,再慢慢来!html

开始启动

咱们都知道,Activity 是有生命周期的,onCreate()onStart()onResume 等等那么这些方法是如何调用的呢?它不会无缘无故的本身调用。其实当咱们开启 APP 的时候会建立一个叫作 ActivityThread 的类,咱们能够认为这个类是主类,就和 Java 程序中的启动类同样,ActivityThread 类有一个 main 函数,这个函数就是咱们的程序入口!java

main.jpg

相信看到这个,你们都会恍然大悟了,这就是咱们学习 Java 的时候的 main 方法,认为是程序的入口。能够看到方法里面对一个 Looper 对象进行了初始化,Looper.prepareMainLooper() 经过这个方法就给当前的线程初始化了 Looper,这个 Looper 成了 Application 的 main looper,这也是为何咱们在主线程中不用本身再 Looper.prepare 的缘由。能够认为任何在主线程的操做都会发送到这个 Looper 对应的 Handler 中去。很容易能够找到 Looper 对应的 Handler,其实就是 ActivityThread 的一个内部类,继承了 Handlerandroid

handler-1.jpg

handler-2.jpg

这个就是 ActivityThreadHandler 的部分代码实现,而后看到 Handler 里面的这段代码app

public void handlerMessage(Message msg){
    .....;
    switch(msg.what){
        case LAUNCH_ACTIVITY:
            .....;
            // r 是在 msg 里面拿到的对象 msg.obj
            handlerLaunchActivity(r,null,"LAUNCH_ACTIVITY");
            .....;
    }
}
复制代码

下面再来看 handlerLaunchActivity() 这个方法:ide

private void handleLaunchActivity(ActivityClientRecord r,Intent customIntent,String reason){
    ·····;
    Activity a = performLaunchActivity(r,customIntent);
    ......;
   	handlerResumeActivity(...);
    
}
复制代码

里面这两个很重要的方法我已经列出来了,下面咱们来看看 performLaunchActivity(r,customIntent) 方法:函数

// 这里面代码一样不少,咱们只挑选重要的
private Activity performLaunchActivity(ActivityClentRecord r,Intent customIntent){
    .......;
    // 这里 new 出了 activity
    activity = mInstrumentation.newActivity(cl,component.getClassName(),r.intent);
    ......;
    // 调用了 Activity 的 attach 方法(很重要)
    activity.attach(appContext,this,getI......);
    ......;
    // 调用了 Activity 的 onCreate 方法
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    // 调用了 onStart 方法
    ........;
}
复制代码

这段代码中分别调用了 Activity 的 attach 方法和 onCreate 方法,同时下面也调用了 onStart 方法,这里省略了。oop

**结论:**在 performLaunchActivity 方法中,首先经过 mInstrumention 产生了 Activity 对象,而后调用了 Activity 的 attach 方法,而后经过 Instrumentation 对象调用了 activity 的生命周期中的方法。说了这么多,无非是大致了解了这个启动过程。知道 Activity 在执行生命周期前是先调用 attach 方法的。其中 attach 方法内的一些代码是很关键的,和整个 Activity 的启动有很重要的关系,下面来看一下 attach 方法的源码:布局

// 这个方法存在于 Activity 类中
final void attach(Context context,ActivityThread aThread,Instrumentation instr,IBinder token,int ident,Application application,Intent intent,ActivityInf info,CharSequence title,Activity parent,String id,.........){
    
    .........;
    // Window 对象赋值
    mWindow = new PhoneWindow(this,window,activityConfigCallback);
    mWindow.set 许多监听回调 (WindowContrallerCallBack、Callback)等等;
    将各类传递过来的各类参数赋值给 Activity 中的成员;
          				     mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken,mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED)!=0);
    mWindowManager = mWindow.getWindowManager();
    
}
复制代码

attach 这个方法的主要内容:首先给 Activity 中的 mWindow 成员变量赋值,具体的实现对象是 PhoneWindow,而后给 Activity 中的其余许多关键的成员赋值,给 mWindow 变量设置 WindowManager,而后给 Activity.mWindowManager 赋值。mWindow 是一个 Window 类型的变量,实际上一个 PhoneWindow 对象,和 Activity 的内容显示有关系学习

onCreate

下面就开始执行 Activity 中的 onCreate 方法了。this

咱们都知道,每个 Activity 对应一个布局文件,在 Activity 的 onCreate() 方法中都须要 setContentView(int res) 经过这个方法咱们就能够将布局与 Activity 关联起来了。那么布局是怎么加载的呢。这个时候就要看 setContentView() 的源码了:

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

getWindow() 返回的是 Window 对象,这个对象刚刚在 attach 方法中咱们接触过,其实它的真正实现是 PhoneWindow,下面来看 PhoneWindow 中的 setContentView 方法:

// 放置窗口内容的视图,它既能够是 mDecor 自己(没有 Title 的状况下),也能够是 mDecor 的子项, 这种状况就是 mDecor 中有 Title 的状况
ViewGroup mContentParent;
// 这是窗口的顶层视图,包含窗口装饰
private DecorView mDecor;

public void setContentView(int layoutResID){
    if(mContentParent == null){
        installDecor();
    }else if(!hasFeature(FEATURE_CONTENT_TRANSITIONS)){
        mContentParent.removeAllView();
    }
    // 将 布局资源 layoutResID 填充到 mContentParent 中
    mLayoutInflater.inflate(layoutResID,mContentParent);
    ........;
}
复制代码

PhoneWindow 类中有两个和视图相关的成员变量,一个是 DecorView mDecor,另外一个是 ViewGroup mConentParent

下面再来看看 PhoneWindow 中 setContentView 中的 inStallDecor() 方法:

private void installDecor(){
    ......;
    
    if(mDecor == null){
        // 生成 mDecor;
        mDecor = generateDecor(-1);
        ......;
    }else{
        mDecor.setWindow(this);
    }
    
    if(mContnetParent == null){
        mContentParent = generateLayout(mDecor);
        .......;
        ....不少代码;
        // 能够认为 DecorContentParent 是操做 Title 的
        final DecorContentParent decorContentParent = (DecorContentParent)mDecor.findViewById(R.id.decor_content_parent);
        if(decorContentParent != null){
            mDecorContentParent = decorContentParent;
            .......;
            对 Title 的一系列操做;
        }
        // 也有获取 TitleView 的代码
       
    }
        
}

复制代码

mDecor 是经过 generateDecor() 方法来获取的。

protected DecorView generateDecor(int featureId){
    ......;
    return new DecorView(context,featureId,this,getAttributes());
    
}
复制代码

DecorView 继承了 FrameLayout 是整个 PhoneWindow 的根视图

再来看看 generateLayout(DecorView decor)方法:

protected ViewGroup generateLayout(DecorView decor){
	// 获取当前主题中的一些属性
    TypedArray a = getWindowStyle();
    // getWindowStyle() 的内容:mContext.obtainStyledAttributes(com.android.internal.R.styleable.Window);
    // 其实就是得到定义的属性组中的一些信息
    ......;
    // 省略的代码大概就是利用 a 来获取主题中一些默认的样式,把这些样式设置到 DecorView 中
    // 大概内容,相似于
    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloading,false);
    if(mIsFloating){
        setLayout(WRAP_CONTENT,WRAP_CONTENT);
    }
    // 有没有 windowNoTitle 等等这种属性
    // 这里的设置大小,能够理解为是设置 DecorView 大小,在这里已经肯定了 DecorView 的大小了
    // 根据SDK 版原本设置视图样式代码
    
    // 填充窗口的资源
    int layoutResource;
    .....根据不一样的条件,给 layoutResource 赋予不一样的值;
    
    mDecor.startChanging();
    // 资源填充到 mDecor 中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); 
    // 这里的 findViewById 就是在 mDecor 中寻找
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ....修改 mDecor 背景颜色,设置 Title 的一些属性;
    return contentParent;
    
}
复制代码

能够看到 generateLayout() 方法里面仍是处理了许多关键的事情:

  • 根据各类属性值设置了 LayoutParam 参数
  • 根据 FEATURE 的值,选择了合适的布局资源 id 赋值给了 layoutResource。
  • 根据 Theme 中设置的不一样的风格,SDK 不一样的风格来对 DecorView 进行设置,Decor 装饰的意思,因此这个名字也很符合。
  • 把 layoutResource 填充到了 DecorView 中
  • 在 layoutResource 中有一个 id 为 ID_ANDROID_CONTENT 的 ViewGroup 即咱们的 contentParent
  • DecorView 包含了 Title 和 contentParent

installDecor() 小结:

  1. 一个 Activity 对应了一个 Window,这个 Window 的实现对象是 PhoneWindow,一对一的关系
  2. PhoneWindow 管理了整个 Activity 页面内容,不包括系统状态栏 ,PhoneWindow 是和应用的某个页面关联的。
  3. PhoneWindow 包含 ActionBar 和 内容。setContentViwe 方法是用来设置内容的,setTitle 是用来操做 Title 的。Window 中定义的 requestFeature() 等方法,不少与 ActionBar 属性相关的设置。这些方法都是公共方法,是为了方便咱们调用的。
  4. PhoneWindow 自己不是一个视图,它的成员变量 mDecor 是整个页面的视图。mDecor 是在 generateLayout() 的时候填充的。Title 和 contentParent 都是经过 findViewById() 从 mDecor 中获取的。

上面介绍的这些,只是执行完了 installDecor() 方法,这个时候,PhoneWindow 有了 DecorView,DecorView 有了本身的样式,有了 Title 和 ContentParent。下一步就是向 ContentParent 中添加本身的布局了。

public void setContentView(int layoutResID){
    .....;
    // 上面已经分析完了
    installDecor();
    // 向 ContentParent 中添加咱们本身的布局资源
    mLayoutInflater.inflate(layoutResID,mContentParent);
    .......;
}
复制代码

到此 setContentView 算是分析完了,onCreate 也就执行完了。那么咱们能够认为上面的 ActivityThread 中的 performLaunchActivity() 执行完了,接下来就开始执行 handleResumeActivity() 方法了。

final void handleResumeActivity(IBinder token,boolean clearHide,boolean isForward,boolean reallyResume,int seq,String reason){
    ......;
    // 能够把 ActivityClientRecord 类认为是记录 Activity 内部关键参数的类
    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();
            ....;
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            .....;
            if(a.mVisibleFromClient){
                a.mWindowAdded = true;
                wm.addView(decor,l)
            }
        }
    }
    。。。。。;
    
}
复制代码

**performResumeActivity()**方法,这个方法内部调用了 Activity 的 performResume() 方法(这个方法在 API 中没有出现,须要在完整源码中查看)

performResumeActivity.jpg

看到上面 mInstrumentation.callActivityOnResume(this) 就是调用了 Activity 的 onResume 方法。

而后再来看看 hanleResumeActivity 方法里面的 addView 方法,wm 是上面 a.getWindowManger() 获取到的,a 是 Activity,getWindowManager() 返回了 mWindowManager 对象,而这个对象是 WindowMangerImpl,它内部方法大部分是在 WindowManagerGlobal 内部实现

private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<>();

public void addView(View view,ViewGroup.LayoutParams params,Display display,Window parentWindow){
    .......;
    ViewRootImpl root;
    View panelParentView = null;
    ......;
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    
    root = new ViewRootImpl(view.getContext(),display);
    view.setLayoutParams(wparams);
    
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    try{
        root.setView(view,wparams,panelParentView);
    }catch (RuntimeException e){
        .....;
    }
}
复制代码

上面代码中,在 addView 方法中,new 了一个 ViewRootImpl 对象,而后调用 ViewRootImpl.setView() 方法。

/** * We have one child */
public void setView(View view,WindowManager.LayoutParams attrs,View panelParentView){
    synchronized(this){
        if(mView == null){
            mView = view;
            
            mAttachInfo.mDisplayState = mDisplay.getState();
            mDisplayManager.registerDisplayListener(mDisplayListener,mHandler);
            ....;
            requestLayout();
            .....;
            view.assignParent(this);
            .....;
        }
    }
    
}

复制代码

首先将传进来的 view 赋值给 mView,而后调用了 requestLayout() 方法,ViewRootImpl 不是 View 的子类,能够认为 ViewRootImpl 是这个 Activity 所对应的整个布局的根,它来执行具体的绘制开始,view.assignParent(this),就是给本身分配了 Parent,Parent 就是 ViewRootImpl 对象。

public void requestLayout(){
    .....;
    
    scheduleTraversals();
}
复制代码

最终调用了 performTraversals() 方法,performTraversals 方法内部代码不少

这里只写一下重要的部分

// 不传递参数,默认是 MATCH_PARENT
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
// window 可使用的宽度
int mWidth;
// window 可使用的高度
int mHeight;

private void performTraversals(){
    int desiredWindowWidth;
    int desireWindowHeight;
    WindowManager.LayoutParams lp = mWindowAttributes;
    
    // 获取应该给根 View 多大
    // mWidth 就是咱们窗口的大小,lp.width 始终是 MATCH_PARENT
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth,lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight,lp.height);
    
    // 告诉 根 View 多大
    performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
    // perforMeasure 内部实现很简单
    
    performLayout(lp,mWidth,mHeight);
    
    performDraw();
    
    
}
复制代码

调用 getRootMeasureSpec(mWidth,lp.width) 获得 childWidthMeasureSpec :

getRootMeasureSpec()-1.jpg

getRootMeasureSpec()-2.jpg

经过这个方法就获取了子 View 应该是多大,还有呈现的模式。而后把获得的数值,经过 performMeasure() 方法设置 view 大小。performMeasure 方法:

private void performMeasure(int childWidthMeasureSpec,int childHeightMeasureSpec){
   .....;
    // 这里的 mView 就是 PhoneWindow 中的 DecorView
   mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
复制代码

经过这一步:mView.measure(childWidthMeasureSpec,childHeightMeasureSpec) 就是给 mView 肯定大小值了,也就是根 View。

这样整个 Activity 的布局对应的根 View---DecorView 的大小就肯定了。具体来看看 mView.measure():

// 测量一个 View 应该是多大的,参数是由它的父 View 提供的,widthMeasureSpec
// 包含了大小和约束条件
public final void measure(int widthMeasureSpec,int heightMeasureSpec){
    .......;
    onMeasure(widthMeasureSpec,heightMeasureSpec);
    .......;
}
复制代码

该方法又调用了onMeasure 方法:

// 仅仅是 View 里面的 onMeasure 方法,不一样的 View 子类有不一样的实现内容
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
}

// 官网文档:测量 View 的大小,这个方法经过 measure 方法来调用,View 的子类应该重写此方法根据子 View 的特色。在重写这个方法的时候,你必须调用 setMeasuredDimension(int,int) 来储存测量出来的宽度和高度。
复制代码

下面就是调用 setMeasuredDimension() 了。其中最关键的一步就是对 View 的两个成员变量进行了赋值(在 setMeasuredDimensionRaw()) 方法中实现的

// 这个方法必须在 onMeasure() 方法中被调用用来储存测量出的宽度和高度。
protected final void setMeasuredDimension(int measuredWidth,int measuredHeight){
    ......;
    // 为了方便,这里直接把 setMeasuredDimensionRaw() 方法内容写到下面了
    //给 View 的成员变量赋值
    mMeasuredWidth = measureWidth;
    mMeasuredHeight = measuredHeight;
    
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    
}

复制代码

好了到此 View 中的 measure 方法也介绍完毕了。是否是以为 onMeasure 方法里面很简单啊!这你就错了,不要忘了咱们分析的 onMeasure 方法是分析的类 View 中的。里面注释明确写明了 View 的子类必须根据自身需求来重写 onMeasure 方法 。咱们上面分析的只是一个过程。不要忘了咱们的根 View 是 DecorView,而 DecorView 的父类又是 FrameLayout。能够到这两个具体的对象中看看他们的 onMeasure。感兴趣的能够看一下源代码,其实分析到这里就能够得出结论了(这个结论仅仅是 measure 这一部分的结论) 后面再从 Activity 的启动一块串联起来!

Measure 结论

Activity 有一个祖先 View 即 DecorView,经过前面的分析 DecorView 的大小是由 ViewRootImpl 中给赋值的,咱们能够认为 ViewRootImpl 是全部 View 的根,咱们知道咱们布局是呈现树的结构。我这里有一个比喻:ViewRootImpl 是这颗树的根,是埋在地下面的,DecorView 是树的主干是在地上面的,ViewGroup 是枝干,单个的 View (相似于 TextView、Button 这种)是树叶。DecorView 这个主干上长出许多枝干,这里咱们的这棵树有两个重要的枝干(或者一个),这个须要根据树的种类样式来决定。这两个重要的枝干就是:Title 和 mContentParent,或者只有 mContentParent。而后这个两个枝干又会生长出许多的枝干,枝干上面生长树叶。

measure() 方法是由其父 View 来调用执行。从根上追溯,DecorView 的大小是由树根(ViewRootImpl)来赋值的,而后枝干又是由 DecorView 来调用的 measure 的。一层层的调用。

举个简单栗子:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:id="@+id/ll_parent"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <RelativeLayout
    	android:id="@+id/rl_parent_1"
        android:layout_width="match_parent"
        android:layout_height="200dp">
        <TextView
            android:id="@+id/tv_child_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/view"/>
        <TextView
            android:layout_below="@id/tv_child_2"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="@string/activity_cycle"
            />
    </RelativeLayout>
    <LinearLayout
    	android:id="@+id/ll_parent_1"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <EditText
        	android:id="@+id/et_child_1"
            android:inputType="text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:hint="@string/progress_bar"/>
    </LinearLayout>
</LinearLayout>
复制代码

咱们这里有这样一棵树,上面的布局是咱们这颗树的 mContentParent 。首先 ViewRootImpl 会给 DecorView 大小赋值(具体大小是由 generateLayout 的时候肯定的)。而后 DecorView 会调用 mContentParent 的 measure ,传入 DecorView 容许它的大小。固然具体的大小是由 measure 传入的参数和 mContentParent 共同决定的(具体细节下面介绍)而后 mContentParent 再调用 ll_parent.measure() 给它传入 mContentParent 所容许它的大小。这个时候就会激活 ll_parent 的 onMeasure 方法,在 ll_parent 的 onMeasure 方法里面确定会调用 rl_parent_1.measure 方法,而后激活 rl_parent_1 的 onMeasure 方法,在 onMeasure 方法里面调用 tv_child_1.measure ,tv_child_1 没有孩子了,直接设置本身的大小。而后再一层层的向父布局返回去。大致就是这样的一个过程。

固然 measure(int widthMeasureSpec,int heightMeasureSpec) 这里的参数包含了,子元素的大小的属性和容许子元素的大小。具体能够看 MeasureSpec 类,很简单。

到此每一个布局就知道本身的大小了。而后开始执行 ViewRootImpl.performLayout() 方法了

Layout()

其实这个和 measure() 方法类似。都是在父控件中调用 layout() 方法,而后在 layout 方法中会调用 onLayout 方法。和 measure 不一样的是 onLayout 方法是给本身的子 View 来布局的,若是不是 ViewGroup 就不须要重写 onLayout 方法了。measure 的宽度和高度是该控件指望的尺寸,真正在屏幕上的位置和大小是由 layout 来决定的。接下来就是递归调用他们的 onLayout 方法。

接下来就是开始 Drawa 了,介绍到这里整个过程算是完成了。

参考:www.cnblogs.com/kross/p/402…

我的水平有限,若有纰漏欢迎指正!

相关文章
相关标签/搜索