您的位置:首页 > 移动开发 > Android开发

我眼中的Window创建/添加/删除/更新过程

2016-09-03 16:15 357 查看
        在Android中和我们打交道最多的就是Activity,因为我们会频繁的与界面进行交互,而Activity内部关于界面方面的操作都是由Window来实现的,因此我们有必要了解下Window的实现机制了;网上有挺多关于Window创建/添加/删除/更新方面的源码分析了,我这篇博客不会去大篇幅的贴出代码分析那些源码机制,取而代之的是以语言描述的方式展现出Window机制中的一些知识点;

        个人认为想要学习Window机制的实现原理,需要弄懂以下几个问题,这篇文章主要讲解的是应用级Window--->Activity的相关内容:

       
(1):Android中Window的分类有哪些呢?

       
(2):与Window实现机制有关的是哪些类或者接口呢?

       
(3):一个Window是怎么创建出来的呢?

       
(4):添加Window的过程中做了些什么事情?

       
(5):删除Window的时候发生了什么?

       
(6):更新Window的时候发生了什么?

        接下来,我一个一个的解答上面的问题:

       
首先是Android中Window的分类

        Android中的Window分为三类:

        <1>:应用Window,我们通常见到的就是Activity了;

        <2>:子Window,比如Dialog;

        <3>:系统Window,比如Toast;

       
那么与Window机制实现有关的类和接口有哪些呢?

        ViewManager(接口)、WindowManager(接口)、WindowManagerImpl(final类)、WindowManagerGlobal(final类)

        具体他们的关系见下图:

                                              


       
接着便是一个Window是怎么创建出来的呢?

        因为Window是对Activity操作的真正执行者嘛,我们平常调用的setContentView实际上调用的也是Window的setContentView,那么很自然找Window是怎么创建的就该先了解下Activity的创建流程了,在这篇文章中,我有讲到过Activity的启动过程最终会执行到ApplicationThread的scheduleLaunchActivity方法上面,在这个方法的最后会发送一条消息来让H这个Handler进行处理,具体消息中携带的标志信息是LAUNCH_ACTIVITY,真正的处理就应该是在H这个Handler里面的handleMessage了,在他里面找到case为LAUNCH_ACTIVITY的语句块,执行handleLaunchActivity方法,而在handleLaunchActivity里面是会通过performLaunchActivity创建一个Activity对象出来的,具体Activity是怎么被创建出来的呢?我们稍微看下ActivityThread$performLaunchActivity源码就知道了:

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
        很明显的看出来是利用Instrumentation对象通过反射创建的;
        接着会创建一个Application对象出来,通过LoadedApk的makeApplication方法,这个方法里面创建Application的方式实际上也是通过Instrumentation对象反射创建的,在makeApplication中创建完Application之后会通过Instrumentation的callApplicationOnCreate方法回调Application的onCreate方法,这个就是我们Application的生命周期方法啦;

        现在我们仅仅只是创建了Activity对象了,但是我们知道Android程序的运行是需要上下文环境支持的,因此继续查看ActivityThread$performLaunchActivity源码你会看到有关创建应用上下文的代码:

if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config);
}
        注意我仅仅截取了与我们分析有关的源码,在这段源码中首先就是创建一个ContextImpl对象了,至于为什么是ContextImpl类型,你查看createBaseContextForActivity方法就知道了,有了ContextImpl对象之后,会通过Activity的attach方法,将ContextImpl与我们的Activity绑定起来;
        到此,我们还没看到有关Window的任何相关代码,只看到了Activity的有关部分,可想而知,在Activity的attach里面必定存在有关Window的部分,简单把有用的源码copy如下:

        Activity$attach

mWindow = PolicyManager.makeNewWindow(this);
............
mWindow.setCallback(this);
.............
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
............
mWindowManager = mWindow.getWindowManager();
............

        可以看到首先就是通过PolicyManager的makeNewWindow方法创建了一个PhoneWindow对象出来,具体makeNewWindow的话是通过Policy类的makeNewWindow方法new出来一个PhoneWindow对象的;接着便是为创建的PhoneWindow对象设置回调;随后会通过setWindowManager方法为PhoneWindow对象设置WindowManager对象,具体WindowManager是怎么创建的就是通过setWindowManager的第一个参数(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)创建的,那么这时候你肯定就想什么时候会创建Context.WINDOW_SERVICE类型的系统服务呢?其实就在我们创建ContextImpl对象的时候啦,上面已经讲到说在创建Activity的时候会绑定ContextImpl对象,即ContextImpl对象会在Activity之前创建的,查看ContextImpl源码,你会发现存在一个static类型的静态块代码区域,这个区域里面找到与Context.WINDOW_SERVICE有关的代码如下:

registerService(WINDOW_SERVICE, new ServiceFetcher() {
Display mDefaultDisplay;
public Object getService(ContextImpl ctx) {
Display display = ctx.mDisplay;
if (display == null) {
if (mDefaultDisplay == null) {
DisplayManager dm = (DisplayManager)ctx.getOuterContext().
getSystemService(Context.DISPLAY_SERVICE);
mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
}
display = mDefaultDisplay;
}
return new WindowManagerImpl(display);
}});
        可以看到首先创建了一个Display对象出来,随后调用了WindowManagerImpl构造函数,创建了一个WindowManagerImpl对象出来;
        如果你查看setWindowManager源码的话,你会发现他最后会执行我们刚刚创建的WindowManagerImpl对象的createLocalWindowManager语句,并且返回这个创建的值,查看createLocalWindowManager的源码会发现其实他也是创建一个WindowManagerImpl对象出来的,最初我对这个地方一直很费解,为什么要分两步来创建WindowManagerImpl对象呢?后来想明白是这样子的,首先创建一个WindowManagerImpl对象,此时的WindowManagerImpl是没有和具体的Window发生关联的,随后创建出来的WindowManagerImpl会和Window发生关联操作;

        最后呢,将创建的WindowManager对象赋给我们的Activity类中属性便可以啦,因为在Activity的别的地方可能会用到WindowManager嘛;

        这样子,Activity中的Window就创建成功啦,我们来做个小结:

        (1):首先是创建一个Activity对象出来;

        (2):接着是创建一个ContextImpl对象出来,ContextImpl是实现了Context上下文接口的,在创建ContextImpl的同时,会在他的static语句块中创建一个只包含有Display对象的WindowManager对象出来;

        (3):通过Activity的attach函数,将Activity与创建的ContextImpl对象绑定到一起,在attach里面会创建PhoneWindow对象,同时为其设置回调接口;

        (4):通过setWindowManager为当前创建的PhoneWindow对象绑定WindowManager对象,并且返回这个WindowManager对象对象,将这个WindowManager赋给Activity类中的变量;

       在创建了WindowManager对象之后,如果我们想要往Window里面添加一个View的话,需要调用WindowManager的addView方法,而WindowManager是接口,WindowManagerImpl是对WindowManager的实现,如果你查看WindowManagerImpl的实现的话,会发现它里面的所有方法都是通过WindowManagerGlobal对象实现的,因此像Window里面添加View实际上执行的是WindowManagerGlobal的addView,除了addView外,删除View和更新View也是在WindowManagerGlobal中实现的;

        在WindowManagerGlobal里的addView方法里面的伪代码实现如下:

//进行一些参数检查,不合法的话则抛出异常
............
root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
        我们可以认为addView里面做了一下几件事情:
        (1):进行参数合法性的检查,不合法的话抛出相应的异常;

        (2):创建一个ViewRootImpl对象出来,为什么要创建一个ViewRootImpl对象呢?这么说吧,我们的Window实际上是一个抽象概念,他需要有View的存在而存在,而ViewRootImpl是Window与View之间进行交互的中介吧,了解View绘制过程的都清楚,View绘制的开始方法其实就是ViewRootImp里面的performTraversals,在创建ViewRootImp里面要注意下面这句代码:

mWindowSession = WindowManagerGlobal.getWindowSession();
他会为我们创建一个IWindowSession对象,这个IWindowSession对象具体来讲的话是通过WindowManagerGlobal的getWindowSession创建的,这段源码不算长,我们来看看:

public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
imm.getClient(), imm.getInputContext());
float animatorScale = windowManager.getAnimationScale(2);
ValueAnimator.setDurationScale(animatorScale);
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
第6行看到通过getWindowManagerService创建了一个WindowManagerService对象出来,查看WindowManagerService源码会发现实际上是通过IPC的方式创建出来的,如下:
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
}
return sWindowManagerService;
}
}
有了WindowManagerService对象之后,会利用该对象调用openSession方法创建出来一个IWindowSession对象,有了这个Session对象之后,随后的添加操作实际上就是ViewRootImpl通过这个Session来进行添加的;
        (3):继续回到WindowManagerGlobal的addView方法,在创建完ViewRootImpl之后,为View设置布局参数,接下来会执行3个add操作,我们来看看这三个对象的定义:

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
就是三个List嘛,其中mViews存储的是所有Window所对应的View,mRoots存储的是所有Window所对应的ViewRootImpl,mParams存储的是所有Window对应的布局参数,最后在所有的添加操作做完之后,执行了ViewRootImpl的setView方法,这里也印证了我们前面提到的ViewRootImpl其实上是Window与View之间交互的桥梁这个观点;
        (4):ViewRootImpl的setView方法比较长,我们仅说下和Window添加View有关联的部分,首先调用requestLayout从根View也就是DecorView开始进行重绘,随后会利用我们在创建ViewRootImpl的Session对象来将当前View添加到窗体中,调用的是addToDisplay方法,这个方法的内部会利用我们在创建ViewRootImpl的时候创建的WindowManagerService对象,调用WindowManagerService对象的addWindow方法;

        到此,一个View就被添加到Window上面啦,我们来对整个添加过程做个小结:

        <1>:首先利用之前创建Window的过程中创建的WindowManager对象,调用它的addView方法,实际上调用的是WindowManagerImpl的addView方法,而WindowManagerImpl里面是由WindowManagerGlobal实现的,也就是调用了WindowManagerGlobal的addView方法;

        <2>:在addView中首先会进行参数合法性的检查,不合法的话抛出相应的异常信息;

        <3>:接着会创建一个ViewRootImpl对象出来,他是Window与View进行交互的桥梁,在创建ViewRootImpl的时候会获取到WindowManagerService对象,同时利用WindowManagerService对象创建一个Session对象,有了Session对象之后,后面的添加操作就可以通过该Session进行了,相当于建立了一个通话过程一样了;

        <4>:接着便是将当前View、ViewRootImpl、布局参数LayoutParams添加到各自队列里面的操作了;

        <5>:最后调用ViewRootImpl的setView方法将View添加到Window上面,其实在setView方法里面真正的添加操作是通过Session来进行传递由WindowMangerService的addWindow方法实现的;

       接着我们看看删除Window的操作是什么样子的了?

        和添加Window的过程有点类似,其实本质上整个过程还是会交割给WindowManager去删除的,而WindowManagerImpl实现了WindowManager接口,因此任务相当于给了WindowManagerImpl,而WindowManagerImpl里面所有的操作都是通过桥接模式转交给WindowManagerGlobal来完成的,在WindowManagerImpl里面是存在两个与删除Window有关的方法的,一个是removeView一个是removeViewImmediate,两者最终都会执行WindowManagerGlobal的removeView,但是是有区别的,具体区别查看源码注释:

/**
* Special variation of {@link #removeView} that immediately invokes
* the given view hierarchy's {@link View#onDetachedFromWindow()
* View.onDetachedFromWindow()} methods before returning.  This is not
* for normal applications; using it correctly requires great care.
*
* @param view The view to be removed.
*/
public void removeViewImmediate(View view);
        意思是removeViewImmediate是removeView的特殊版本,使用它会在removeView返回之前触发View树中View的onDetachedFromWindow和onDetachedFromWindow方法,一般情况下我们的应用程序是不会用这个方法删除Window的,使用的时候要格外的注意;

        因为上面的removeView和removeViewImmediate都是执行的WindowManagerGlobal的removeView,只不过就是第二个boolean参数值不同而已,因此只需要查看WindowManagerGlobal的removeView方法就可以了:

        代码不长,我们看下源码:

public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}

synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}

throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
        第7行会通过findViewLocked方法获取到要删除View的index值,这个获取过程是通过数组遍历实现的,接着就调用removeViewLocked来删除这个View,那必须要知道removeViewLocked干了些什么啦,代码也不长,我贴出来了:

private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();

if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
        首先会获取到要删除View的index对应的ViewRootImpl,此时你应该就有预感了,既然你添加View到Window的时候用到了ViewRootImpl作为桥梁,那么在删除的时候,你就应该也用到ViewRootImpl作为桥梁了,后面你会发现确实是这样子的,有了ViewRootImpl之后就调用他的die方法啦,是这样吧;

        在die方法里面会根据我们boolean类型的参数immediate来判断到底是直接执行doDie()方法还是通过Handler发送一条带有MSG_DIE标志的消息,至于immediate参数值是什么时候传入的在一开始我们已经讲了WindowManagerImpl里面存在两个删除Window的方法removeView和removeViewImmediate,前者传入的immediate值是false,后者传入的immediate值是true,true跟false的区别其实就是同步和异步删除的区别了,但是不管用哪种方法,最终执行的都是ViewRootImpl的doDie()方法;

        这个ViewRootImpl的doDie()方法里面会做一下几件事情:

        (1):调用dispatchDetachedFromWindow方法,在dispatchDetachedFromWindow方法里面你会见到我们很熟悉的Session操作身影了,在addView就已经说过添加操作是通过Session来相当于建立通信渠道一样,由WindowManagerService来真正执行添加操作,那么这里删除操作情况也一样,也是由Session来建立通信渠道的,调用的是Session的remove方法来移除Window,而你查看Session的remove源码的话会发现实际上执行的还是WindowManagerService的removeWindow方法的,情况和addView完全一致;

        (2):最后还会执行WindowManagerGlobal的doRemoveView方法,直接查看doRemoveView源码你会发现其实就是将当前View从mViews中移出,将当前ViewRootImpl从当前mRoots移出,将当前LayoutParams从mParams移出而已了,因为你在创建的时候添加进去了嘛,删除的时候就该移出出去啊!

void doRemoveView(ViewRootImpl root) {
synchronized (mLock) {
final int index = mRoots.indexOf(root);
if (index >= 0) {
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view);
}
}
}
        酱紫的话,删除操作过程也结束啦,我们也来个小结呐:

        <1>:调用WindowManagerImpl的removeView或者removeViewImmediate来进行删除,但是后者的话我们要慎用啦,官方都这么建议了还是小心点啦,但是两者最终执行的都是WindowManagerGlobal的removeView方法;

        <2>:在WindowManagerGlobal的removeView方法里面,首先先找到我们要删除View对应的index索引值,有了这个index值之后我们便可以得到该index值对应的是哪个ViewRootImpl了,因为Window上的View的删除操作实际上还是由ViewRootImpl作为中介桥梁完成的;

        <3>:接下来的删除操作就转交给ViewRootImpl来完成,ViewRootImpl的删除实际上是通过创建ViewRootImpl的时候创建出来的Session来完成的,他好像是建立了一个通信通道一样,具体最后的删除操作是由WindowManagerService来完成的,这个过程中是有涉及到IPC通信的;

        <4>:在最后删除结束准备返回之前一定要记得将当前View从mViews列表中删除,将当前ViewRootImpl从mRoots删除,将当前LayoutParams从mParams中删除;

       最后就是在更新Window的时候发生什么啦?

        不用说,和前面一样,更新Window操作方法最终执行到的是WindowManagerGlobal的updateViewLayout里面的,这个源码也不长,我们贴出来看看:

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

view.setLayoutParams(wparams);

synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
        其实我们可以想想什么情况下我们会进行更新Window操作,就是在我们Window里面的View的LayoutParams属性发生变化的时候嘛,要是让我们实现的话,肯定就是先把新属性赋值给View,随后重绘View就可以啦,其实源码里就是这么实现的,只不过做了点封装而已了;看第11行,将新的LayoutParams参数赋值给当前View,随后通过findViewLocked方法找到当前View的索引值,利用该索引值找到其对应的ViewRootImpl对象,然后更新我们的LayoutParams列表呀,其实就是把原来的删了,把新的填进去罢了;最后呢,执行了ViewRootImpl的setLayoutParams方法,怎么样,又看到了ViewRootImpl了吧,整个添加/删除/更新操作都是拿他作为中间桥梁的,在ViewRootImpl的setLayoutParams方法里面你会看到执行了scheduleTraversals方法,这个方法就会开启我们从DecorView的视图重绘工作,接着呢,还需要更新我们的Window视图呀,具体是通过scheduleTraversals调用performTraversals方法之后,在performTraversals方法里面执行的,在performTraversals方法里面执行了ViewRootImpl的relayoutWindow方法,而在relayoutWindow里面就会执行Session的relayout方法了,很可爱,我们又见到了Session啦,下一步不用想肯定就是执行的WindowManagerService的relayoutWindow方法来更新Window啦啦!
        至此,更新操作结束啦,我们也来小结一下:

        <1>:更新操作调用的是WindowManagerGlobal的updateViewLayout方法;

        <2>:这个方法里面首先会更新我们当前View的LayoutParams属性,接着会通过当前View找到片当前View所在的index索引值,有了这个索引值我们便可以找到当前View所处的ViewRootImpl啦,随后更新我们的LayoutParams列表;

        <3>:通过ViewRootImpl来进行具体的更新操作,具体实现是首先先是更新View啦,其实上就是重绘View而已,接着便是重绘Window了,重绘Window的话,实际上是通过创建ViewRootImpl的时候创建的Session对象来完成的,而Session的话只不过是提供了一个通道而已啦,具体的实现还是通过WindowManagerService来进行的;

        好了,至此对Window的创建/添加/删除/更新操作的源码机制分析完毕啦,当然我这里主要分析的是Activity这个应用级Window的整个过程把,实际上Dialog和Toast这两种Window的话个人感觉整个过程应该比较类似吧,以后有时间了再细细查看了,只要明白Window、ViewRootImpl、View、WindowManagerImpl、WindowManagerGlobal、Session、WindowManagerService之间的关系的话,感觉能理解的更好;

       
本文只是简单的分析而已,不免会有疏漏错误之处,欢迎指正!!!!!!

    
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息