美文网首页Android 开发艺术探索读书笔记Android开发Android知识
Android 开发艺术探索读书笔记 8 -- 理解 Windo

Android 开发艺术探索读书笔记 8 -- 理解 Windo

作者: 开心wonderful | 来源:发表于2017-08-24 15:06 被阅读36次

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

    • 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 的依赖关系。

    本篇文章就介绍到这。

    相关文章

      网友评论

        本文标题:Android 开发艺术探索读书笔记 8 -- 理解 Windo

        本文链接:https://www.haomeiwen.com/subject/cnqddxtx.html