如何验证上一个问题

首先,说明一下运行条件

 //主题
name="AppTheme" parent="@android:style/Theme.Holo.Light.NoActionBar"//编译版本
android {compileSdkVersion 19buildToolsVersion '19.1.0'defaultConfig {applicationId "com.socks.uitestapp"minSdkVersion 15targetSdkVersion 19versionCode 1versionName "1.0"}
}dependencies {compile fileTree(include: ['*.jar'], dir: 'libs')compile 'com.android.support:appcompat-v7:19.1.0'
}//Activity代码
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}
}//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:text="Hello World!"android:textSize="20sp" />

OK,咱们的软件已经准备好了,采用的是最简单的布局,界面效果如下:

607813-9955ed18d1f64c3a.png

下面用Hierarchy看一下树状结构:

607813-b094dee5d70bb9c4.png

第一层,就是上面的DecorView,里面有一个线性布局,上面的是ViewStub,下面就是id为content的ViewGroup,是一个FrameLayout。而我们通过setContentView()设置的布局,就是TextView了。
能不能在源码里面找到源文件呢?当然可以,这个布局就是screen_simple.xml
frameworks/base/core/res/res/layout/screen_simple.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"android:orientation="vertical"><ViewStub android:id="@+id/action_mode_bar_stub"android:inflatedId="@+id/action_mode_bar"android:layout="@layout/action_mode_bar"android:layout_width="match_parent"android:layout_height="wrap_content"android:theme="?attr/actionBarTheme" /><FrameLayoutandroid: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" />
</LinearLayout>

所以,即使你不调用setContentView(),在一个空Activity上面,也是有布局的。而且肯定有一个DecorView,一个id为content的FrameLayout。
你可以采用下面的方式获取到DecorView,但是你不能获取到一个DecorView实例,只能获取到ViewGroup。
下面贴上这个图,你就可以看明白了(转自 工匠若水)

607813-68e40a644c2c8784.png

ViewGroup view = (ViewGroup) getWindow().getDecorView();

我们通过setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?

有开发经验的朋友应该知道,我们的界面元素在onResume()之后才对用户是可见的,这是为啥呢?
那我们追踪一下,onResume()是什么时候调用的,然后看看做了什么操作就OK了。
这一下,我们又要从ActivityThread开始说起了,不熟悉的快去看前一篇文章《Activity启动过程全解析》(http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287)。
话说,前文说到,我们想要开启一个Activity的时候,ActivityThread的handleLaunchActivity()会在Handler中被调用

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {//就是在这里调用了Activity.attach()呀,接着调用了Activity.onCreate()和Activity.onStart()生命周期,但是由于只是初始化了mDecor,添加了布局文件,还没有把//mDecor添加到负责UI显示的PhoneWindow中,所以这时候对用户来说,是不可见的Activity a = performLaunchActivity(r, customIntent);......if (a != null) {//这里面执行了Activity.onResume()handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed);if (!r.activity.mFinished && r.startsNotResumed) {try {r.activity.mCalled = false;//执行Activity.onPause()mInstrumentation.callActivityOnPause(r.activity);}}}
}

所以说,ActivityThread.handleLaunchActivity执行完之后,Activity的生命周期已经执行了4个(onCreate()、onStart()、onResume()、onPause())。
下面咱们终点看下handleResumeActivity()做了什么

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume) {//这个时候,Activity.onResume()已经调用了,但是现在界面还是不可见的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();//decor对用户不可见decor.setVisibility(View.INVISIBLE);ViewManager wm = a.getWindowManager();WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;//这里记住这个WindowManager.LayoutParams的type为TYPE_BASE_APPLICATION,后面介绍Window的时候会见到l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;if (a.mVisibleFromClient) {a.mWindowAdded = true;//终于被添加进WindowManager了,但是这个时候,还是不可见的wm.addView(decor, l);}if (!r.activity.mFinished && willBeVisible&& r.activity.mDecor != null && !r.hideForNow) {//在这里,执行了重要的操作!if (r.activity.mVisibleFromClient) {r.activity.makeVisible();}}}

从上面的分析中我们知道,其实在onResume()执行之后,界面还是不可见的,当我们执行了Activity.makeVisible()方法之后,界面才对我们是可见的

if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);

OK,其实讲到了这里,关于Activity中的界面显示应该算是告一段落了,我们知道了Activity的生命周期方法的调用时机,还知道了一个最简单的Activity的界面的构成,并了解了Window、PhoneWindow、DecorView、WindowManager的存在。
但是我还是感觉不过瘾,因为上面只是在流程上大体上过了一遍,对于Window、WindowManager的深入了解还不够,所以下面就开始讲解Window、WindowManager等相关类的稍微高级点的只是。
前面看累了的朋友,可以上个厕所,泡个咖啡,休息下继续往下看。

ViewManager、WindowManager、WindowManagerImpl、WindowManagerGlobal到底都是些什么玩意?

WindowManager其实是一个接口,和Window一样,起作用的是它的实现类

public interface WindowManager extends ViewManager {//对这个异常熟悉么?当你往已经销毁的Activity中添加Dialog的时候,就会抛这个异常public static class BadTokenException extends RuntimeException {public BadTokenException() {}public BadTokenException(String name) {super(name);}}//其实WindowManager里面80%的代码是用来描述这个内部静态类的public static class LayoutParams extends ViewGroup.LayoutParamsimplements Parcelable {}
}

WindowManager继承自ViewManager这个接口,从注释和方法我们可以知道,这个就是用来描述可以对Activity中的子View进行添加和移除能力的接口

/** Interface to let you add and remove child views to an Activity. To get an instance* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.*/
public interface ViewManager
{public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view);
}

那么我们在使用WindowManager的时候,到底是在使用哪个类呢?
是WindowManagerImpl。

public final class WindowManagerImpl implements WindowManager {}

怎么知道的呢?那我们还要从Activity.attach()说起
话说,在attach()里面完成了mWindowManager的初始化

 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,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, IVoiceInteractor voiceInteractor) {mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);mWindowManager = mWindow.getWindowManager();}

那我们只好看下(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)是什么玩意了。
这里要说明的是,context是一个ContextImpl对象,这里先记住就好,以后再细说。

class ContextImpl extends Context {//静态代码块,完成各种系统服务的注册static {......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);}});......}@Overridepublic Object getSystemService(String name) {ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);return fetcher == null ? null : fetcher.getService(this);}
}

要注意的是,这里返回的WindowManagerImpl对象,最终并不是和我们的Window关联的,而且这个方法是有可能返回null的,所以在Window.setWindowManager()的时候,进行了处理

 public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {mAppToken = appToken;mAppName = appName;mHardwareAccelerated = hardwareAccelerated|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);//重试一遍if (wm == null) {wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);}//设置parentWindow,创建真正关联的WindowManagerImpl对象mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}public final class WindowManagerImpl implements WindowManager {//最终调用的这个构造private WindowManagerImpl(Display display, Window parentWindow) {mDisplay = display;mParentWindow = parentWindow;}public WindowManagerImpl createLocalWindowManager(Window parentWindow) {return new WindowManagerImpl(mDisplay, parentWindow);}}

所以说,每一个Activity都有一个PhoneWindow成员变量,并且也都有一个WindowManagerImpl,而且,PhoneWindow和WindowManagerImpl在Activity.attach()的时候进行了关联。
查一张类图(转自 工匠若水)

607813-c97c66c7bf0edd98.png

知道了这些,那么下面的操作就可以直接看WindowManagerImpl了。
其实WindowManagerImpl这个类也没有什么看透,为啥这么说呢?因为他其实是代理模式中的代理。是谁的代理呢?是WindowManagerGlobal。

public final class WindowManagerImpl implements WindowManager {private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();private final Display mDisplay;private final Window mParentWindow;@Overridepublic void addView(View view, ViewGroup.LayoutParams params) {mGlobal.addView(view, params, mDisplay, mParentWindow);}@Overridepublic void updateViewLayout(View view, ViewGroup.LayoutParams params) {mGlobal.updateViewLayout(view, params);}@Overridepublic void removeView(View view) {mGlobal.removeView(view, false);}@Overridepublic void removeViewImmediate(View view) {mGlobal.removeView(view, true);}}

从上面的代码中可以看出来,WindowManagerImpl里面对ViewManager接口内方法的实现,都是通过代码WindowManagerGlobal的方法实现的,所以终点转移到了WindowManagerGlobal这个类。
还记得前面我们的DecorView被添加到了WindowManager吗?

wm.addView(decor, l);

其实最终调用的是WindowManagerGlobal.addView():

 public final class WindowManagerGlobal {private static IWindowManager sWindowManagerService;private static IWindowSession sWindowSession;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>();//WindowManagerGlobal是单例模式private static WindowManagerGlobal sDefaultWindowManager;public static WindowManagerGlobal getInstance() {synchronized (WindowManagerGlobal.class) {if (sDefaultWindowManager == null) {sDefaultWindowManager = new WindowManagerGlobal();}return sDefaultWindowManager;}}public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;......synchronized (mLock) {ViewRootImpl root;root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);}......try {//注意下这个方法,因为下面介绍ViewRootImpl的时候会用到root.setView(view, wparams, panelParentView);}catch (RuntimeException e) {}}}

我们看到,WindowManagerGlobal是单例模式,所以在一个App里面只会有一个WindowManagerGlobal实例。在WindowManagerGlobal里面维护了三个集合,分别存放添加进来的View(实际上就是DecorView),布局参数params,和刚刚实例化的ViewRootImpl对象,WindowManagerGlobal到底干嘛的呢?
其实WindowManagerGlobal是和WindowManagerService(即WMS)通信的。
还记得在上一篇文章中我们介绍ActivityThread和AMS之间的IBinder通信的吗?是的,这里也是IBinder通信。

 public static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession == null) {try {InputMethodManager imm = InputMethodManager.getInstance();IWindowManager windowManager = getWindowManagerService();sWindowSession = windowManager.openSession(......} catch (RemoteException e) {Log.e(TAG, "Failed to open window session", e);}}return sWindowSession;}}public static IWindowManager getWindowManagerService() {synchronized (WindowManagerGlobal.class) {if (sWindowManagerService == null) {//ServiceManager是用来管理系统服务的,比如AMS、WMS等,这里就获取到了WMS的客户端代理对象sWindowManagerService = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));}return sWindowManagerService;}}

首先通过上面的方法获取到IBinder对象,然后转化成了WMS在本地的代理对象IWindowManager,然后通过openSession()初始化了sWindowSession对象。这个对象是干什么的呢?
"Session"是会话的意思,这个类就是为了实现与WMS的会话的,谁和WMS的对话呢?WindowManagerGlobal类内部并没有用这个类呀!
是ViewRootImpl与WMS的对话。

ViewRootImpl是什么?有什么作用?ViewRootImpl如何与WMS通信

你还记得吗?在前面将WindowManagerGlobal.addView()的时候,实例化了一个ViewRootImpl,然后添加到了一个集合里面,咱们先看下ViewRootImpl的构造函数吧

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {public ViewRootImpl(Context context, Display display) { mContext = context;//获取WindowSessionmWindowSession = WindowManagerGlobal.getWindowSession();mDisplay = display;......mWindow = new W(this);//默认不可见mViewVisibility = View.GONE;//这个数值就是屏幕宽度的dp总数mDensity = context.getResources().getDisplayMetrics().densityDpi;mChoreographer = Choreographer.getInstance();mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);}
}

在这个构造方法里面,主要是完成了各种参数的初始化,并且最关键的,获取到了前面介绍的WindowSession,那么你可能好奇了,这个ViewRootImpl到底有什么作用呢?
ViewRootImpl负责管理视图树与WMS交互,与WMS交互是通过WindowSession。而且ViewRootImpl也负责UI界面的布局与渲染,负责把一些事件分发至Activity,以便Activity可以截获事件。大多数情况下,它管理Activity顶层视图DecorView,它相当于MVC模型中的Contriller。
WindowSession是ViewRootImpl获取之后,主动和WMS通信的,但是我们在前面的文章知道,客户端和服务器需要互相持有对方的代理引用,才能实现双向通信,那么WMS是怎么得到ViewRootImpl的通信代理的呢?
是在ViewRootImpl.setView()的时候。
还记得不?在上面介绍WindowManagerGlobal.addView()的时候,我还终点说了下,在这个方法的try代码块中,调用了ViewRootImpl.setView(),下面咱们看下这个方法干嘛了:

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {mView = view;int res;requestLayout();try {res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);}catch (RemoteException e) {throw new RuntimeException("Adding window failed", e);} finally {}     }}}

为了突出重点,我简化了很多代码,从上面可以看出来,是mWindowSession.addToDisplay()这个方法把mWindow传递给WMS,WMS就持有了当前ViewRootImpl的代码,就可以调用W对象让ViewRootImpl做一些事情了。
这样,双方有可对方的接口,WMS中的Session注册到WindowManagerGlobal的成员WindowSession中,ViewRootImpl::W注册到WindowState中的成员mClient中。前者是为了App改变View结构时请求WMS为其更新布局。候着代表了App端的一个添加到WMS中的View,每一个像这样通过WindowManager接口中addView()添加的窗口都有一个对应的ViewRootImpl,也有一个相应的ViewRootImpl::W。它可以理解为是ViewRootImpl中暴露给WMS的接口,这样WMS可以通过这个接口和App端通信。
另外源码中很多地方采用了这种将接口隐藏为内部类的方法,这样可以实现六大设计原则之一——接口最小原则。

从什么时候开始绘制整个Activity的View树的?

注册前面代码中的requestLayout(),因为这个方法执行之后,我们的ViewRootImpl才开始绘制整个View树!

@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;scheduleTraversals();}}
void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;//暂停UI线程消息队列对同步消息的处理mTraversalBarrier = mHandler.getLooper().postSyncBarrier();//向Choreographer注册一个类型为CALLBACK_TRAVERSAL的回调,用于处理UI绘制mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();}}

"Choreographer就是一个消息处理器,根据vsync信号来计算frame"
解释起来比较麻烦,我们暂时不展开讨论,你只要知道,当回调被出发之后,mTraversalRunnable对象的run()就会被调用

 final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}

doTraversal()中最关键的,就是调用了performTraversals(),然后就开始measure、layout和draw了,这里面的逻辑本篇文章不讲,因为终点是Activity的界面显示流程,这一块属于View的,找时间单独拿出来说

 void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");try {performTraversals();} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}}

来回倒腾了这么多,终于看见界面了,让我哭会儿!!

Window的类型有几种?分别在什么情况下会使用到哪一种?

Window的类型是根据WindowManager.LayoutParams的type属性相关的,根据类型可以分为三类:

  • 取值在FIRST_APPLICATION_WINDOW与LAST_APPLICATION_WINDOW之间(1-99),是常用的顶层应用程序窗口,需将token设置成Activity的token,比如前面开启Window的时候设置的类型即为TYPE_APPLICATION

  • 在FIRST_SUB_WINDOW和LAST_SUB_WINDOW(1000-1999)之间,与顶层窗口相关联,需将token设置成它所附着宿主窗口的token,比如PopupWindow就是TYPE_APPLICATION_PANEL

  • 取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW(2000-2999)之间,不能用于应用程序,使用时需要有特殊权限,它是特定的系统功能才能使用,比如Toast就是TYPE_TOAST=2005,所以不需要特殊权限

下面是所有的Type说明

//WindowType:开始应用程序窗口public static final int FIRST_APPLICATION_WINDOW = 1;//WindowType:所有程序窗口的base窗口,其他应用程序窗口都显示在它上面public static final int TYPE_BASE_APPLICATION  = 1;//WindowType:普通应用程序窗口,token必须设置为Activity的token来指定窗口属于谁public static final int TYPE_APPLICATION        = 2;//WindowType:应用程序启动时所显示的窗口,应用自己不要使用这种类型,它被系统用来显示一些信息,直到应用程序可以开启自己的窗口为止public static final int TYPE_APPLICATION_STARTING = 3;//WindowType:结束应用程序窗口public static final int LAST_APPLICATION_WINDOW = 99;//WindowType:SubWindows子窗口,子窗口的Z序和坐标空间都依赖于他们的宿主窗口public static final int FIRST_SUB_WINDOW        = 1000;//WindowType: 面板窗口,显示于宿主窗口的上层public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;//WindowType:媒体窗口(例如视频),显示于宿主窗口下层public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;//WindowType:应用程序窗口的子面板,显示于所有面板窗口的上层public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;//WindowType:对话框,类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;//WindowType:媒体信息,显示在媒体层和程序窗口之间,需要实现半透明效果public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4;//WindowType:子窗口结束public static final int LAST_SUB_WINDOW        = 1999;//WindowType:系统窗口,非应用程序创建public static final int FIRST_SYSTEM_WINDOW    = 2000;//WindowType:状态栏,只能有一个状态栏,位于屏幕顶端,其他窗口都位于它下方public static final int TYPE_STATUS_BAR        = FIRST_SYSTEM_WINDOW;//WindowType:搜索栏,只能有一个搜索栏,位于屏幕上方public static final int TYPE_SEARCH_BAR        = FIRST_SYSTEM_WINDOW+1;//WindowType:电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;//WindowType:系统提示,出现在应用程序窗口之上public static final int TYPE_SYSTEM_ALERT      = FIRST_SYSTEM_WINDOW+3;//WindowType:锁屏窗口public static final int TYPE_KEYGUARD          = FIRST_SYSTEM_WINDOW+4;//WindowType:信息窗口,用于显示Toastpublic static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;//WindowType:系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏public static final int TYPE_SYSTEM_OVERLAY    = FIRST_SYSTEM_WINDOW+6;//WindowType:电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏public static final int TYPE_PRIORITY_PHONE    = FIRST_SYSTEM_WINDOW+7;//WindowType:系统对话框public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;//WindowType:锁屏时显示的对话框public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;//WindowType:系统内部错误提示,显示于所有内容之上public static final int TYPE_SYSTEM_ERROR      = FIRST_SYSTEM_WINDOW+10;//WindowType:内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖public static final int TYPE_INPUT_METHOD      = FIRST_SYSTEM_WINDOW+11;//WindowType:内部输入法对话框,显示于当前输入法窗口之上public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;//WindowType:墙纸窗口public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;//WindowType:状态栏的滑动面板public static final int TYPE_STATUS_BAR_PANEL  = FIRST_SYSTEM_WINDOW+14;//WindowType:安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;//WindowType:拖放伪窗口,只有一个阻力层(最多),它被放置在所有其他窗口上面public static final int TYPE_DRAG              = FIRST_SYSTEM_WINDOW+16;//WindowType:状态栏下拉面板public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;//WindowType:鼠标指针public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;//WindowType:导航栏(有别于状态栏时)public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;//WindowType:音量级别的覆盖对话框,显示当用户更改系统音量大小public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;//WindowType:起机进度框,在一切之上public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;//WindowType:假窗,消费导航栏隐藏时触摸事件public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;//WindowType:梦想(屏保)窗口,略高于键盘public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;//WindowType:导航栏面板(不同于状态栏的导航栏)public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;//WindowType:universe背后真正的窗户public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;//WindowType:显示窗口覆盖,用于模拟辅助显示设备public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;//WindowType:放大窗口覆盖,用于突出显示的放大部分可访问性放大时启用public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;//WindowType:......public static final int TYPE_KEYGUARD_SCRIM          = FIRST_SYSTEM_WINDOW+29;public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;//WindowType:系统窗口结束public static final int LAST_SYSTEM_WINDOW      = 2999;

为什么使用PopWindow的时候,不设置背景就不能触发事件?

我们在使用PopupWindow的时候,会发现如果不给PopupWindow设置背景,那么就不能触发点击返回事件,有人认为这个是BUG,其实并不是的。
我们以下面的方法为例,其实所有的显示方法都有下面的流程:

public void showAtLocation(IBinder token, int gravity, int x, int y) {if (isShowing() || mContentView == null) {return;}mIsShowing = true;mIsDropdown = false;WindowManager.LayoutParams p = createPopupLayout(token);p.windowAnimations = computeAnimationResource();//在这里会根据不同的设置,配置不同的LayoutParams属性preparePopup(p);if (gravity == Gravity.NO_GRAVITY) {gravity = Gravity.TOP | Gravity.START;}p.gravity = gravity;p.x = x;p.y = y;if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;invokePopup(p);}

我们重点看下preparePopup()

private void preparePopup(WindowManager.LayoutParams p) {//根据背景的设置情况进行不同的配置if (mBackground != null) {final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();int height = ViewGroup.LayoutParams.MATCH_PARENT;//如果设置了背景,就用一个PopupViewContainer对象来包裹之前的mContentView,并设置背景后PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);popupViewContainer.setBackground(mBackground);popupViewContainer.addView(mContentView, listParams);mPopupView = popupViewContainer;} else {mPopupView = mContentView;}}

为啥包了一层PopupViewContainer,就可以处理按钮点击事件了?因为PopupWindow没有相关事件回调,也没有重写按键和触摸方法,所以接收不到对应的信号

public class PopupWindow {}

而PopupViewContainer则可以,因为它重写了相关方法

private class PopupViewContainer extends FrameLayout {@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {if (getKeyDispatcherState() == null) {return super.dispatchKeyEvent(event);}if (event.getAction() == KeyEvent.ACTION_DOWN&& event.getRepeatCount() == 0) {KeyEvent.DispatcherState state = getKeyDispatcherState();if (state != null) {state.startTracking(event, this);}return true;} else if (event.getAction() == KeyEvent.ACTION_UP) {//back键消失KeyEvent.DispatcherState state = getKeyDispatcherState();if (state != null && state.isTracking(event) && !event.isCanceled()) {dismiss();return true;}}return super.dispatchKeyEvent(event);} else {return super.dispatchKeyEvent(event);}}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {return true;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {final int x = (int) event.getX();final int y = (int) event.getY();//触摸在外面就消失if ((event.getAction() == MotionEvent.ACTION_DOWN)&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {dismiss();return true;} else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {dismiss();return true;} else {return super.onTouchEvent(event);}}
}

在Activity中使用Dialog的时候,为什么有时候会报错"Unable to add window -- token is not valid; is your activity running?"?

这种情况一般发生在什么时候?一般发生在Activity进入后台,Dialog没有主动Dismiss掉,然后从后台再次进入App的时候。
为什么会这样呢?
还记得前面说过吧,子窗口类型的Window,比如Dialog,想要显示的话,比如保证appToken与Activity保持一致,而当Activity销毁,再次回来的时候,Dialog试图重新创建,调用ViewRootImp的setView()的时候就会出问题,所以记得在Activity不可见的时候,主动Dismiss掉Dialog。

if (res < WindowManagerGlobal.ADD_OKAY) {switch (res) {case WindowManagerGlobal.ADD_BAD_APP_TOKEN:case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:throw new WindowManager.BadTokenException("Unable to add window -- token " + attrs.token+ " is not valid; is your activity running?");case WindowManagerGlobal.ADD_NOT_APP_TOKEN:throw new WindowManager.BadTokenException("Unable to add window -- token " + attrs.token+ " is not for an application");case WindowManagerGlobal.ADD_APP_EXITING:throw new WindowManager.BadTokenException("Unable to add window -- app for token " + attrs.token+ " is exiting");case WindowManagerGlobal.ADD_DUPLICATE_ADD:throw new WindowManager.BadTokenException("Unable to add window -- window " + mWindow+ " has already been added");case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:// Silently ignore -- we would have just removed it// right away, anyway.return;case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:throw new WindowManager.BadTokenException("Unable to add window " + mWindow +" -- another window of this type already exists");case WindowManagerGlobal.ADD_PERMISSION_DENIED:throw new WindowManager.BadTokenException("Unable to add window " + mWindow +" -- permission denied for this window type");case WindowManagerGlobal.ADD_INVALID_DISPLAY:throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow +" -- the specified display can not be found");}throw new RuntimeException("Unable to add window -- unknown error code " + res);}}

为什么Toast需要由系统统一控制,在子线程中为什么不能显示Toast?

首先Toast也属于窗口系统,但是并不是属于App的,是由系统同一控制的。
关于这一块不想说太多,具体实现机制请参考后面的文章。
为了看下面的内容,你需要知道以下几件事情:

  1. Toast的显示是由系统Toast服务控制的,与系统之间的通信方式是Binder

  2. 整个Toast系统会维持最多50个Toast的队列,依次显示

  3. 负责现实工作的是Toast的内部类TN,它负责最终的显示与隐藏操作

  4. 负责给系统Toast服务发送内容的是INotificationManager的实现类,它负责在Toast.show()里面把TN对象传递给系统消息服务,service.enqueueToast(pkg, tn, mDuration);这样Toast服务就持有客户端的代理,可以通过TN来控制每个Toast的显示与隐藏。

再来张图(转自 工匠若水)

607813-8622411bfd58b91d.png

ok,现在假如你知道上面这些啦,那么我们下面就看为什么在子线程使用Toast.show()会提示

"No Looper; Looper.prepare() wasn't called on this thread."

原因很简单,因为TN在操作Toast的时候,是通过Handler做的

@Overridepublic void show() {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.post(mShow);}@Overridepublic void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);}

所以说,TN初始化的线程必须为主线程,在子线程中使用Handler,由于没有消息队列,就会造成这个问题。

结语

上面写了这么多,你可能看了前面忘了后面,下面,凯子哥给你总结一下,这篇文章到底讲了什么东西:

  • 每个Activity,都至少有一个Window,这个Window实际类型为PhoneWindow,当Activity中有子窗口,比如Dialog的时候,就会出现多个Window。Activity的Window是我们控制的,状态栏和导航栏的Window由系统控制。

  • 在DecorView的里面,一定有一个id为content的FraneLayout的布局容器,咱们自己定义的xml布局都放在这里面。

  • Activity的Window里面有一个DecorView,它使继承自FrameLayout的一个自定义控件,作为整个View层的容器,及View树的根节点。

  • Window是虚拟的概念,DecorView才是看得见,摸得着的东西,Activity.setContentView()实际调用的是PhoneWindow.setContentView(),在这里面实现了DecorView的初始化和id为content的FraneLayout的布局容器的初始化,并且会根据主题等配置,选择不同的xml文件。而且在Activity.setContentView()之后,Window的一些特征位将被锁定。

  • Activity.findViewById()实际上调用的是DecorView的findviewById(),这个方法在View中定义,但是是final的,实际起作用的是在ViewGroup中被重写的findViewTraversal()方法。

  • Activity的mWindow成员变量是在attach()的时候被初始化的,attach()是Activity被通过反射手段实例化之后调用的第一个方法,在这之后生命周期方法才会依次调用

  • 在onResume()刚执行之后,界面还是不可见的,只有执行完Activity.makeVisible(),DecorView才对用户可见

  • ViewManager这个接口里面就三个接口,添加、移除和更新,实现这个接口的有WindowManager和ViewGroup,但是他们两个面向的对象是不一样的,WindowManager实现的是对Window的操作,而ViewGroup则是对View的增、删、更新操作。

  • WindowManagerImpl是WindowManager的实现类,但是他就是一个代理类,代理的是WindowManagerGlobal,WindowManagerGlobal一个App里面就有一个,因为它是单例的,它里面管理了App中所有打开的DecorView,ContentView和PhoneWindow的布局参数WindowManager.LayoutParams,而且WindowManagerGlobal这个类是和WMS通信用的,是通过IWindowSession对象完成这个工作的,而IWindowSession一个App只有一个,但是每个ViewRootImpl都持有对IWindowSession的引用,所以ViewRootImpl可以和WMS喊话,但是WMS怎么和ViewRootImpl喊话呢?是通过ViewRootImpl::W这个内部类实现的,而且源码中很多地方采用了这种将接口隐藏为内部类的方式,这样可以实现六大设计原则之一——接口最小原则,这样ViewRootImpl和WMS就互相持有对方的代理,就可以互相交流了

  • ViewRootImpl这个类每个Activity都有一个,它负责和WMS通信,同时相应WMS的指挥,还负责View界面的测量、布局和绘制工作,所以当你调用View.invalidate()和View.requestLayout()的时候,都会把事件传递到ViewRootImpl,然后ViewRootImpl计算出需要重绘的区域,告诉WMS,WMS再通知其他服务完成绘制和动画等效果,当然,这是后话,咱们以后再说。

  • Window分为三种,子窗口,应用窗口和系统窗口,子窗口必须依附于一个上下文,就是Activity,因为它需要Activity的appToken,子窗口和Activity的WindowManager是一个的,都是根据appToken获取的,描述一个Window属于哪种类型,是根据LayoutParam.type决定的,不同类型有不同的取值范围,系统类的的Window需要特殊权限,当然Toast比较特殊,不需要权限

  • PopupWindow使用的时候,如果想触发按键和触摸事件,需要添加一个背景,代码中会根据是否设置背景进行不同的逻辑判断

  • Dialog在Activity不可见的时候,要主动dismiss掉,否则会因为appToken为空crash

  • Toast属于系统窗口,由系统服务NotificationManagerService统一调度,NotificationManagerService中维持着一个集合ArrayList,最多存放50个Toast,但是NotificationManagerService只负责管理Toast,具体的现实工作由Toast::TN来实现

最后来一张Android的窗口管理框架(转自 ariesjzj)

607813-5e7218a4453ea45e.png

OK,关于Activity的界面显示就说到这里吧,本篇文章大部分的内容来自于阅读下面参考文章之后的总结和思考,想了解更详细的可以研究下。
下次再见,拜拜~

参考文章

http://blog.csdn.net/yanbober/article/details/46361191
http://blog.csdn.net/yanbober/article/details/45970721
http://blog.csdn.net/jinzhuojun/article/details/37737439
http://blog.csdn.net/xieqibao/article/details/6567814
http://www.cnblogs.com/samchen2009/p/3364327.html

作者:凯子哥
链接:http://www.jianshu.com/p/65295b2cb047

转载于:https://www.cnblogs.com/xs104/p/5887253.html

【转载】【凯子哥带你学Framework】Activity界面显示全解析(下)相关推荐

  1. 【凯子哥带你学Framework】Activity界面显示全解析

    前几天凯子哥写的Framework层的解析文章<Activity启动过程全解析>,反响还不错,这说明"写让大家都能看懂的Framework解析文章"的思想是基本正确的. ...

  2. 【凯子哥带你学Android】Andriod性能优化之列表卡顿——以“简书”APP为例

    这几天闲得无聊,就打开手机上的开发者模式里面的"GPU过度绘制"功能,看看别家的App做的咋样,然后很偶然的打开了"简书",然后就被它的过度绘制惊呆了,于是写了 ...

  3. 【凯子哥带你夯实应用层】都说“知乎”逼格高,我们来实现“知乎”回答详情页动画效果

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 2014已经远去,2015年的目标很简单,就是继续熟悉Android的上层API,虽然偶尔会为了某个问题去研 ...

  4. 【凯子哥带你夯实应用层】Android的Google官方设计指南(上)

    Android 设计规范 时间 2015.3.2 版本 V1.0 翻译 杨鹏 整理 赵凯强 本文章是我公司一个大牛之前的公司同事翻译的Android的Google官方设计指导,经过我整理而成,分享给大 ...

  5. 【凯子哥带你夯实应用层】使用ActionMode实现有删除动画的多选删除功能

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 ActionMode是3.0之后,官方推荐的一种上下文菜单的实现方式,在之前一直用的是Context Men ...

  6. 【凯子哥带你做高仿】“煎蛋”Android版的高仿及优化(二)——大图显示模式、评论“盖楼”效果实现详解

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 在前一篇文章中,我们学习了如何进行逆向工程和TcpDump进行抓包,获取我们的数据接口,那么有了数据之后,我 ...

  7. 【凯子哥带你做高仿】“煎蛋”Android版的高仿及优化(三)——使用GreenDao实现本地Sqlite缓存

    到目前为止,煎蛋的Android项目算是告一段落了,功能基本都已完成,那么今天,我就介绍一下在煎蛋这个项目里,是怎么完成数据缓存功能的.想看代码的请戳煎蛋项目的GITHUB地址 转载请注明出处:htt ...

  8. Carson带你学Android:源码解析自定义View Draw过程

    前言 自定义View是Android开发者必须了解的基础 网上有大量关于自定义View原理的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化 等 今天,我将全面总结自定义View ...

  9. Carson带你学Android:最全面的Webview使用详解

    前言 现在很多App里都内置了Web网页(Hyprid App),比如说很多电商平台,淘宝.京东.聚划算等等,如下图 那么这种该如何实现呢?其实这是Android里一个叫WebView的组件实现的.今 ...

  10. 星哥带你学秒杀-我看你还是有机会的

    一.前言 大家好,好久不发文章了.最近自己把秒杀系统搭建了一下,想给有需要帮助的童鞋学习.整个项目已经放到Github上去了,项目Pull下来,修改好配置文件就可以跑起来,减少你们项目框架搭建的时间, ...

最新文章

  1. linux shell 删除两个文件相同部分
  2. php 将一个字符串转换成数组,PHP将一个字符串转换成数组
  3. 【如意影视】运营级+完整类库+解析线路+无限增加或删减解析接口+如意可视化播放器1.1
  4. phalapi可以依赖注入么_phalapi-进阶篇8(PhalApi能带来什么和进阶篇总结)
  5. 荣耀赵明 “Diss” 5G 手机;甲骨文创始人埃里森:Uber 一文不值;Chrome 77 发布 | 极客头条...
  6. 张广慧:云计算对游戏开发者的价值
  7. 精密电阻阻值表丝印大全
  8. qdir 自动创建多级目录_QDir 类 - 目录信息类
  9. 四川大学计算机学院2020转专业,四川大学化学学院2020年本科生转专业工作实施方案...
  10. CentOS7搭建FLV和RTMP流媒体服务器
  11. 衣带渐宽终不悔,为“指针”消得人憔悴(四)
  12. 2019/2/13打印华氏温度与摄氏温度对照表
  13. 独家 | 全球2000家客户,这家公司推动林肯MKZ成为最流行的自动驾驶样车
  14. java对象为什么要重写equals方法
  15. Java中catch和throw同时使用
  16. 12、Tello 进行起飞命令执行和视频显示
  17. SQL_Server 2008R2数据库安装教程
  18. Android中解析读取复杂word,excel,ppt等的方法
  19. 机器学习与统计建模 —— 归一化和标准化
  20. 一本大学计算机专业最新排名,中国校友会网2018中国大学计算机类各本科专业排行榜...

热门文章

  1. TopOn的两种测试方法
  2. 栈 -- 4.1.1 Valid Parentheses I-II -- 图解
  3. c++中的explicit关键字及隐式类型转换
  4. 获取用户真实IP以及internalProxies
  5. java免安装版配置_Java环境变量一键配置
  6. SQLserver插入\更新中文乱码
  7. Prototype的JSON支持
  8. Linux系统(七)组管理和用户管理
  9. Numpy系列(七)求解线性方程组、计算逆矩阵求解线性方程组
  10. 如何通俗易懂地理解基于模型的强化学习?