前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Window刨根问底

Window刨根问底

作者头像
码上积木
发布2020-12-11 10:12:46
4730
发布2020-12-11 10:12:46
举报
文章被收录于专栏:码上积木

前言

上篇文章中有说到Actiivity中window的一些知识,今天就和大家一起好好捋捋这个window。

Window是什么?在Android中都用到了哪些地方?

  • 首先,它是一个窗口,是Android中唯一的展示视图的中介,所有的视图都是通过Window来呈现的,无论是Activity,Dialog或Toast,他们的视图都是附加到WIndow上的,所以Window是View的直接管理者。
  • Window是一个抽象类,他的具体实现就是PhoneWindow。
  • Window的具体实现在WindowManagerService中,但是创建Window或者访问Window的操作都需要WindowManager。所以这就需要WindowManager和WindowManagerService进行交互,交互的方式就是通过IPC,具体涉及的参数就是token。
  • 每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl建立联系,所以Window并不是实际存在的,而是以View的形式存在。

涉及到Window的地方:

  • 事件分发机制。界面上事件分发机制的开始都是这样一个过程:DecorView——>Activity——>PhoneWindow——>DecorView——>ViewGroup。之前看过一个比较有趣的问题:事件到底是先到DecorView还是先到Window的?,其实是先到DecorView的,具体逻辑可以自己翻下源码,有机会也可以出篇文章讲讲~
  • 各种视图的显示。比如Activity的setContentView,Dialog,Toast的显示视图等等都是通过Window完成的。

Window的分层和类别?

  • 由于界面上有不止一个的Window,所以就有了分层的概念。每个Window都有自己对应的Window层级—z-ordered,层级大的会覆盖到层级小的上面,类似HTML中的z-index。

Window主要分为三个类别::

  • 应用Window。对应着一个Activity,Window层级为1~99,在视图最下层。
  • 子Window。不能单独存在,需要附属在特定的父Window之中(如Dialog就是子Window),Window层级为1000~1999。
  • 系统Window。需要声明权限才能创建的Window,比如Toast和系统状态栏,Window层级为2000~2999,处在视图最上层。

Window的内部机制—添加、删除、更新。

  • 之前说过,Window的操作都是通过WindowManager来完成的,而WindowManager是一个接口,他的实现类是WindowManagerImpl,并且全部交给WindowManagerGlobal来处理。下面具体说下addView,updateViewLayout,和removeView。

1) addView

代码语言:javascript
复制
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
      //...
        ViewRootImpl root;
        View panelParentView = null;

            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
  • 首先,通过add方法修改了WindowManagerGlobal中的一些参数,比如mViews—存储了所有Window所对应的View,mRoots——所有Window所对应的ViewRootImpl,mParams—所有Window对应的布局参数。
  • 其次,setView方法主要完成了两件事,一是通过requestLayout方法完成异步刷新界面的请求,进行完整的view绘制流程。其次,会通过WindowSession进行一次IPC调用,交给到WMS来实现Window的添加。

2)updateViewLayout

代码语言:javascript
复制
 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//...
        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);
        }
    }

这里更新了WindowManager.LayoutParamsViewRootImpl.LayoutParams,然后在ViewRootImpl内部同样会重新对View进行绘制,最后通过IPC通信,调用到WMS的relayoutWindow完成更新。

3)removeView

代码语言:javascript
复制
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);
        }
    }
    
    
    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
            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);
            }
        }
    }    

该方法中,通过view找到mRoots中的对应索引,然后同样走到ViewRootImpl中进行View删除工作,通过die方法,最终走到dispatchDetachedFromWindow()方法中,主要做了以下几件事:

  • 回调onDetachedFromeWindow。
  • 垃圾回收相关操作;
  • 通过Session的remove()在WMS中删除Window;
  • 通过Choreographer移除监听器

Window中的Token是什么?

代码语言:javascript
复制
public abstract class Window {
    private IBinder mAppToken;
}
  • 是Window类中的一个变量,是一个Binder对象。在Window中主要是实现WindowManagerService和应用所在的进程通信,也就是上文说到的WindowManager和WindowManagerService进行交互。
  • 是一个添加view的权限标识。拥有token的context可以创建界面、进行UI操作,而没有token的context如service、Application,是不允许添加view到屏幕上的。所以它存在的意义就是为了保护window的创建,也是为了防止Application或Service来做进行view或者UI相关的一些操作。

Activity,Dialog,Toast的Window创建过程

上篇文章说过Dialog的创建,先来回顾下:

1)Dialog

代码语言:javascript
复制

//构造函数
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
        //......
        //获取了WindowManager对象,mContext一般是个Activity,获取系统服务一般是通过Binder获取
        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        //创建新的Window
        Window w = PolicyManager.makeNewWindow(mContext);
        mWindow = w;
        //这里也是上方mWindow.getCallback()为什么是Activity的原因,在创建新Window的时候会设置callback为自己
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        //关联WindowManager与新Window,token为null
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    }



//show方法
    public void show() {
        //......
        if (!mCreated) {
            //回调Dialog的onCreate方法
            dispatchOnCreate(null);
        }
        //回调Dialog的onStart方法
        onStart();
        //获取当前新Window的DecorView对象
        mDecor = mWindow.getDecorView();
        WindowManager.LayoutParams l = mWindow.getAttributes();
        try {
            //把一个View添加到Activity共用的windowManager里面去
            mWindowManager.addView(mDecor, l);
            //......
        } finally {
        }
    }

可以看到一个Dialog从无到有经历了以下几个步骤:

  • 首先创建了一个新的Window,类型是PhoneWindow类型,与Activity创建Window过程类似,并设置setCallback回调。
  • 将这个新Window与从Activity拿到的WindowManager对象相关联,也就是dialog与Activity公用了同一个WindowManager对象。
  • show方法展示Dialog,先回调了Dialog的onCreate,onStart方法。
  • 然后获取Dialog自己的DecorView对象,并通过addView方法添加到WindowManager对象中,Dialog出现到屏幕上。

2)Activity

代码语言:javascript
复制
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, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        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);
        }
        mUiThread = Thread.currentThread();

        //...

        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;

        mWindow.setColorMode(info.colorMode);

        setAutofillOptions(application.getAutofillOptions());
        setContentCaptureOptions(application.getContentCaptureOptions());
    }
    
    

    public void setContentView(@LayoutRes int layoutResID) {
        // 交给Window
        getWindow().setContentView(layoutResID);
        // 创建ActionBar
        initWindowDecorActionBar();
    }    

关于Activity的启动流程,相比大伙都知道些,流程最后会走到ActivityThread中的performLauchActivity方法,然后会创建Activity的实例对象,并调用attach方法,也就是上述贴的源码。

在这个方法中,创建了新的Window对象,设置回调接口。这个回调接口主要就是用作Window在接收到外界状态改变的时候,就会回调给这个callback,比如onAttachedToWindow、dispatchTouchEvent方法等,这个上篇文章也有说过,事件分发的时候就是通过在DecorView中这个callback进行分发的。

然后view怎么显示到界面上的呢,Activity可没有show方法哦?其实就是通过setContentView方法。该方法主要做了以下几件事:

  • 创建DecorView,如果不存在的话。
  • 然后将xml中解析到的view添加到DecorView的mContentParent中,也就是布局为android.R.id.content的ContentView。
  • 回调onContentChanged方法,通知Activity视图已经发生改变。

贴张图:

到这里,一个有完整view结构的DecorView就创建出来了,但是它还没有被显示到手机界面上,也就是没有被添加到Window中。最后要调用了WMS的addView方法才会被用户真正看到:

代码语言:javascript
复制
    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

3)Toast

代码语言:javascript
复制
   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;
        final int displayId = mContext.getDisplayId();

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


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


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

Toast有点不同的在于,它内部维护了两个IPC通信,一个是NotificationManagerService,一个是回调TN接口。最终的实现都是走到TN.class的handleShowhandleHide方法,也就是addView和removeView。

总结

关于Window的内容太多了,今天好多地方都是简单的一笔介绍带过,其中还有很多很多逻辑,以后有时间再慢慢和大家聊聊,就这样了晚安。

参考

《Android开发艺术探索》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码上积木 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Window是什么?在Android中都用到了哪些地方?
  • Window的分层和类别?
  • Window的内部机制—添加、删除、更新。
  • Window中的Token是什么?
  • Activity,Dialog,Toast的Window创建过程
  • 总结
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档