首先了解一下安卓系统窗口的类型,系统定义了三种窗口类型,包括:
1.应用窗口。这类窗口一般一个Activity对应一个应用窗口。
2.子窗口。这种类型的窗口必须要有一个父窗口,如PopupWindow即属于这类窗口。
3.系统窗口。如Toast即属于这类窗口。
每一类窗口都有一代表其层次的常量,一般这个常量越大表示层的位置越靠上,可以知道 系统窗口这个常量最大,子窗口次之,应用窗口最小。
接下来说一下应用窗口的整个创建过程,归纳一下可以分为一下几大步:
1.首先创建一个Activity并且配置这个Activity。由于一个应用窗口一般对应一个Activity,所以创建应用窗口之前我们需要首先创建一个Activity.
先利用ClassLoader从程序文件中装载指定的Activity对应的Class文件,如下:
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
......
} catch (Exception e) {
......
}
然后构造好这个Activity后调用attach()函数来设置内部变量,并创建一个Window对象,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,
Object lastNonConfigurationInstance,
HashMap<String,Object> lastNonConfigurationChildInstances,
Configuration config) {
attachBaseContext(context);
mWindow = PolicyManager.makeNewWindow(this); //为这个Activity创建一个Window对象
mWindow.setCallback(this); //设置该window的Callback接口为当前的Activity对象
2.
配置Window对象。上面第一步已经创建并配置好Activity,并且为Activity创建了一个Window对象,接下来就是配置这个Window对象。
我们知道每个Window内部都有一个WindowManager对象,所以我们需要给Window内部的mWindowManager变量赋值,如下:
mWindowManager=mWindow.getWindowManager();
3.给窗口添加View.配置好Activity和Window后,我们就要给这个Window加上View了。在PerformLaunchActivity()内部调用callActivityOnCreate()开始,并最终会调用到Activity的onCreate()函数,我们知道onCreate()函数里面有一个setContentView(),我们平常一般通过这个函数来设置Activity的View,其实这个setContentView内部是调用到其对应的Window对象的setContentView()函数:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
然后我们就来看看Window对象的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();
}
}
首先是installDecor()为Window安装一个窗口修饰,关于窗口修饰,其实就是一个基础窗口,比如标题栏(ActionBar),我们知道Actionbar有很多不同的主题,所以也就是这个窗口修饰有很多不同主题,一般来说一个窗口修饰就是为我们创建一个标题栏(当然也有不要标题栏的主题),然后下面一个空的FrameLayout,之后我们定义的Layout.xml就是放在这个FrameLayout里面,称之为窗口内容。
然后通过inflate()方法来把我们的layout.xml添加到窗口修饰中,当做窗口内容。
最后这个cb即所在的Activity(前面activity的attach()方法里设置了Window的回调),cb.onContentChanged()通知应用程序窗口内容发生了改变,这种回调实现了Window里的内容发生了改变,可以调用Activity里的方法来处理这种改变,也就是起到通知Activity的作用。
4.Window的视图配置完后,就是把创建窗口告知Wms(WindowManagerService),Wms负责把窗口显示在屏幕上。
当Activity各方面都准备好后,就通知Ams(ActivityManagerService),Ams负责Activity的管理,当Ams确定Activity都准备好没问题后,一样的会通过IPC跨进程的方式调到客户端IBinder对象ApplicationThread,ApplicationThread中通过Hander发消息到主线程ActivityThread中调用handleResumeActivity()中调用Activity的onResume()方法,然后在其中再调用makeVisiable(),最后makeVisiable()就会调用WindowManger的addView()来让View和Window建立联系:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager(); // 获取WindowManager对象
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE); //显示
}
从上面可以看出先获取WindowManager对象,然后wm.addView()添加窗口,然后显示。
那么这个addView()里面做了些什么呢?
①首先创建一个ViewRoot对象(每个窗口都有一个ViewRoot对象(实现类ViewRootImpl),因为Window的添加显示经常要与Wms打交道,即进程间通信,必然要用到IPC调用,所以ViewRoot必不可少[ViewRoot里有个ViewRootHandler,可以完成线程间通信,而ViewRoot内部又有Binder子类W类,可以完成进程间通信])
②调用ViewRoot的setView()方法完成添加工作。
setView()方法有三个参数,分别是:1.View即要显示的整个界面 2.LayoutParams窗口的参数,如大小位置等 3.panelParentView
setView()里做的事:
1.给重要变量赋值
2.调用requestLayout().发出界面重绘请求,这个方法只是给UI线程发一个异步消息,通知它下一个消息处理是界面重绘,从而让该窗口在相应任何其他用户消息之前首先变得可见。
3.调用sWindowSession.add().这一步也是重中之重,这一步实现了跨进程把窗口添加到Wms中。这个sWindowSession对象是Viewroot中的一个Binder引用,即对应Wms上的一个Session对象,这样就可以在ViewRoot里调用Wms上的方法add()来实现添加窗口了。(关于Binder架构不了解的可以去看看)
这样,应用窗口的创建和显示工作基本完成了。可以归纳为以上四大步。