面试官:Android中的Application真的不能弹出Dialog吗(Window全面解析)

3,852 阅读14分钟

本文将深入分析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. 应用级别窗口,层级范围1~99,比如Activity。须将token设置成Activity的token
  2. 子窗口,不能单独存在,必须依附于特定的父window,层级范围1000~1999,比如Dialog。需将token设置成它所附着宿主窗口的token
  3. 系统级别窗口,层级范围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模块及应用程序客户端的接口。

主要交互流程如下:

  1. Activity中添加窗口是通过WindowManager类的addViw和removeView完成,转而调用ViewRootImpl(ViewRoot是早期的Android版本中的,现在则是ViewRootImpl)类的相关方法,最终调用到WMS的添加,删除方法。
  2. AMS通知ActivityThread销毁某个Activity时,ActivityThread会直接调用WindowManager的remove方法删除窗口。
  3. 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的添加过程
  1. 检查参数是否合法,如果是子Window还需要调整一些布局参数

  2. 创建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;
        }
    }
}
  1. 通过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;
    }
  1. 如果没有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

  2. 将View添加到DecorView的mContentParent中,所以叫setContentView比较合适。

  3. 回调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);
    }

参考文章

blog.csdn.net/u012702547/…

zhuanlan.51cto.com/art/202007/…

Android开发艺术探索

Android内核剖析