本文将深入分析Android中的Window窗口机制,标题的疑问可以在具体模块查看分析和答案,下面开始分析:
Window表示窗口,是一个抽象类,实际上的实现是PhoneWindow,Android中所有的视图都是通过Window来呈现的,Window实际是View的直接管理者,单击事件由Window传递给DecorView,然后再由DecorView传递给我们的View,就连Activity的设置视图的方法setContentView在底层也会是通过Window来完成的。
Window的具体实现在WindowManagerService(WMS),所以了解WMS的启动流程的非常重要的,同时我们也需要了解Activity等界面中Window的创建过程以及具体WMS的工作流程。
WindowManagerService(WMS)思维导图
下面是关于图片的一些说明:
-
WMS的创建不是在system_server主线程,而是在另一个线程(DisplayThread)中创建
-
DecoreView是什么?
在PhoneWindow类中,mDecor的类型是DecorView,当调用setContentView时,如果mDecor还没有创建,则会调用installDecor方法来创建Activity中的DecorView和其他框架的View
-
窗口的显示次序分析
手机屏幕是以左上角为原点,向右为X轴方向,向下为Y轴方向的一个二维空间。为方便管理窗口显示次序,手机屏幕被扩展为了一个三维空间,多定义了一个Z轴,方向为垂直于屏幕表面指向屏幕外。多个窗口依照其前后顺序排布在这个虚拟的Z轴上,因此窗口的显示次序又被称为Z序(Z order)
一个Application中真的不能弹出Dialog吗
首先我们做个测试,用getApplciationContext创建Dialog,然后弹出Dialog。
//vm api 18
public void test1(){
Dialog alert = new Dialog(getApplicationContext());
//alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
View view = LayoutInflater.from(this).inflate(R.layout.dialog,null);
alert.setContentView(view);
alert.show();
}
然后就可以看到控制台报错日志
10-19 10:19:17.646 6451-6451/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
at android.view.ViewRootImpl.setView(ViewRootImpl.java:563)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:269)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
at android.app.Dialog.show(Dialog.java:281)
at com.jackie.testdialog.MainActivity.test1(MainActivity.java:52)
at com.jackie.testdialog.MainActivity$1.onClick(MainActivity.java:27)
Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window的上面,
Window有三种类型:
- 应用级别窗口,层级范围1~99,比如Activity。须将token设置成Activity的token
- 子窗口,不能单独存在,必须依附于特定的父window,层级范围1000~1999,比如Dialog。需将token设置成它所附着宿主窗口的token。
- 系统级别窗口,层级范围2000~2999,比如Toast。**系统类型的window是需要检查权限的,需要在AndroidManifest中声明。**如果创建的是系统窗口,那么分两种情况,对于TYPE _ INPUT _ METHOD,TYPE _ VOICE _ INTERACTION,TYPE _ WALLPAPER,TYPE _ DREAM,TYPE _ ACCESSIBILITY _ OVERLAY这些系统窗口,token是不可以为null的,而对于其他的系统窗口,token可以为null,所以Dialog中设置系统的type,同时添加权限,也可以直接显示。
理解这了上面这些内容,我们就知道如何解决该问题了。再看看上面的错误,因为传的是getApplcationContext,所以我们没有token,而这时候要弹出Dialog就报错了。因此在上面的例子中,只需要指定对话框的Window类型为系统类型就可以正常弹出对话框了。
//vm api 18 android 18上测试成功
public void test1(){
Dialog alert = new Dialog(getApplicationContext());
alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
View view = LayoutInflater.from(this).inflate(R.layout.dialog,null);
alert.setContentView(view);
alert.show();
}
//vm api 29 android29上测试成功
public void test(){
Dialog alert = new Dialog(getApplicationContext());
alert.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
View view = LayoutInflater.from(this).inflate(R.layout.dialog,null);
alert.setContentView(view);
alert.show();
}
同时需要声明权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
WMS的接口结构
WMS的接口结构指的是WMS功能模块和其他功能模块之间的交互接口,其中主要包括与AMS模块及应用程序客户端的接口。
主要交互流程如下:
- Activity中添加窗口是通过WindowManager类的addViw和removeView完成,转而调用ViewRootImpl(ViewRoot是早期的Android版本中的,现在则是ViewRootImpl)类的相关方法,最终调用到WMS的添加,删除方法。
- AMS通知ActivityThread销毁某个Activity时,ActivityThread会直接调用WindowManager的remove方法删除窗口。
- AMS中直接调用WMS,这种请求一般都不是请求WMS创建或者删除窗口,而是告诉WMS一些其他信息,比如某个新的Activity就要启动了,从而WMS会保存一个该Activity记录的引用。
绘制屏幕是借助于SurfaceFlinger模块完成的,SurfaceFlinger是Linux的一个驱动,它内部会使用芯片的图形加速完成对界面的绘制。
Window的内部机制
WindowManager继承与ViewManager,ViewManager的源码如下:
/** 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常用的就只有这个三个功能,但是这三个功能就已经足够了。
Window是一个抽象的概念,**每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此,它是以View的形式存在的。**这一点从WindowManager的定义也可以看出,
public interface WindowManager extends ViewManager
继承了ViewManager,而且WindowManager中很多方法都是针对View的,这说明View才是Window存在的实体。在实际使用中无法直接访问Window,需要通过WindowManager。WindowManager是一个接口,它的真正实现是WindowManagerImpl类
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
可以发现,WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给WindowManagerGlobal来处理,WindowManagerImpl这种工作模式是典型的桥接模式,将操作委托给WindowManagerGlobal来实现。
Window的添加过程
-
检查参数是否合法,如果是子Window还需要调整一些布局参数
-
创建ViewRootImpl并添加到View列表中,WindowManagerGlobal内部有如下几个列表比较重要
@UnsupportedAppUsage private final ArrayList<View> mViews = new ArrayList<View>(); @UnsupportedAppUsage private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); @UnsupportedAppUsage private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); private final ArraySet<View> mDyingViews = new ArraySet<View>();mViews存储的是素有的Window所对应的View,mRoots存储的是所有Window所对应的ViewRootImpl,mParams存储的是所有Window所对应的布局参数,而mDyingViews则存储了那些正在被删除的View对象,或者说是那些已经调用removeView方法但是删除操作还未完成的Window对象。
//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//检查参数是否合法
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//如果是子Window还需要调整一些布局参数
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
//2.创建ViewRootImpl并添加到View列表中
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//3.通过ViewRootImpl来更新界面并完成Window的添加过程
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
-
通过ViewRootImpl来更新界面并完成Window的添加过程
root.setView(view, wparams, panelParentView)这个步骤由ViewRootImpl的setView方法来完成,View的绘制过程是由ViewRootImpl来完成的,这里也不例外,setView内部会通过requestLayout来完成异步刷新请求。里面的scheduleTraversals实际上是View绘制的入口:@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }接着会通过WindowSession最终来完成Window的添加过程。在下面的代码中,mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是Window的添加过程是一次IPC调用。
try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); setFrame(mTmpFrame); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } }Session内部会通过WindowManagerService来实现Window的添加
@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel); }这样,Window的添加请求就交给WindowManagerService去处理了,在WIndowManagerService内部会为每一个应用保留一个单独的Session。
window添加流程图
Window的更新过程也类似,ViewRootImpl中会通过setLayoutParams,再通过ViewRootImpl的scheduleTraversals方法来对View重新布局,包括测量,布局,重绘这三个过程,ViewRootImpl还会通过WindowSession来更新Window的视图,这个过程最终由WMS的relayoutWindow来具体实现的,它同样是一个IPC过程。
Window的创建过程
View是Android中视图的呈现方式,但是View不能单独存在,它必须依附在Window这个抽象的概念上面,因此有视图的地方就有Window。
Activity的Window创建过程
我们直接从performLaunchActivity方法开始,这个方法内部会通过类加载器创建Activity的实例对象,并调用attach方法为其关联运行过程中所以来的一系列上下文环境变量,代码如下所示:
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
···
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
···
if (activity != null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (r.overrideConfig != null) {
config.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
在activity的attach方法中,系统会创建Activity所属的Window对象并为其设置回调接口,Window对象的创建过程是通过mWindow = new PhoneWindow(this, window, activityConfigCallback)方法实现的。
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法。Window中的Callback:
public interface Callback {
public boolean dispatchKeyEvent(KeyEvent event);
public boolean dispatchKeyShortcutEvent(KeyEvent event);
public boolean dispatchTouchEvent(MotionEvent event);
public boolean dispatchTrackballEvent(MotionEvent event);
public boolean dispatchGenericMotionEvent(MotionEvent event);
···
public void onWindowFocusChanged(boolean hasFocus);
public void onAttachedToWindow();
public void onDetachedFromWindow();
···
}
下面来分析Activity的视图是怎么附属在Window上的,由于Activity的视图由setContentView方法提供,我们只需要看setContentView方法的实现即可。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
window的具体实现是PhoneWindow,所以只需要看PhoneWindow的setContentView
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
//1.如果没有DecorView,那就创建它
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//2.将View添加到DecorView的mContentParent中
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//3.回调Activity的onContentChange方法通知Activity视图已经改变
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
-
如果没有DecorView,那就创建它,在installDecor中进行DecorView的创建,DecorView是一个FrameLayout,是Activity中的顶级View,一般来说内部包含标题栏和内部类,但是会随主题的变化而发生改变,但是内容栏一定会存在的,它的固定id是android.R.id.content。installDecor内部通过generateDecor方法来直接创建DecorView,这个时候DecorView还只是一个空白的FrameLayout
protected DecorView generateDecor(int featureId) { // System process doesn't have application context and in that case we need to directly use // the context we have. Otherwise we want the application context, so we don't cling to the // activity. Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, getContext()); if (mTheme != -1) { context.setTheme(mTheme); } } } else { context = getContext(); } return new DecorView(context, featureId, this, getAttributes()); }初始化DecoreView的结构,通过PhoneWindow的generateLayout
-
将View添加到DecorView的mContentParent中,所以叫setContentView比较合适。
-
回调Activity的onContentChange方法通知Activity视图已经改变,Activity的onContentChanged方法是个空实现,可以在子Activity中处理这个回调。
onContentChanged()是Activity中的一个回调方法 当Activity的布局改动时,即setContentView()或者addContentView()方法执行完毕时就会调用该方法, 例如,Activity中各种View的findViewById()方法都可以放到该方法中。
经过上面的三个步骤,Window已经创建,DecorView也已经创建,此时DecorView还没有被WindowManager正式添加到Window中,DecorView还没有被WindowManager识别,这个时候Window还无法提供具体功能,因为它还无法接受外接的输入信息。onCreate中执行setContentView方法。
接下来,在ActivityThread的handleResumeActivity方法中,**首先调用Activity的onResume方法,接着会调用Activity.makeVisible()在该方法中,DecorView真正完成了添加和显示这两个过程,到这里Activity的视图才能被看到。**DecoreView和Window进行关联。
handleResumeActivity->performResumeActivity->performResume->onResume
handleResumeActivity方法
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
···
// TODO Push resumeArgs into the activity for consideration
//这个方法最终执行了onResume方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
···
final int forwardBit = isForward
? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityTaskManager.getService().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
//ActivityClientRecord中windowmanger进行设置
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
// Get rid of anything left hanging around.
cleanUpPendingRemoveWindows(r, false /* force */);
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
performConfigurationChangedForActivity(r, r.newConfig);
if (DEBUG_CONFIGURATION) {
Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig "
+ r.activity.mCurrentConfig);
}
r.newConfig = null;
}
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
}
}
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
//关键方法,DecorView和WindowManager进行管理,同时DecorView可见
r.activity.makeVisible();
}
}
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
//最后执行该方法,表示消息处理完毕
Looper.myQueue().addIdleHandler(new Idler());
}
performResume方法中执行了onResume方法
final void performResume(boolean followedByPause, String reason) {
dispatchActivityPreResumed();
performRestart(true /* start */, reason);
mFragments.execPendingActions();
mLastNonConfigurationInstances = null;
if (mAutoFillResetNeeded) {
// When Activity is destroyed in paused state, and relaunch activity, there will be
// extra onResume and onPause event, ignore the first onResume and onPause.
// see ActivityThread.handleRelaunchActivity()
mAutoFillIgnoreFirstResumePause = followedByPause;
if (mAutoFillIgnoreFirstResumePause && DEBUG_LIFECYCLE) {
Slog.v(TAG, "autofill will ignore first pause when relaunching " + this);
}
}
mCalled = false;
// mResumed is set by the instrumentation
//执行onResume方法
mInstrumentation.callActivityOnResume(this);
···
onPostResume();
···
dispatchActivityPostResumed();
}
handleResumeActivity中的makeView方法
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
//DecoreView和WindowManager进行关联。
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
//设置DecorView可见
mDecor.setVisibility(View.VISIBLE);
}
所以在onResume执行完这个方法后整个界面才是可见的,事件的传递是按照这个顺序:Acitvity的Window.CallBack中的dispatchTouchEvent到ViewGroup的dispatchTouchEvent,再到子View的dispatchTouchEvent。
小结
handleLaunchActivity到performLaunchActivity,performLaunchActiviy中通过反射创建Activity,同时进行activity.attch方法为其关联运行过程中需要的一系列上下文对象,同时在attach方法中创建PhoneWindow对象,然后执行onCreate方法中的setContentView方法,创建DecorView,设置contentView并通过onContentChange来回调;在handleResumeActivity中先执行onResume方法,然后在执行r.activity.makeVisible方法,**此时DecorView和Activity进行关联,设置DecorView可见。**最后设置Looper.myQueue().addIdleHandler(new Idler())。
Dialog的创建Window过程
Dialog和Activity的创建Window过程及其类似,也创建了一个PhoneWindow,具体可以自行查看源码。
Toast的创建Window过程
Toast具有定时取消的功能,采用Handler,Toast内部有两类IPC过程,Toast无法在没有Looper的线程中弹出,这是因为Handler需要使用Looper才能完成线程切换的功能。
一个app中有多少个window
Activity个数+Dialog个数+Toast个数+PopupWindow个数+悬浮框个数等等。
如何看activity中的window
~ » adb shell dumpsys window | grep com.jackie.testdialog
如何看popupwindow的window
adb shell dumpsys window | grep com.jackie.testdialog
···
allAppWindows=[Window{e5c1bce u0 com.jackie.testdialog/com.jackie.testdialog.MainActivity}, Window{3416c83 u0 PopupWindow:cb9f7b7}]
如何看toast中的window
~ » adb shell dumpsys window ##记住不要加包名过滤,这样才能看出来
WINDOW MANAGER ANIMATOR STATE (dumpsys window animator)
DisplayContentsAnimator #0:
Window #0: WindowStateAnimator{4c0b5b6 com.android.systemui.ImageWallpaper}
Window #1: WindowStateAnimator{e2f02b7 com.android.launcher3/com.android.launcher3.Launcher}
Window #2: WindowStateAnimator{df90eca com.jackie.testdialog/com.jackie.testdialog.MainActivity}
Window #3: WindowStateAnimator{2106342 PopupWindow:647f4aa}
Window #4: WindowStateAnimator{294498d InputMethod}
Window #5: WindowStateAnimator{79a7742 DockedStackDivider}
Window #6: WindowStateAnimator{c271353 AssistPreviewPanel}
Window #7: WindowStateAnimator{6448f53 Toast} toast在这里
Window #8: WindowStateAnimator{c0e8090 Toast}
Window #9: WindowStateAnimator{9b8b490 KeyguardScrim}
Window #10: WindowStateAnimator{208f189 StatusBar}
Window #11: WindowStateAnimator{5cc58e NavigationBar}
其他相关知识点
先来看看这段代码:
DisplayThread.getHandler().runWithScissors(() ->
sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
onlyCore, policy), 0); //同步等待WMS初始化成功
该段代码表示WMS的创建不是在system_server主线程,而是在另外一个线程(DisplayThread),再来看看这个Handler的runWithScissors方法,其实该方法就是让handler消息执行之后然后再继续往下执行,是个同步方法,同时也有hide标记,因为该方法不完善,无法取消任务,以及可能造成死锁问题。
public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
if (r == null) {
throw new IllegalArgumentException("runnable must not be null");
}
if (timeout < 0) {
throw new IllegalArgumentException("timeout must be non-negative");
}
if (Looper.myLooper() == mLooper) {
r.run();
return true;
}
BlockingRunnable br = new BlockingRunnable(r);
return br.postAndWait(this, timeout);
}
参考文章
zhuanlan.51cto.com/art/202007/…
Android开发艺术探索
Android内核剖析