理解 Window 和 WindowManager

   Window 是一个抽象类,它的具体实现是 PhoneWindow,创建一个 Window 非常简单,只需要通过 WindowManager 即可完成。 WindowManager 是外界访问 Window 的入口。Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManager 的交互是一个 IPC 的过程。在 Android 中,所有的视图都是通过 Window 来呈现的,不管是 Activity、Dialog 还是 Toast ,他们的视图实际上都是附加到 Window 上的,因此 Window 实际上是 View 的直接管理者。

一、 Window 和 WindowManager

   为了分析 Window 的工作机制,我们需要先了解如何使用 WindowManager 添加一个 Window。下面的代码通过 WindowManager 添加 Window 的过程。

    WindowManager manager = this.getWindowManager();
        Button button = new Button(this);
        button.setText("button");
        LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT
        , LayoutParams.WRAP_CONTENT,1,0, PixelFormat.TRANSPARENT);

        layoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE
                | LayoutParams.FLAG_SHOW_WHEN_LOCKED ;
        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        layoutParams.x = 100;
        layoutParams.y  = 300;
        manager.addView(button,layoutParams);

上面的代码可以将 Button 添加到屏幕坐标为(100,300) 的位置上。WindowManager.LayoutParams 中的 flag 和 type 这两个参数比较重要。

flag 参数

flag 参数表示 Window 的属性,它有很多选项,通过这些选项可以控制 Window 的显示特性。这里介绍常用的特性:

  • FLAG_NOT_FOCUSABLE
    表示 Window 不需要获取焦点,也不需要接收各种输入事件,此时标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的 Window。

  • FLAG_NOT_TOUCH_MODAL
    在此模式下,系统将当前 Window 区域以外的单击事件传递给底层的 Window ,当前 Window 区域以内的单击事件则自己处理。一般来说都会开启这个标记。

  • FLAG_SHOW_WHEN_LOCKED
    开启此模式可以让 Window 显示在锁屏界面上。

Type 参数

Type 表示 Window 的类型,Window 有三种类型,分别是应用 Window、子 Window 和系统 Window。应用类 Window 对应一个 Activity;子 Window 不能单独存在,需要附属在特定的父 Window,如常见的 Dialog;系统 Window 需要声明权限才能创建,如 Toast 和系统状态栏。

Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的上面。在上面的三类 Window 中,应用层 window 的层级范围是 1 ~ 99; 子 Window 的层级范围是 1000 ~ 1999;系统 Window 的层级范围是 2000 ~ 2999;这些层级范围对用着 WindowManger.LayoutParams 的 type 参数,如果想要 Window 位于所有 Window 的最顶层,那么采用最大层级就行了。系统层级有很多值,一般我们选择 TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERRO 就行。如我们可以指定:

layoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;

同时需要声明权限:


WindowManager 所提供的功能很简单,常用的就是添加 View、更新 View 、删除 View。这三个方法定义在 ViewManager 中,而 WindowManager 继承了 ViewManager。

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

对于我们来说,大部分 Window 的操作都可以通过这三个方法来实现,如拖动 View 的效果,就是根据手指的位置来设置 LayoutParams 中的 x 和 y 的值就行了。如:

       button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {

                int rawX = (int) event.getRawX();
                int  rawY = (int) event.getRawY();

                switch (event.getAction()){
                    case MotionEvent.ACTION_MOVE:{
                        layoutParams.x = rawX;
                        layoutParams.y = rawY;
                        manager.updateViewLayout(button,layoutParams);
                    }
                    break;

                    default:
                        break;
                }
                return false;
            }
        });

二、Window 的内部机制

   Window 是一个抽象的概念,每一个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不是实际存在的,而是以 View的形式表现出来。在实际使用中不能直接访问 Window ,对 Window 的访问必须通过 WindowManager。

2.1 Window 的添加过程

Window 的添加过程需要通过 WindowManager 的 addView 来实现,WindowManager 是一个接口。它的真正实现是 WindowManagerImpl 类。在 WindowManagerImpl 的三大操作的实现如下:

 */
public final class WindowManagerImpl implements WindowManager {
    
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;
    private IBinder mDefaultToken;
    public WindowManagerImpl(Context context) {
        this(context, null);
    }
    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }
    public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
        return new WindowManagerImpl(displayContext, mParentWindow);
    }
 
    public void setDefaultToken(IBinder token) {
        mDefaultToken = token;
    }
    @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);
    }
    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }
    ...

}

可以发现,WindowManagerImpl 并没有直接实现 Window 的三大操作,而是全部交给了 WindowManagerGlobal 来处理,WindowManagerGlobal 以工厂的形式向外提供自己的实例,在 WindowManagerGlobal 中有如下一段代码:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

WindowManagerImpl 的这种工作模式是典型的桥接模式,将所有的操作全部委托给 WindowManagerGlobal 来实现。
addView 的实现如下:

   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;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent and we're running on L or above (or in the
            // system context), assume we want hardware acceleration.
            final Context context = view.getContext();
            if (context != null
                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                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);
                    }
                }
            }
            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 {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

如上代码,addView 主要分为如下几步:

  • 检查参数是否合法,如果是子 Window 那么还需要调整一下布局参数
  • 创建 ViewRootImpl 并将 View 添加到列表中。
  • 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程。
    这个过程由 ViewRootImpl 的 setView 方法来完成。其代码如下:
//frameworks/base/core/java/android/view/ViewRootImpl.java
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
           ...
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } 
                ...
    }

这里主要就是调用了mWindowSession的addToDisplay方法。mWindowSession是IWindowSession类型的,它是一个Binder对象,用于进行进程间通信,IWindowSession是Client端的代理,它的Server端的实现为Session,此前包含ViewRootImpl在内的代码逻辑都是运行在本地进程的,而Session的addToDisplay方法则运行在WMS所在的进程。

//frameworks/base/services/core/java/com/android/server/wm/Session.java
 @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

这样一来,Window 的添加请求就交给 WindowManagerService 去处理了,在 WindowManagerService 内部为每一个应用保留一个单独的 Session。具体的添加过程这里不再分析。

2.2 Window 的删除过程

Window 的删除过程和添加过程一样,都是先通过 WindowManagerImpl 后,再通过 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);
        }
    }

在 removeView 中,先通过 findViewLocked 来查找待删除的 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);
            }
        }
    }

removeViewLocked 是通过 ViewRootImpl 来完成删除操作的。在 WindowManager 中提供了两种接口:removeView 和 removeViewImmediate,分别表示同步和异步删除。这里主要说异步删除的过程,具体的删除由 ViewRootImpl 的 die 方法来完成。在异步删除的情况下,die 方法只是发送了一个请求删除的消息后就立刻返回了,这个时候 View 并没有完成删除操作,所以最后会将其添加到 mDyingViews 中,mDyingViews 表示待删除的 View 列表。ViewRootImpl 的 die 方法如下:

  boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

在 die 方法内部只是做了简单的判断,如果是异步操作,那么就发送一个 MSG_DIE 的消息,ViewRootImpl 中的 Handler 会处理此消息并调用 doDie 方法,如果是同步删除,那么就不发消息直接调用 doDie 方法。而 doDie 内部会调用 dispatchDetachedFromWindow 方法,


    void dispatchDetachedFromWindow() {
        mFirstInputStage.onDetachedFromWindow();
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }

        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();

        destroyHardwareRenderer();

        setAccessibilityFocus(null, null);

        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;

        mSurface.release();

        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }

        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }

        mDisplayManager.unregisterDisplayListener(mDisplayListener);

        unscheduleTraversals();
    }

这个方法主要实现了四件事情:

  • 垃圾回收工作,如清除数据,移除回调。
  • 通过 Session 的 remove 方法来移除 Window。
  • 调用 View 的 dispathcDetachedFromWindow 方法。
  • 调用 WindowManagerGloabl 的 doRemoveView 方法刷新数据。
2.3 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);
        }
    }

这里主要就是用新的 LayoutParams 替换掉旧的 LayoutParams,接着更新 ViewRootImpl 中的 LayoutParams,这一步是通过 ViewRootImpl 的 setLayoutParams 方法来实现的。在 ViewRootImpl 中会通过 scheduleTraversals 方法来对 View 重新布局,包括测量、布局、重绘这三个火车。除了 View 本身的重绘之外,ViewRootImpl 还会通过 WindowSession 来更新 Window 的视图。这个过程由 WindowManagerService 的 relayoutWIndow() 来实现,是一个 IPC 过程。

三、 Window 的创建过程

这里我们需要知道的是,在 Android 系统中,View 是 Android 中的视图的呈现方式,但是 View 不能单独存在,必须依附到 Window 上,也就是有视图的地方,就有 Window ,如 Activity、Dialog、Toast 以及 PopupWindow 等。

3.1 Activity 的 Window 的创建过程

要分析 Activity 中的 Window 的创建过程,就必须要了解 Acitvity 的启动过程,这里大致介绍一下就是,Activity 的启动过程最终室友 ActivityThread 中的 performLaunchAcitvity() 来完成整个启动过程的,这个方法内部会通过类加载器创建 Activity 的实例,并调用 attach 方法为其关联运行过程中的上下文环境。
ActivityThread#performLaunchActivity() 代码如下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    //...
    //通过类加载器创建Activity实例
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent);
   //...

    Application app = r.packageInfo.makeApplication(false, mInstrumentation);

    Context appContext = createBaseContextForActivity(r, activity);
    CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
    Configuration config = new Configuration(mCompatConfiguration);

    //关联运行过程中所依赖的一系列上下文环境变量
    activity.attach(appContext, this, getInstrumentation(), r.token,
            r.ident, app, r.intent, r.activityInfo, title, r.parent,
            r.embeddedID, r.lastNonConfigurationInstances, config,
            r.voiceInteractor);

    mActivities.put(r.token, r);
    return activity;
}

在attach方法里,系统会创建Activity所属的Window对象并为其设置回调接口。
attach 的实现如下:

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, IVoiceInteractor voiceInteractor) {

    attachBaseContext(context);
    mFragments.attachActivity(this, mContainer, null);

    //创建Window对象
    mWindow = PolicyManager.makeNewWindow(this);
    //设置回调 比如我们熟悉的onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent
    //[重要] 当Window接收到外界的状态改变时就会调用Activity实现的回调
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);

    //赋值
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    mUiThread = Thread.currentThread();
    mMainThread = aThread;
    mInstrumentation = instr;
    mToken = token;
    mIdent = ident;
    mApplication = application;
    mIntent = intent;
    mComponent = intent.getComponent();
    mActivityInfo = info;
    mTitle = title;
    mParent = parent;
    mEmbeddedID = id;
    mLastNonConfigurationInstances = lastNonConfigurationInstances;

    //...       

    //设置WindowManager
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
}

再来看 PolicyManager.makeNewWindow(this),PilicyManager的真正实现类是Policy类。
位于source\frameworks\base\policy\src\com\android\internal\policy\impl\Policy.java

public Window makeNewWindow(Context context) {
    return new PhoneWindow(context);
}  

Window初始完毕后,再来看Activity的视图是如何依附在Window上的。Activity的视图通过setContent方法提供。

public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

可以发现,Activity的setContentView将具体实现交由Window处理。
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.
    //1.创建DecorView (如果没有创建的话)
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
        //2.将View添加到DecorView的mContentParent中
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    //3.回调Activity的onContentChanged()通知Activity视图已经发生改变
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

经过setContentView方法,DecorView已经被创建并初始化完毕,Activity的布局文件也已经成功添加到DecorView的mContentParent中,但这个时候DecorView还没有被WindowManager添加到Window中。

  • Activity的attach()中,Window被创建并初始化
  • 在Activity的setContentView中 (PhoneWindow#setContentView),DecorView被创建 (如果没被创建的话)
  • 而在ActivityThread#handleResumeActivity首先会调用Activity的onResume方法中,接着会先将DecorView设为不可见(INVISIBLE),然后会调用Activity的makeVisible(),在makeVisible()中,将DecorView添加到Window并置为Visible。
void makeVisible() {
    //将DecorView添加到Window
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    //将Activity的显示置为Visible
    mDecor.setVisibility(View.VISIBLE);
}  

正是在 makeVisible 方法中,DecorView 真正的完成了添加和显示过程,到这里 Activity 的视图才能被看到。

3.2 Dialog 的 Window 的创建过程

Dialog 的 Window 的创建过程和 Acivity 类似,主要有如下几个步骤:

1、创建 Window

Dialog 中的 Window的创建同样是通过 PolicyManager 的 makeNewWindow 来完成的,创建后的对象实际上就是 PhoneWindow,这个过程和 Acitivity 的 Window 的创建过程是一致的。这里不在说明了。
Dialog 的构造方法如下:

Dialog(Context context, int theme, boolean createContextThemeWrapper) {
    //...        
    mContext = context;

    mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
    //创建Window对象
    Window w = PolicyManager.makeNewWindow(mContext);
    mWindow = w;
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    w.setWindowManager(mWindowManager, null, null);
    w.setGravity(Gravity.CENTER);
    mListenersHandler = new ListenersHandler(this);
}
2、初始化 DecorView 并将 Dialog 的视图添加到 DevorView 中

这个过程也和 Activity 类似,都是通过 Window 去添加指定的布局文件。

public void setContentView(int layoutResID) {
    mWindow.setContentView(layoutResID);
}
3、将 DecorView 添加到 Window 中

在 Dialog 的 show 方法中,会通过 WindowManager 将 DecorView 添加到 Window 中,如下:

mWindowManager.addView(mDevor,1);
mShowing = true;

这里需要注意,普通的 Dialog 有一个特殊的地方,那就是必须采用 Activity 的 Context,如果采用 Application 的 Context,就会报错。原因是没有用 token 导致的,而应用 token 一般只有 Activity 所有,所以这里要用 Activity 做为 Contex 显示对话框。

最后,当Dialog dismiss时,会通过WindowManager来移除DecorView

@Override
public void dismiss() {
    if (Looper.myLooper() == mHandler.getLooper()) {
        dismissDialog();
    } else {
        mHandler.post(mDismissAction);
    }
}

void dismissDialog() {
    //...
    //移除DecorView
    mWindowManager.removeViewImmediate(mDecor);
   //...
}  
3.3 Toast 的 Window 的创建过程

Toast 和 Dialog 不同,它的工作过程就稍微显得复杂。首先 Toast 也是基于 Window 来实现的,但是由于 Toast 具有定时取消的功能,所以系统采用了 Handler。在 Toast 的内部有两类 IPC 过程,第一类就是 Toast 访问 NotificationManagerService,第二类是 NotificationManagerService 回掉 Toast 里的 TN 接口。
Toast提供了show和cancel分别用于显示和隐藏Toast 。
show() 和 cancel

public void show() {
    if (mNextView == null) {
        throw new RuntimeException("setView must have been called");
    }

    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;

    try {
        service.enqueueToast(pkg, tn, mDuration);
    } catch (RemoteException e) {
        // Empty
    }
}  

public void cancel() {
    mTN.hide();

    try {
        getService().cancelToast(mContext.getPackageName(), mTN);
    } catch (RemoteException e) {
        // Empty
    }
}

从上面代码可以看到,显示和隐藏 Toast 都需要通过 NMS 来实现,由于 NMS 运行在系统过程中,所以只能通过远程调用的方式来显示和隐藏 Toast 。需要注意的是 TN 这个类,是一个 Binder 类,在 Toast 和 NMS 进行 IPC 的过程中,当 NMS 处理 Toast 的显示和隐藏请求是都会回掉 TN 中的方法。这个时候,TN 运行在 Binder 线程池中,所以需要通过 hander 将其切换到当前线程中。
Toast 的显示过程,主要是调用了 NMS 中的 enqueueToast 方法。

/参数一:当前应用的包名
//参数二:远程回调
//参数三:Toast的时长
//enqueueToast首先将Toast封装为ToastRecord对象并将其添加到一个名为mToastQueue的队列中
service.enqueueToast(pkg, tn, mDuration);  

当ToastRecord被添加到mToastQueue中后,Inotifacationmanager就会通过showNextToastLacked方法来显示当前的Toast。

NotificationManagerService#showNextToastLocked() 如下:

void showNextToastLocked() {
    //获取下一个ToastRecord
    ToastRecord record = mToastQueue.get(0);
    while (record != null) {
        if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
        try {
            //record.callback就是Toast.java类中的TN (Binder) 对象
            record.callback.show();
            //发送延迟消息来移除toast
            //scheduleTimeoutLocked -> mHandler.sendMessageDelayed -> cancelToastLocked -> record.callback.hide();
            scheduleTimeoutLocked(record);
            return;
        } catch (RemoteException e) {
            Slog.w(TAG, "Object died trying to show notification " + record.callback
                    + " in package " + record.pkg);
            // remove it from the list and let the process die
            int index = mToastQueue.indexOf(record);
            if (index >= 0) {
                mToastQueue.remove(index);
            }
            keepProcessAliveLocked(record.pid);
            if (mToastQueue.size() > 0) {
                record = mToastQueue.get(0);
            } else {
                record = null;
            }
        }
    }
}  

可以发现,Toast的显示和移除都是通过Toast的TN类(Binder对象)来完成的。

Toast内部类TN 如下:

private static class TN extends ITransientNotification.Stub {
    final Runnable mShow = new Runnable() {
        @Override
        public void run() {
            handleShow();
        }
    };

    final Runnable mHide = new Runnable() {
        @Override
        public void run() {
            handleHide();
            // Don't do this in handleHide() because it is also invoked by handleShow()
            mNextView = null;
        }
    };

    @Override
    public void show() {
        if (localLOGV) Log.v(TAG, "SHOW: " + this);
        mHandler.post(mShow);
    }

    @Override
    public void hide() {
        if (localLOGV) Log.v(TAG, "HIDE: " + this);
        mHandler.post(mHide);
    }

    //...
}  

TN 中的 show 和 hide 方法对于的 Runnable 如下:

public void handleShow() {
        //...
        mWM = (WindowManager)context.getSystemService
        //...
        if (mView.getParent() != null) {
            mWM.removeView(mView);
        }
        mWM.addView(mView, mParams);
        //...
    }
}  

public void handleHide() {
    if (mView != null) {
        if (mView.getParent() != null) {
            mWM.removeView(mView);
        }
        mView = null;
    }
}





《Android 开发艺术探索》 学习整理

你可能感兴趣的:(理解 Window 和 WindowManager)