这篇文章主要是配合源码简单的介绍一下,程序的加载过程,Activity 中布局的加载过程,能够大体的了解整个过程。不过过度的追究细节,因为里面任何一个细节可能都够你研究一段时间的!先了解掌握大体过程,再慢慢来!
开始启动
我们都知道,Activity 是有生命周期的,onCreate()、onStart() 、onResume 等等那么这些方法是如何调用的呢?它不会平白无故的自己调用。其实当我们开启 APP 的时候会创建一个叫做 ActivityThread 的类,我们可以认为这个类是主类,就和 Java 程序中的启动类一样,ActivityThread 类有一个 main 函数,这个函数就是我们的程序入口!
main.jpg
相信看到这个,大家都会恍然大悟了,这就是我们学习 Java 的时候的 main 方法,认为是程序的入口。可以看到方法里面对一个 Looper 对象进行了初始化,Looper.prepareMainLooper() 通过这个方法就给当前的线程初始化了 Looper,这个 Looper 成了 Application 的 main looper,这也是为什么我们在主线程中不用自己再 Looper.prepare 的原因。可以认为任何在主线程的操作都会发送到这个 Looper 对应的 Handler 中去。很容易可以找到 Looper 对应的 Handler,其实就是 ActivityThread 的一个内部类,继承了 Handler
handler-1.jpg
handler-2.jpg
这个就是 ActivityThread 中 Handler 的部分代码实现,然后看到 Handler 里面的这段代码
public void handlerMessage(Message msg){
.....; switch(msg.what){ case LAUNCH_ACTIVITY:
.....; // r 是在 msg 里面拿到的对象 msg.obj
handlerLaunchActivity(r,null,"LAUNCH_ACTIVITY");
.....;
}
}下面再来看 handlerLaunchActivity() 这个方法:
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 方法,这里省略了。
结论:在 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 方法了。
我们都知道,每一个 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() 小结:
一个 Activity 对应了一个 Window,这个 Window 的实现对象是 PhoneWindow,一对一的关系
PhoneWindow 管理了整个 Activity 页面内容,不包括系统状态栏 ,PhoneWindow 是和应用的某个页面关联的。
PhoneWindow 包含 ActionBar 和 内容。setContentViwe 方法是用来设置内容的,setTitle 是用来操作 Title 的。Window 中定义的 requestFeature() 等方法,很多与 ActionBar 属性相关的设置。这些方法都是公共方法,是为了方便我们调用的。
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_PARENTfinal 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() 方法了
作者:sydMobile
链接:https://www.jianshu.com/p/8b496181a7d7
共同学习,写下你的评论
评论加载中...
作者其他优质文章





