Android应用程序窗口View的创建过程
View类是Android中非常重要的一个类.view是应用程序界面的直观体现,我们看到的应用程序界面就可以看作是View(视图)组成的.
那么我们应用程序的界面是怎么创建的呢,也就是应用程序的View是什么时候创建的?
在android中与界面直接相关的就是Activity了.
我们平时在Activity的onCreate()函数中,通过调用它的setContentView()函数,将我们应用程序的界面资源设置进去.然后运行程序就可以看到我们布局文件里描述的界面了.
从我们调用setContentView()函数将界面资源设置进去,到运行完成界面完全显示出来,其中经过了很多过程.
这里我主要是通过源码来分析一下其中最终界面到底是什么样的View?然后分析一下View的measure,layout,draw过程.
因为我们设置界面是setContentView()中设置的,所以就从该函数开始来分析.
在
分析点击android桌面app图标启动应用程序的过程一文中我们知道Activity的onCreate()函数最先被调用.第五十步
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
privateActivityperformLaunchActivity(ActivityClientRecordr,IntentcustomIntent){
....
Activityactivity=null;
try{
java.lang.ClassLoadercl=r.packageInfo.getClassLoader();
activity=mInstrumentation.newActivity(
cl,component.getClassName(),r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
if(r.state!=null){
r.state.setClassLoader(cl);
}
}catch(Exceptione){
....
}
try{
Applicationapp=r.packageInfo.makeApplication(false,mInstrumentation);
...
if(activity!=null){
ContextappContext=createBaseContextForActivity(r,activity);
CharSequencetitle=r.activityInfo.loadLabel(appContext.getPackageManager());
Configurationconfig=newConfiguration(mCompatConfiguration);
...
activity.attach(appContext,this,getInstrumentation(),r.token,
r.ident,app,r.intent,r.activityInfo,title,r.parent,
r.embeddedID,r.lastNonConfigurationInstances,config);
if(customIntent!=null){
activity.mIntent=customIntent;
}
r.lastNonConfigurationInstances=null;
activity.mStartedActivity=false;
inttheme=r.activityInfo.getThemeResource();
if(theme!=0){
activity.setTheme(theme);
}
activity.mCalled=false;
mInstrumentation.callActivityOnCreate(activity,r.state);
.....
}
这里首先创建Activity的实例,然后mInstrumentation.callActivityOnCreate(activity,r.state)该函数最终就会调用Activity的onCreate()函数.
好了,看setContentView()函数
第一步:setContentView()
在frameworks/base/core/java/android/app/Activity.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicvoidsetContentView(intlayoutResID){
getWindow().setContentView(layoutResID);
initActionBar();
}
publicvoidsetContentView(Viewview){
getWindow().setContentView(view);
initActionBar();
}
publicvoidsetContentView(Viewview,ViewGroup.LayoutParamsparams){
getWindow().setContentView(view,params);
initActionBar();
}
Activity中setContentView()函数有三个重载函数,一般用第一个比较多,这里就按第一个继续往下分析,其实它们最终实现都一样.
首先看getWindow()函数
第二步:getWindow()
在frameworks/base/core/java/android/app/Activity.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicWindowgetWindow(){
returnmWindow;
}
Activity的成员变量mWindow是Window类型,它是什么时候被赋值的呢?
这里还是到分析点击android桌面app图标启动应用程序的过程一文中第五十步看看
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
privateActivityperformLaunchActivity(ActivityClientRecordr,IntentcustomIntent){
....
Activityactivity=null;
try{
java.lang.ClassLoadercl=r.packageInfo.getClassLoader();
activity=mInstrumentation.newActivity(
cl,component.getClassName(),r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
if(r.state!=null){
r.state.setClassLoader(cl);
}
}catch(Exceptione){
...
}
try{
Applicationapp=r.packageInfo.makeApplication(false,mInstrumentation);
...
if(activity!=null){
ContextappContext=createBaseContextForActivity(r,activity);
CharSequencetitle=r.activityInfo.loadLabel(appContext.getPackageManager());
Configurationconfig=newConfiguration(mCompatConfiguration);
...
activity.attach(appContext,this,getInstrumentation(),r.token,
r.ident,app,r.intent,r.activityInfo,title,r.parent,
r.embeddedID,r.lastNonConfigurationInstances,config);
....
}
这里创建Activity的实例后,就通过activity.attach()函数给activity内部变量赋值,所以进attach()函数里面看看
第三步:attach()
在frameworks/base/core/java/android/app/Activity.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
finalvoidattach(Contextcontext,ActivityThreadaThread,
Instrumentationinstr,IBindertoken,intident,
Applicationapplication,Intentintent,ActivityInfoinfo,
CharSequencetitle,Activityparent,Stringid,
NonConfigurationInstanceslastNonConfigurationInstances,
Configurationconfig){
...
mWindow=PolicyManager.makeNewWindow(this);//
....
}
mWindow=PolicyManager.makeNewWindow(this);这里就是给activity中mWindow赋值.那继续看PolicyManager.makeNewWindow(this)这个函数
第四步:makeNewWindow()
在frameworks/base/core/Java/com/android/internal/policyPolicyManager.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
//Thestaticmethodstospawnnewpolicy-specificobjects
publicstaticWindowmakeNewWindow(Contextcontext){
returnsPolicy.makeNewWindow(context);
}
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicfinalclassPolicyManager{
privatestaticfinalStringPOLICY_IMPL_CLASS_NAME=
"com.android.internal.policy.impl.Policy";
privatestaticfinalIPolicysPolicy;
static{
//Pullintheactualimplementationofthepolicyatrun-time
try{
ClasspolicyClass=Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy=(IPolicy)policyClass.newInstance();
}catch(ClassNotFoundExceptionex){
....
}
这里继续调用sPolicy.makeNewWindow(context);由上面代码可以知道这里的sPolicy其实是Policy类型
第五步:makeNewWindow()
在frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassPolicyimplementsIPolicy{
.
publicWindowmakeNewWindow(Contextcontext){
returnnewPhoneWindow(context);
}
这里直接newPhoneWindow(context)返回,可知PhoneWindow类是Window类的子类,进入PhoneWindow类看看
第六步:PhoneWindow()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassPhoneWindowextendsWindowimplementsMenuBuilder.Callback{
.
publicPhoneWindow(Contextcontext){
super(context);
mLayoutInflater=LayoutInflater.from(context);
}
来看一下PhoneWindow类的构造函数,首先是调用了其父类(Window)的构造函数,然后创建了一个LayoutInflater对象mLayoutInflater,这个对象根据它的名字大概可以知道它是渲染布局资源的
去Window类的构造函数看看
第七步:Window()
在frameworks/base/core/java/android/view/Window.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicWindow(Contextcontext){
mContext=context;
}
回到第二步中,这是我们知道getWindow()返回其实是一个PhoneWindow对象,即Activity的成员变量mWindow是PhoneWindow类型.
然后回到第一步中,那么接着其实是调用PhoneWindow.setContentView()了
第八步:setContentView()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicvoidsetContentView(intlayoutResID){
if(mContentParent==null){
installDecor();
}else{
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID,mContentParent);
finalCallbackcb=getCallback();
if(cb!=null&&!isDestroyed()){
cb.onContentChanged();
}
}
PhoneWindow的成员变量mContentParent是ViewGroup类型,第一次进来为null,所以调用installDecor()函数,那我们首先看看该函数
第九步:installDecor()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
privatevoidinstallDecor(){
if(mDecor==null){
mDecor=generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
...
}
if(mContentParent==null){
mContentParent=generateLayout(mDecor);
//SetupdecorpartofUItoignorefitsSystemWindowsifappropriate.
mDecor.makeOptionalFitsSystemWindows();
mTitleView=(TextView)findViewById(com.android.internal.R.id.title);
if(mTitleView!=null){
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if((getLocalFeatures()&(1< ViewtitleContainer=findViewById(com.android.internal.R.id.title_container);
if(titleContainer!=null){
titleContainer.setVisibility(View.GONE);
}else{
mTitleView.setVisibility(View.GONE);
}
if(mContentParentinstanceofFrameLayout){
((FrameLayout)mContentParent).setForeground(null);
}
}else{
mTitleView.setText(mTitle);
}
}else{
mActionBar=(ActionBarView)findViewById(com.android.internal.R.id.action_bar);
if(mActionBar!=null){
mActionBar.setWindowCallback(getCallback());
if(mActionBar.getTitle()==null){
mActionBar.setWindowTitle(mTitle);
}
finalintlocalFeatures=getLocalFeatures();
if((localFeatures&(1< mActionBar.initProgress();
}
if((localFeatures&(1< mActionBar.initIndeterminateProgress();
}
booleansplitActionBar=false;
finalbooleansplitWhenNarrow=
(mUiOptions&ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW)!=0;
if(splitWhenNarrow){
splitActionBar=getContext().getResources().getBoolean(
com.android.internal.R.bool.split_action_bar_is_narrow);
}else{
splitActionBar=getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowSplitActionBar,false);
}
finalActionBarContainersplitView=(ActionBarContainer)findViewById(
com.android.internal.R.id.split_action_bar);
if(splitView!=null){
mActionBar.setSplitView(splitView);
mActionBar.setSplitActionBar(splitActionBar);
mActionBar.setSplitWhenNarrow(splitWhenNarrow);
finalActionBarContextViewcab=(ActionBarContextView)findViewById(
com.android.internal.R.id.action_context_bar);
cab.setSplitView(splitView);
cab.setSplitActionBar(splitActionBar);
cab.setSplitWhenNarrow(splitWhenNarrow);
}elseif(splitActwww.tt951.comionBar){
Log.e(TAG,"Requestedsplitactionbarwith"+
"incompatiblewindowdecor!Ignoringrequest.");
}
.
}
}
}
}
PhoneWindow的成员变量mDecor是DecorView类型,看一下DecorView的类图结构
由上图可知,DecorView也是View,其实这个DecorView也是应用程序窗口根View.
第一次进来mDecor为null,所以会执行下面:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
mDecor=generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);//焦点先交给mDecor的子view处理,如果子View没有处理自己再处理
首先来看一下generateDecor()这个函数
第十步:generateDecor()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
protectedDecorViewgenerateDecor(){
returnnewDecorView(getContext(),-1);
}
这里就直接新建了DecorView实例
回到第九步中,mContentParent==null成立,所以执行:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
mContentParent=generateLayout(mDecor);
进入generateLayout(mDecor)函数看看,传进去的参数就是第十步创建的DecorView对象
第十一步:generateLayout()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
protectedViewGroupgenerateLayout(DecorViewdecor){
....
mDecor.startChanging();
Viewin=mLayoutInflater.inflate(layoutResource,null);
decor.addView(in,newViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));
ViewGroupcontentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
returncontentParent;
}
这个函数内容还是挺多的,不过不难,首先收集在清单配置文件中,给该activity配置的theme属性值,然后根据这些属性值去加载系统的布局文件,设置这些theme属性值也可以在代码中设置,不过要setContentView()之前,不过就不起作用了.
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
else{
//Embedded,sonodecorationisneeded.
layoutResource=com.android.internal.R.layout.screen_simple;
//System.out.println("Simple!");
}
这里假设需要加载的布局文件id是com.android.internal.R.layout.screen_simple,系统的布局文件在frameworks/base/core/res/res/layout/目录下,
进去screen_simple.xml看一下
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
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"/>
有一个id为content的控件,这个很关键.
在确定好加载哪个系统布局文件后,接下来:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
Viewin=mLayoutInflater.inflate(layoutResource,null);
decor.addView(in,newViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));
mLayoutInflater在第六步创建的,这里将一个布局文件渲染成一个View,这个view的具体类型就是布局文件的根节点的对象类型,像上面的screen_simple.xml它的根节点就是LinearLayout.
接着将这个渲染成的LinearLayout添加到decor中,因为DecorView是ViewGroup类型,能添加子view.
接着往下看:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
ViewGroupcontentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT);
这里ID_ANDROID_CONTENT为com.android.internal.R.id.content,我们进到findViewById()函数进去看看
第十二步:findViewById()
在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicViewfindViewById(intid){
returngetDecorView().findViewById(id);
}
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicfinalViewgetDecorView(){
if(mDecor==null){
installDecor();
}
returnmDecor;
}
这个函数就去查找decorView中id为com.android.internal.R.id.content的子view,在上面的布局文件中就是一个FrameLayout了,所以说系统布局文件要有一个id为content的控件.
好了,回到第十一步,找到了这控件后就返回到第九步中,将它赋值给了mContentParent.
现在整理一下思路,mDecor赋值了,mContentParent也赋值了,它们的关系是:
回到第九步,继续往下分析:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
mTitleView=(TextView)findViewById(com.android.internal.R.id.title);
if(mTitleView!=null){
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if((getLocalFeatures()&(1< ViewtitleContainer=findViewById(com.android.internal.R.id.title_container);
if(titleContainer!=null){
titleContainer.setVisibility(View.GONE);
}else{
mTitleView.setVisibility(View.GONE);
}
if(mContentParentinstanceofFrameLayout){
((FrameLayout)mContentParent).setForeground(null);
}
}else{
mTitleView.setText(mTitle);
}
}else{
mActionBar=(ActionBarView)findViewById(com.android.internal.R.id.action_bar);
if(mActionBar!=null){
mActionBar.setWindowCallback(getCallback());
if(mActionBar.getTitle()==null){
mActionBar.setWindowTitle(mTitle);
}
finalintlocalFeatures=getLocalFeatures();
if((localFeatures&(1< mActionBar.initProgress();
}
if((localFeatures&(1< mActionBar.initIndeterminateProgress();
}
booleansplitActionBar=false;
finalbooleansplitWhenNarrow=
(mUiOptions&ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW)!=0;
if(splitWhenNarrow){
splitActionBar=getContext().getResources().getBoolean(
com.android.internal.R.bool.split_action_bar_is_narrow);
}else{
splitActionBar=getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowSplitActionBar,false);
}
finalActionBarContainersplitView=(ActionBarContainer)findViewById(
com.android.internal.R.id.split_action_bar);
if(splitView!=null){
mActionBar.setSplitView(splitView);
mActionBar.setSplitActionBar(splitActionBar);
mActionBar.setSplitWhenNarrow(splitWhenNarrow);
finalActionBarContextViewcab=(ActionBarContextView)findViewById(
com.android.internal.R.id.action_context_bar);
cab.setSplitView(splitView);
cab.setSplitActionBar(splitActionBar);
cab.setSplitWhenNarrow(splitWhenNarrow);
}elseif(splitActiowww.baiyuewang.netnBar){
Log.e(TAG,"Requestedsplitactionbarwith"+
"incompatiblewindowdecor!Ignoringrequest.");
}
PhoneWindow类的成员函数installDecor()还会检查前面加载的窗口布局文件是否包含有一个id值为“title”的TextView控件。如果包含有的话,就会将它保存在PhoneWindow类的成员变量mTitleView中,用来描述当前应用程序窗口的标题栏。但是,如果当前应用程序窗口是没有标题栏的,即它的Feature位FEATURE_NO_TITLE的值等于1,那么PhoneWindow类的成员函数installDecor()就需要将前面得到的标题栏隐藏起来。注意,PhoneWindow类的成员变量mTitleView所描述的标题栏有可能是包含在一个id值为“title_container”的容器里面的,在这种情况下,就需要隐藏该标题栏容器。另一方面,如果当前应用程序窗口是设置有标题栏的,那么PhoneWindow类的成员函数installDecor就会设置它的标题栏文字。应用程序窗口的标题栏文字保存在PhoneWindow类的成员变量mTitle中,我们可以调用PhoneWindow类的成员函数setTitle来设置.如果没有id值为“title”的TextView控件,就去检查前面加载的窗口布局文件是否包含有一个id值为“action_bar”的ActionBarView控件.下面就不分析了.
回到第八步setContentView()函数里,
往下接着到
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
mLayoutInflater.inflate(layoutResID,mContentParent);
这里的layoutResID,是在activity的onCreate()方法里面,通过setContentView()设置的应用程序的窗口布局资源id.
这里mLayoutInflater.inflate()方法,将应用程序的窗口布局资源渲染成一个view,然后添加到mContentParent这个ViewGroup中.
|
|