Android图形系统(一)-Window加载视图过程

本篇开始进行了新的专题:绘制优化,初步打算分两部分来写,一部分是原理机制篇,做优化,你原理机制都不清楚谈何优化,所以知识储备是十分有必要的,另外一部分就是优化实践篇。(先是这么计划着,之后看是否会再调整结构)

那么本篇文章就开启原理机制部分的总结,打算先捋一下window的加载视图过程。

话不多说,进入主题。

一、了解整体视图关系

Activity: 本身不是窗口,也不是视图,它只是窗口的载体,一方面是key、touch事件的响应处理,一方面是对界面生命周期做统一调度

Window: 一个顶级窗口查看和行为的一个抽象基类。它也是装载View的原始容器, 处理一些应用窗口通用的逻辑。使用时用到的是它的唯一实现类:PhoneWindow。Window受WindowManager统一管理。

DecorView: 顶级视图,一般情况下它内部会包含一个竖直方向的LinearLayout,上面的标题栏(titleBar),下面是内容栏。通常我们在Activity中通过setContentView所设置的布局文件就是被加载到id为android.R.id.content的内容栏里(FrameLayout)。

二、window的类型

添加窗口是通过WindowManagerGlobal的addView方法操作的,这里有三个必要参数:view,params,display。

display : 表示要输出的显示设备。

view : 表示要显示的View,一般是对该view的上下文进行操作。(view.getContext())

params : 类型为WindowManager.LayoutParams,即表示该View要展示在窗口上的布局参数。有2个比较重要的参数:flags,type

-flags:表示Window的属性:

flags 意义
FLAG_NOT_FOCUSABLE 表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层具有焦点的Window。
FLAG_NOT_TOUCH_MODAL 系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理,这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法接收到单击事件。
FLAG_SHOW_WHEN_LOCKED 开启此模式可以让Window显示在锁屏的界面上。

-type: 表示窗口的类型(具体值太多了就不一一列举了,具体可以去WindowManager的LayoutParams看详细类型描述):

window类型 特点 层级范围 典型window代表
system window 独立存在 2000~2999 Toast 、警告提示window、键盘window等
application window 属于应用的窗口 1~99 对应的就是activity的window
sub window 需要依附于父window,不能独立存在 1000~1999 Dialog 、 popupWindow

这个type层级到底有什么作用:
Window是分层的,每个Window都有对应的z-ordered,(z轴,从1层层叠加到2999,你可以将屏幕想成三维坐标模式)层级大的会覆盖在层级小的Window上面。如果想要Window位于所有Window的最顶层,那么采用较大的层级即可。另外有些系统层级的使用是需要声明权限的。

那么照这么说,最底层的应该是应用window,子window在其上,系统window在最上面,这也符合视图展示的预期效果。

另外WindowManager的LayoutParams中还有个token必须要提一嘴:主要作用是为了维护activity和window的对应关系。

三、window的创建

讲window的创建过程,那么肯定得了解activity的启动流程,但是在这里不详细说activitiy的启动流程了,因为后面会有计划单独拎出四大组件开篇章来讲解启动流程。

那么简单的先上个图了解下activity的启动流程(借用辉辉的图):

对于window的创建,我们就从handleLaunchActivity开始,开始看源码吧:

//ActivityThread
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ``````
    //获取WindowManagerService的Binder引用(proxy端)。
    WindowManagerGlobal.initialize();
    //创建activity,调用attach方法,然后调用Activity的onCreate,onStart,onResotreInstanceState方法
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        ``````
        //会调用Activity的onResume方法.
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
        ``````
    }
}

主要看 Activity a = performLaunchActivity(r, customIntent);方法,关注Activity的attach方法:

inal 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) {
        //绑定上下文
        attachBaseContext(context);
        //创建Window, PhoneWindow是Window的唯一具体实现类
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        ``````
        //设置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());
        }
        //创建完后通过getWindowManager就可以得到WindowManager实例
        mWindowManager = mWindow.getWindowManager();//其实它是WindowManagerImpl
    }

这里创建了一个PhoneWindow对象,并且实现了Window的Callback接口,这样activity就和window关联在了一起,并且通过callback能够接受key和touch事件。

此外,初始化且设置windowManager。每个 Activity 会有一个 WindowManager 对象,这个 mWindowManager 就是和 WindowManagerService 进行通信,也是 WindowManagerService 识别 View 具体属于那个 Activity 的关键,创建时传入 IBinder 类型的 mToken。

mWindow.setWindowManager(..., mToken, ..., ...)

我们从window的setWindowManager方法出发,很容易找到WindowManager这个接口的具体的实现是WindowManagerImpl。

四、window添加view过程

我们前面知道PhoneWindow对View来说更多是扮演容器的角色,而真正完成把一个 View,作为窗口添加到 WMS 的过程是由 WindowManager 来完成的。而且从上面创建过程我们知道了WindowManager 的具体实现是 WindowManagerImpl。

那么我们继续来跟代码:

从上面handleLaunchActivity的代码中performLaunchActivity后面,有个handleResumeActivity,从名字也能看出,跟activity onResume相关。进去看看:

//ActivityThread
final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        //把activity数据记录更新到ActivityClientRecord
        ActivityClientRecord r = mActivities.get(token);
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
            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;
             ``````
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//把decor添加到窗口上(划重点)
                }
            }
                //屏幕参数发生了改变
                performConfigurationChanged(r.activity, r.tmpConfig);
                WindowManager.LayoutParams l = r.window.getAttributes();
                    if (r.activity.mVisibleFromClient) {
                        ViewManager wm = a.getWindowManager();
                        View decor = r.window.getDecorView();
                        wm.updateViewLayout(decor, l);//更新窗口状态
                    }
                ``````
                if (r.activity.mVisibleFromClient) {
                    //已经成功添加到窗口上了(绘制和事件接收),设置为可见
                    r.activity.makeVisible();
                }
            //通知ActivityManagerService,Activity完成Resumed
             ActivityManagerNative.getDefault().activityResumed(token);
        }
    }

我们注意到这么几行代码:

            if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);/
                }

wm是activity getWindowManager()获取的,那不就是WindowManagerImpl的addView方法吗,追!

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

我们看到了又代理了一层:mGlobal, 它是谁? 如果有印象的会记得讲window类型的时候带了一嘴的WindowManagerGlobal ,那这个WindowManagerImpl原来也是一个吃空饷的家伙!对于Window(或者可以说是View)的操作都是交由WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。这种工作模式是桥接模式,将所有的操作全部委托给WindowManagerGlobal来实现。

在WindowManagerImpl的全局变量中通过单例模式初始化了WindowManagerGlobal,也就是说一个进程就只有一个WindowManagerGlobal对象。那看看它:

//WindowManagerGlobal
   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) {
            //调整布局参数,并设置token
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    //如果待删除的view中有当前view,删除它
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                }
                // The previous removeView() had not completed executing. Now it has.
               //之前移除View并没有完成删除操作,现在正式删除该view
            }

            //如果这是一个子窗口个(popupWindow),找到它的父窗口。
            //最本质的作用是使用父窗口的token(viewRootImpl的W类,也就是IWindow)
            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) {
                    //在源码中token一般代表的是Binder对象,作用于IPC进程间数据通讯。并且它也包含着此次通讯所需要的信息,
                    //在ViewRootImpl里,token用来表示mWindow(W类,即IWindow),并且在WmS中只有符合要求的token才能让
                    //Window正常显示.
                        panelParentView = mViews.get(i);
                    }
                }
            }
            //创建ViewRootImpl,并且将view与之绑定
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);//将当前view添加到mViews集合中,mViews存储所有Window对应的View
            mRoots.add(root);//将当前ViewRootImpl添加到mRoots集合中,mRoots存储所有Window对应的ViewRootImpl
            mParams.add(wparams);//将当前window的params添加到mParams集合中,存储所有Window对应的布局参数
        }
          ``````
            //通过ViewRootImpl的setView方法,完成view的绘制流程,并添加到window上。
            root.setView(view, wparams, panelParentView);
    }

最最重要的是:root.setView(view, wparams, panelParentView); 一方面触发绘制流程,一方面把view添加到window上。

讲setView之前先普及下WindowManager与WindowManagerService binder IPC的两个接口:

IWindowSession: 应用程序向WMS请求功能
实现类:Session
IWindow:WMS向客户端反馈它想确认的信息
实现类:W

下面看看ViewRootImpl的setView:

//ViewRootImpl
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                int res;
                 //在 Window add之前调用,确保 UI 布局绘制完成 --> measure , layout , draw
                requestLayout();//View的绘制流程
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    //创建InputChannel
                    mInputChannel = new InputChannel();
                }
                try {
                    //通过WindowSession进行IPC调用,将View添加到Window上
                    //mWindow即W类,用来接收WmS信息
                    //同时通过InputChannel接收触摸事件回调
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                }
                ``````
                    //处理触摸事件回调
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                ``````
    }

在ViewRootImpl的setView()方法里,

1.执行requestLayout()方法完成view的绘制流程(之后会讲)
2.通过WindowSession将View和InputChannel添加到WmS中,从而将View添加到Window上并且接收触摸事件。这是一次IPC 过程。
那么接下来看看这个IPC过程

//ViewRootImpl的setView方法中:
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
        getHostVisibility(), mDisplay.getDisplayId(),
        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
        mAttachInfo.mOutsets, mInputChannel);

mWindowSession:类型是interface IWindowSession

//WindowManagerGlobal
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService();
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}

我们看到了getWindowManagerService(); 获取了WMS , 那么再看下windowManager.openSession返回值就是sWindowSession

@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    Session session = new Session(this, callback, client, inputContext);
    return session;
}

IWindowSession的真正实现类是Session,他是一个Binder. 那么Session的addToDisplay:

@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);
}

从这知道了,最终是WMS执行addWindow操作.

下面一张图总结下:

图出处:https://blog.csdn.net/freekiteyu/article/details/79408969

WMS执行addWindow部分代码有点多,本篇就不铺开说了,不然篇幅就太长了,之后再说,看下如下的流程图:

图出处:https://www.jianshu.com/p/effaff9ab9f2

总结起来:WMS中 addWindow流程就几点:
1.通过type和 token对window进行分类和验证,确保其有效性。
2.构造WindowState与Window一一对应。
3.通过token对window进行分组。
4.对window分配层级。

那么到这里,window添加view的过程就结束了。

五、总结
下面用一张图来总结下Activity、PhoneWindow、 DecorView 、WindowManagerGlobal 、ViewRootImpl 、Wms 以及WindowState之间的关系:

图出处:https://blog.csdn.net/qian520ao/article/details/78555397?locationNum=7&fps=1

Activity在attach的时候,创建了一个PhoneWindow对象,并且实现了Window的Callback接口,这样activity就和window绑定在了一起,通过setContentView,创建DecorView,并解析好视图树加载到DecorView的contentView部分,WindowManagerGlobal一个进程只有唯一一个,对当前进程内所有的视图进行统一管理,其中包括ViewRootImpl,它主要做两件事情,先触发view绘制流程,再通过IPC 把view添加到window上。

另外这是添加视图的方法执行时序图:

图出处:https://blog.csdn.net/qian520ao/article/details/78555397?locationNum=7&fps=1

至于Window的删除和更新过程,举一反三,也是使用WindowManagerGlobal对ViewRootImpl的操作,最终也是通过Session的IPC跨进程通信通知到WmS。整个过程的本质都是同出一辙的。下一节接着讲DecorView布局的加载流程。

参考:
https://blog.csdn.net/qian520ao/article/details/78555397?locationNum=7&fps=1
https://www.jianshu.com/p/effaff9ab9f2
https://blog.csdn.net/freekiteyu/article/details/79408969

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