Android 开发艺术探索读书笔记 8 -- 理解 Window 和 WindowManager

本篇文章主要介绍以下几个知识点:

  • Window 和 WindowManager
  • Window 的内部机制
  • Window 的创建过程
hello,夏天 (图片来源于网络)

Window 表示一个窗口的概念,是一个抽象类,其具体实现是 PhoeWindow。

WindowManager 是外界访问 Window 的入口,创建一个 Window 需要通过 WindowManager。

Android 中的所有视图都是通过 Window 来呈现的(如 Activity、Dialog、Toast 等),Window 是 View 的直接管理者。

8.1 Window 和 WindowManager

下面代码将一个 Button 添加到屏幕坐标为(100,300)的位置上,演示了通过 WindowManager 添加 Window 的过程:

mFloatingButton = new Button(this);
mFloatingButton.setText("button");
mLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
// Flags 参数表 Window 的属性,常用的有如下:
// 1. FLAG_NOT_FOCUSABLE  表示 Window 不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层具有焦点的 Window
// 2. FLAG_NOT_TOUCH_MODAL  此模式下,系统会将当前 Window 区域外的单击事件传递给底层的 Window,当前 Window 区域内的单击事件则自己处理。若不开启此标记则其他 Window 将无法收到单击事件。
// 3. FLAG_SHOW_WHEN_LOCKED  开启此模式可以让 Window 显示在锁屏的界面上
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL ;
mLayoutParams.gravity = Gravity.LEFT;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mWindowManager.addView(mFloatingButton, mLayoutParams);

WindowManager 继承了 ViewManager, 所提供的功能常用的有以下3个方法:

public interface ViewManager{
    // 添加 View
    public void addView(View view, ViewGroup.LayoutParams params);
    // 更新 View
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    // 删除 View
    public void removeView(View view);
}

根据上面的方法,实现拖动 Window 的效果只需根据手指的位置来设定 LayoutParams 中的 x 和 y 的值即可:

  1. 给 View 设置 onTouchListener
  2. onTouch 方法中不断更新 View 的位置。

代码如下:

public boolean onTouch(View v, MotionEvent event){
    int rawX = (int)event.getRawX();
    int rawY = (int)event.getRawY();
    switch(event.getAction()){
        case MotionEvent.ACTION_MOVE:
            mLayoutParams.x = rawX;
            mLayoutParams.y = rawY;
            mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
           break;
        default:
           break;
    }
    return false;
}

8.2 Window 的内部机制

Window 是一个抽象的概念,每一个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,即 Window 是以 View 的形式存在。

下面从添加、删除、更新来分析 Window 的内部机制。

8.2.1 Window 的添加过程

Window 的添加需通过接口 WindowManager 的 addView 来实现,而其真正的实现类是 WindowManagerImpl

public final class WindowManagerImpl implements WindowManager {
    // 将所有的操作全部委托给 WindowManagerGlobal 来实现
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;

    . . .

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        mGlobal.updateViewLayout(view, params);
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    . . .
}

可以看出 WindowManagerImpl 并没直接实现 Window 的三大操作,而是全交给了 WindowManagerGlobal 来处理:

public final class WindowManagerGlobal {

    // 存储所有 Window 所对应的 View
    private final ArrayList<View> mViews = new ArrayList<View>();
    // 存储所有 Window 所对应的 ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    // 存储所有 Window 所对应的布局参数
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
    // 存储那些正在被删除的 View 对象
    private final ArraySet<View> mDyingViews = new ArraySet<View>();

    . . .

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 1. 检查参数是否合法,如果是子 Window 那么还需要调整一些布局参数
        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);
        }
       
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {

            . . .

            // 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) {
            . . .
        }
    }
 }

上面第3步由 ViewRootImplsetView 方法来完成:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                . . .

                // 1. requestLayout 来完成异步刷新请求
                requestLayout();
                . . .
                // 2. 通过 WindowSession 最终来完成 Window 的添加过程
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();

                    // mWindowSession 的类型是 IWindowSession,是一个 Binder 对象,
                    // 真正的实现类是 Session,也就是 Window 的添加过程是一次 IPC 调用
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                   . . .
                } 
                . . .
            }
        }
    }

其中 setView 内部的 requestLayout 方法如下:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            // scheduleTraversals 是 View 的绘制入口
            scheduleTraversals();
        }
    }

上面 setView 内部的 mWindowSession,即 Session,内部会通过 WindowManagerService 来实现 Window 的添加,具体怎么添加这里就不分析了。

以上就是 Window 的添加流程。

8.2.2 Window 的删除过程

Window 的删除过程和添加过程一样是通过WindowManagerGlobal 来实现的, 其removeView 如下:

public final class WindowManagerGlobal {

    . . .

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

        synchronized (mLock) {
            // 1. 通过 findViewLocked 来查找待删除的 View 的索引(查找过程就是建立的数组遍历)
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            // 2. 调用 removeViewLocked 来做进一步的删除
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("...");
        }
    }
 }

上面的 removeViewLocked 如下:

    private void removeViewLocked(int index, boolean immediate) {
        // removeViewLocked 是通过 ViewRootImpl 来完成删除操作的
        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());
            }
        }
       // 具体的删除操作由 ViewRootImpl 的 die 方法完成
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

上面 ViewRootImpldie 方法如下:

   /**
     * @param immediate True, do now if not in traversal. False, put on queue and do later.
     * @return True, request has been queued. False, request has been completed.
     */
    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 方法
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "... ");
        }
        // 若是异步删除,则发送 MSG_DIE 消息,ViewRootImpl 中的 Handler 会处理此消息并调用 doDie 方法
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

上面 doDie 方法在内部会调用方法 dispatchDetachedFromWindow 来实现真正的删除 View 逻辑。

方法 dispatchDetachedFromWindow 主要做四件事:

(1)垃圾回收相关的工作

(2)通过 Session 的 remove 方法删除 Window

(3)在内部调用 View 的 onDetachedFromWindow() 以及 onDetachedFromWindowInternal(),做资源回收工作。

(4)调用 WindowManagerGlobaldoRemoveView 方法刷新数据。

8.2.3 Window 的更新过程

Window 的更新过程还是看 WindowManagerGlobal 中的 updateViewLayout 方法,如下:

public final class WindowManagerGlobal {

    . . .

    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;

        // 1. 更新 View 的 LayoutParams
        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            // 2. 更新 ViewRootImpl 中的 LayoutParams
            // ViewRootImpl 会对 View 重新布局、更新 Window 的视图
            root.setLayoutParams(wparams, false);
        }
    }
 }

8.3 Window 的创建过程

上面分析可知,View 是 Android 中的视图的呈现方式,但 View 不能单独存在,必须附着在 Window 上面,因此有视图的地方就有 Window。

下面分析一些视图元素中的 Window 的创建过程。

8.3.1 Activity 的 Window 创建过程

要分析 Activity 的 Window 创建过程就必须了解 Activity 的启动过程。

Activity 的启动过程很复杂,最终会由 ActivityThread 中的 performLaunchActivity() 来完成整个启动过程,代码如下:

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
   
        . . .

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            // 通过类加载器创建 Activity 的实例对象
            activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
            . . .
        } catch (Exception e) { . . . }

        try {
            . . .

            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (DEBUG_CONFIGURATION) Slog.v(TAG, ". . . ");

                // 调用其 attach 方法为其关联运行过程中所依赖的一系列上下文环境变量
                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);

               . . .
            }
        } catch (Exception e) { . . . } 

        return activity;
    }

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

// 创建 Window 对象
mWindow = PolicyManager.makeNewWindow(this);
// 实现 Window 的 Callback 接口
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
. . .

上面 Activity 的 Window 是通过 PolicyManager 的一个工厂方法来创建的,其 makeNewWindow 如下:

public Window makeNewWindow(Context context){
    // Window 的具体实现是 PhoneWindow
    return new PhoneWindow(context);
}

到这里 Window 已经创建完了,接下来分析 Activity 的视图是如何附属在 Window 上。

由于 Activity 的视图由 setContentView 方法提供,只需看其实现即可:

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

从上面代码可知 Activity 将具体实现交给 Window 处理,而 Window 的具体实现是 PhoneWindowPhoneWindowsetContentView 方法大致遵循如下步骤:

1. 若无 DecorView,则创建它

DecorView 是一个 FrameLayout,是 Activity 中的顶级 View,其创建过程由 installDecor 方法来完成,此方法内部通过 generateDecor 方法来直接创建 DecorView

protected DecorView generateDecor(){
    return new DecorView(getContext(), -1);
}

2. 将 View 添加到 DecorViewmContentParent

步骤1已创建和初始化 DecorView,接下来直接将 Activity 的视图添加到 DecorViewmContentParent 中即可:

mLayoutInflater.inflate(layoutResID, mContentParent);

3. 回调 Activity 的 onContentChanged 方法通知 Activity 视图已发生改变

由于 Activity 实现了 Window 的 Callback 接口,其布局文件已被添加到 DecorViewmContentParent 中,需要通知 Activity 做相应的处理:

final Callback cb = getCallback();
if(cb != null && !isDestroyed()){
    cb.onContentChanged();
}

经过上面3个步骤,DecorView 已被创建和初始完毕,Activity 的布局文件也添加到了 DecorViewmContentParent 中。

接下来在 ActivityThreadhandleResumeActivity 方法中会先调用 Activity 的 onResume 方法,再调用 Activity 的 makeVisible() 方法:

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

在上面 makeVisible 方法中,DecorView 真正地完成了添加和显示这两个过程,Activity 的视图才能被用户看到。

以上便是 Activity 中的 Window 的创建过程。

8.3.2 Dialog 的 Window 创建过程

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

1. 创建 Window

Dialog 中的 Window 的创建同样是通过 PolicyManagermakeNewWindow 方法来完成的,过程和 Activity 类似:

Dialog(Context context, int theme, boolean createContextThemeWrapper){
    . . .
    mWindowManger = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
    Window w = PolicyManage.makeNewWindow(mContext);
    mWindow = w;
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    . . .
}

2. 初始化 DecorView 并将 Dialog 的视图添加到 DecorView

这个过程也和 Acitivity 的类似,通过 Window 去添加指定的布局文件:

public void setContentView(int layoutResID){
    mWindow.setContentView(layoutResID);
}

3. 将 DecorView 添加到 Window 中并显示

在 Dialog 的 show 方法中,会通过 WindowManagerDecorView 添加到 Window 中:

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

以上便是 Dialog 的 Window 创建过程,和 Activity 的类似。

注:普通的 Dialog 必须采用 Activity 的 Context,若采用 Application 的 Context 会报错。

8.3.3 Toast 的 Window 创建过程

Toast 和 Dialog 不同,稍复杂。Toast 也是基于 Window 来实现的,由于具有定时取消功能,系统采用了 Handler。

具体的过程这里不再介绍了,有兴趣的可去看看书。本篇文章主要是对 Window 有一个更加清晰的认识,理解 Window 和 View 的依赖关系。

本篇文章就介绍到这。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容