配色: 字号:
Android应用程序窗口View的创建过程
2017-01-18 | 阅:  转:  |  分享 
  
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中.

献花(0)
+1
(本文系thedust79首藏)