浅析 android 应用界面的展现流程(三)会话的创建

在《浅析 android 应用界面的展现流程(二)布局与视图的创建》最后我们提到,ViewRootImpl.setView() 方法第一步是将客户端与服务端联系起来,这一步不仅仅包括了将客户端的 token 传到 WMS 中,也包括了在 WMS 中创建对应 WindowState、SurfaceSession 等对象,本文先从一些重要的对象的初始化开始,到 WindowState 对象的建立,逐步把应用程序与 WMS 之间的联系说清楚。

4. 与 WindowManagerService 建立连接

回顾一下 ViewRootImpl.setView() 的源码:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
            ......
            try {
                ......
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mInputChannel); // 与 WMS 通信添加该窗体对应的 WindowState
            } catch (RemoteException e) {
                ......
            }
            ......
        }
    }
}

4.1 Session 与 IWindowSession

以前做过 web 端的看官肯定对 Session 不会陌生,它代表一次登录的会话,在一次会话中我们拥有一个 session id,会话期间我们不需要再次登录,服务端通过客户端传过来的 session id 就可以找到对应会话场景,会话可能会有超时时间,超时后 session id 失效,就需要再次登录。

我们先来看下这个 mWindowSession 是什么,在 ViewRootImpl 的构造函数中:

public ViewRootImpl(Context context, Display display) {
    ......
    mWindowSession = WindowManagerGlobal.getWindowSession();
    ......
}

public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService();
                sWindowSession = windowManager.openSession(
                        imm.getClient(), imm.getInputContext());
                ......
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to open window session", e);
            }
        }
        return sWindowSession;
    }
}

可以看出这里会通过 Binder 调用 openSession() 在 WMS 端打开一个Session,并将其返回,服务端实现在 WindowManagerService.java 中:

public IWindowSession openSession(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, client, inputContext);
    return session;
}

其中 client 其实就是应用程序传过来的 IInputMethodClient Binder 对象,与 Session 绑定。这样一来,在客户端就可以用这个 IWindowSession 对象与 WMS 通信,比如下面要说的通过 addToDisplay 接口创建对应的 WindowState 对象,还有在下一篇文章中要说的 relayout()。(顺便吐个槽,在这里创建 session 没有鉴权,比如调用者进程是否是在 ActivityManagerService.mPidsSelfLocked 中之类的)。
这个 Session 与应用程序进程对应,在客户端,IWindowSession 也是单例。

4.2 addToDisplay

addToDisplay 会将 ViewRootImpl 中的 W 对象(IWindow.Stub)传到 WMS 端,这个对象是在 ViewRootImpl 构造函数中初始化,不仅仅提供了 WMS 与 Activity 通信的通道,而且在 WMS 中作为一个 token,是 WindowState 所在映射的 key。

下面来看 addToDisplay 的服务端实现:

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets,
        InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outInputChannel);
}

调用的实际是 ActivityManagerService.addWindow(),这个方法较为复杂,我们分开来说:

4.2.1 权限控制

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, InputChannel outInputChannel) {
    int[] appOp = new int[1];
1.  int res = mPolicy.checkAddPermission(attrs, appOp);
    if (res != WindowManagerGlobal.ADD_OKAY) {
        return res;
    }
    ......
}

mPolicy 其实就是 PhoneWindowManager,它是在 WindowManagerService 初始化时创建的,来看它的 checkAddPermission 方法:

public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
    int type = attrs.type;

    outAppOp[0] = AppOpsManager.OP_NONE;

    if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
            || type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
        return WindowManagerGlobal.ADD_OKAY;
    }
    String permission = null;
    switch (type) {
        case TYPE_TOAST:
            break;
        case TYPE_DREAM:
        case TYPE_INPUT_METHOD:
        case TYPE_WALLPAPER:
        case TYPE_PRIVATE_PRESENTATION:
            break;
        case TYPE_PHONE:
        case TYPE_PRIORITY_PHONE:
        case TYPE_SYSTEM_ALERT:
        case TYPE_SYSTEM_ERROR:
        case TYPE_SYSTEM_OVERLAY:
            permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
            outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
            break;
        default:
            permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
    }
    if (permission != null) {
        if (mContext.checkCallingOrSelfPermission(permission)
                != PackageManager.PERMISSION_GRANTED) {
            return WindowManagerGlobal.ADD_PERMISSION_DENIED;
        }
    }
    return WindowManagerGlobal.ADD_OKAY;
}

类型从 WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW 到 WindowManager.LayoutParams.LAST_SYSTEM_WINDOW 的窗口都是系统级的,如果类型不是系统级则直接返回 ADD_OKAY 表示允许,如果是系统级,则需要 SYSTEM_ALERT_WINDOW 或者 INTERNAL_SYSTEM_WINDOW 权限,然后靠 checkCallingOrSelfPermission 检验调用者权限。可以看出,当我们做一些悬浮窗一类的应用时,一般会把悬浮窗的类型设置为 TYPE_PHONE、TYPE_SYSTEM_ALERT 甚至 TYPE_SYSTEM_ERROR,需要 SYSTEM_ALERT_WINDOW 权限,就是在这里进行权限验证的了。

4.2.2 token

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, InputChannel outInputChannel) {
        ......
        boolean addToken = false;
2.      WindowToken token = mTokenMap.get(attrs.token);
        ......
}

mTokenMap 是以 WindowManager.LayoutParams.token 为 key,WindowToken 为 value 的映射。

attrs.token
WindowManager.LayoutParams.token 是在 WindowManagerGlobal 执行 addView 时赋值的:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ......
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    }
    ......
}

作为 Activity 绑定的 WindowManagerImpl,parentWindow 就是 Activity 所在的 PhoneWindow(具体流程可以回顾下《浅析 android 应用界面的展现流程(二)UI与布局的准备》),转到 Window.adjustLayoutParamsForSubWindow(Window.java):

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
    CharSequence curTitle = wp.getTitle();
    if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
        wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
        ......
    } else {
        if (wp.token == null) {
            wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
        }
        ......
    }
    ......
}

在这里 mContainer = null,返回的是 Window.mAppToken 了,这个 IBinder 对象其实就是在 Activity.attach 过程中塞进来,其实是 ActivityClientRecord.token,这其实是 AMS 传过来的 ActivityRecord.appToken(代码就不再展开了,大家可以顺着这条线捋下去)。

所以作为应用程序界面 Activity,WindowManager.LayoutParams.token 对应 ActivityRecord 中的 appToken:

final IApplicationToken.Stub appToken; // window manager token

ActivityRecord 是每个 Activity 启动时新建的代表一个运行的 Activity 信息的对象,appToken 是在 ActivityRecord 构造函数中初始化的,即:

appToken = new Token(this);

它其实是个 IApplicationToken 的服务端实现了,虽然这个名字看起来很像是一个 app 只有一个单例,但是它确实是与 ActivityRecord 对应的。

mTokenMap
mTokenMap 是在 startActivitiy 时添加对应的 WindowToken 的,本文不是讲述 Activity 启动流程的,在这里只来看 WindowToken 的初始化:
<center>

addAppToken.jpg

图 4.1 从 startActivitiy 到 addAppToken(Android 4.4)</center>
我们只来看第7步:addAppToken(WindowManagerService.java):

public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
        int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
        int configChanges) {
    if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
            "addAppToken()")) { // 鉴权
        throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
    }
    ......
    synchronized(mWindowMap) {
        AppWindowToken atoken = findAppWindowToken(token.asBinder());
        if (atoken != null) {
            Slog.w(TAG, "Attempted to add existing app token: " + token);
            return;
        }
        atoken = new AppWindowToken(this, token);
        atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
        atoken.groupId = taskId;
        atoken.appFullscreen = fullscreen;
        atoken.showWhenLocked = showWhenLocked;
        atoken.requestedOrientation = requestedOrientation;
        atoken.layoutConfigChanges = (configChanges &
                (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
        ......
        mTokenMap.put(token.asBinder(), atoken);
        ......
    }
}

可以发现,这个函数就是在鉴权之后创建一个 AppWindowToken 以此为 value,传进来的 IApplicationToken 对象为 key,也就是前面提到的 ActivityRecord 中的 appToken。

总之就是一句话:
在 WindowManagerService.addWindow 中传入 WindowManager.LayoutParams attrs,其中 attrs.token 就是在 startActivitiy 时向 mTokenMap 添加的 AppWindowToken 所对应的 key。所以作为 Activity 启动,在 addWindow 中:


WindowToken token = mTokenMap.get(attrs.token);

取得的 token 一定不为空。

4.2.3 创建 WindowState

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, InputChannel outInputChannel) {
        ......
3.      win = new WindowState(this, session, client, token,
                attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
        ......
}

WindowState 的构造函数,构造函数中关键字段的初始化都已经写到注释里了:

WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
       WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
       int viewVisibility, final DisplayContent displayContent) {
    mService = service; // wms
    mSession = s; // IWindowSession - Session,代表一个应用程序
    mClient = c; // 应用程序进程的一个类型为 W 的 Binder 本地对象,通过它可以与运行在应用程序进程这一侧的 Activity 组件进行通信
    mAppOp = appOp;
    mToken = token; // mTokenMap 中取得的,一个 Activity 对应一个,可以唯一标识一个窗口
    mOwnerUid = s.mUid; // 应用程序的 UID
    mWindowId = new IWindowId.Stub() { // 提供给外界的一些回调
        @Override
        public void registerFocusObserver(IWindowFocusObserver observer) {
            WindowState.this.registerFocusObserver(observer);
        }
        @Override
        public void unregisterFocusObserver(IWindowFocusObserver observer) {
            WindowState.this.unregisterFocusObserver(observer);
        }
        @Override
        public boolean isFocused() {
            return WindowState.this.isFocused();
        }
    };
    mAttrs.copyFrom(a); // addView 传进来的 WindowManager.LayoutParams 对象的拷贝
    mViewVisibility = viewVisibility; // 当前所创建的WindowState对象所描述的窗口视图的可见性
    mDisplayContent = displayContent; // 
    mPolicy = mService.mPolicy; 
    mContext = mService.mContext; // WMS 的 Context,当然 SystemServer 里就一个
    DeathRecipient deathRecipient = new DeathRecipient();
    mSeq = seq;
    mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
    if (WindowManagerService.localLOGV) Slog.v(
        TAG, "Window " + this + " client=" + c.asBinder()
        + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
    try {
        c.asBinder().linkToDeath(deathRecipient, 0);
    } catch (RemoteException e) {
        mDeathRecipient = null;
        mAttachedWindow = null;
        mLayoutAttached = false;
        mIsImWindow = false;
        mIsWallpaper = false;
        mIsFloatingLayer = false;
        mBaseLayer = 0;
        mSubLayer = 0;
        mInputWindowHandle = null;
        mWinAnimator = null;
        return;
    }
    mDeathRecipient = deathRecipient;

    if ((mAttrs.type >= FIRST_SUB_WINDOW &&
            mAttrs.type <= LAST_SUB_WINDOW)) {
        ......
    } else {
        // The multiplier here is to reserve space for multiple
        // windows in the same type layer.
        mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)
                * WindowManagerService.TYPE_LAYER_MULTIPLIER
                + WindowManagerService.TYPE_LAYER_OFFSET; // 窗口的基础 Z 轴位置
        mSubLayer = 0; // 如果不是子窗体,一直为0
        mAttachedWindow = null; // 如果不是子窗体,一直为 null
        mLayoutAttached = false; // 如果不是子窗体,一直为false
        mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
                || mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
        mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
        mIsFloatingLayer = mIsImWindow || mIsWallpaper;
    }

    WindowState appWin = this;
    while (appWin.mAttachedWindow != null) {
        appWin = appWin.mAttachedWindow;
    }
    WindowToken appToken = appWin.mToken;
    while (appToken.appWindowToken == null) {
        WindowToken parent = mService.mTokenMap.get(appToken.token);
        if (parent == null || appToken == parent) {
            break;
        }
        appToken = parent;
    }
    mRootToken = appToken;
    mAppToken = appToken.appWindowToken;

    mWinAnimator = new WindowStateAnimator(this); // 一个 WindowState 对应一个 WindowStateAnimator,动画相关的控制
    mWinAnimator.mAlpha = a.alpha; // 该窗体的 alpha 通道

    mRequestedWidth = 0;
    mRequestedHeight = 0;
    mLastRequestedWidth = 0;
    mLastRequestedHeight = 0;
    mXOffset = 0;
    mYOffset = 0;
    mLayer = 0;
    mInputWindowHandle = new InputWindowHandle(
            mAppToken != null ? mAppToken.mInputApplicationHandle : null, this,
            displayContent.getDisplayId());
}

4.2.4 WindowState 的调整

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, InputChannel outInputChannel) {
        ......
4.      mPolicy.adjustWindowParamsLw(win.mAttrs);
5.      win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

6.      res = mPolicy.prepareAddWindowLw(win, attrs);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        if (outInputChannel != null && (attrs.inputFeatures
                & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            String name = win.makeInputChannelName();
            InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
            win.setInputChannel(inputChannels[0]);
            inputChannels[1].transferTo(outInputChannel);

7.          mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
        }
        ......
}

步骤4:实际调用的是 PhoneWindowManager 的 adjustWindowParamsLw() 方法:

public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
    switch (attrs.type) {
        case TYPE_SYSTEM_OVERLAY:
        case TYPE_SECURE_SYSTEM_OVERLAY:
            // These types of windows can't receive input events.
            attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
            attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
            break;
    }
}

可以看出就是根据窗口类型调整属性,凡是 TYPE_SYSTEM_OVERLAY 和 TYPE_SECURE_SYSTEM_OVERLAY 类型的均无焦点。

步骤5:设置这个窗口是否是只对自己UID可见。

步骤6:根据窗口的类型鉴权。

步骤7:创建IO输入事件连接通道的,以便正在增加的窗口可以接收到系统所发生的键盘和触摸屏事件,可以参考罗老师的这篇文章

这三步基本跟我们的主线 - 创建session没有太大的关系,逻辑也比较简单,各位看官可以自行分析下。

4.2.5 attach

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, InputChannel outInputChannel) {
        ......
8.      win.attach(); // SurfaceSession
        mWindowMap.put(client.asBinder(), win); // 把 IWindow 和 WindowState 关联起来
        ......
}

下面通过 WindowState.attach() 方法创建一个关联的SurfaceSession对象,以便可以用来和SurfaceFlinger服务通信(WindowState.java):

void attach() {
    if (WindowManagerService.localLOGV) Slog.v(
        TAG, "Attaching " + this + " token=" + mToken
        + ", list=" + mToken.windows);
    mSession.windowAddedLocked();
}

展开 Session.windowAddedLocked()(Session.java):

void windowAddedLocked() {
    if (mSurfaceSession == null) {
        if (WindowManagerService.localLOGV) Slog.v(
            WindowManagerService.TAG, "First window added to " + this + ", creating SurfaceSession");
        mSurfaceSession = new SurfaceSession();
        if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
                WindowManagerService.TAG, "  NEW SURFACE SESSION " + mSurfaceSession);
        mService.mSessions.add(this);
    }
    mNumWindow++;
}

我们发现在 WindowState 创建成功后并执行 windowAddedLocked 创建 SurfaceSession 后才会把 Session 加到 WindowManagerService.mSessions 中,展开 SurfaceSession 的构造函数:

public SurfaceSession() {
    mNativeClient = nativeCreate();
}

nativeCreate() 的实现在 /framework/base/core/jni/android_view_SurfaceSession.cpp:

static jint nativeCreate(JNIEnv* env, jclass clazz) {
    SurfaceComposerClient* client = new SurfaceComposerClient();
    client->incStrong((void*)nativeCreate);
    return reinterpret_cast<jint>(client);
}

可以发现,SurfaceSession 实际对应到C++ 层中的一个 SurfaceComposerClient 对象,nativeCreate() 返回的就是 SurfaceComposerClient 对象的指针。SurfaceComposerClient 对象主要是用来在应用程序进程和 SurfaceFlinger 服务之间建立连接的,以便应用程序进程可以为运行在它里面的应用程序窗口请求SurfaceComposerClient创建绘制表面(Surface)的操作等。

4.3 小结

至此,与 WMS 的连接已经过完了,总结一下,就是:Session/IWindowSession - SurfaceSession - SurfaceComposerClient(进程内单例),W/IWindow - IApplicationToken - AppWindowToken/WindowToken(一个窗体一个) 的对应关系。

下一篇文章我们将会继续展开《浅析 android 应用界面的展现流程(二)UI与布局的准备》中提到的 ViewRootImpl.setView 的第二步:requestLayout()。

标签: none

已有 2 条评论

  1. gaolf gaolf

    首先表示感谢,我也在看andorid显示部分的逻辑,跟着这里的流程走轻松很多,提个问题:
    [[[
    4. 与 WindowManagerService 建立连接
    回顾一下 WindowManagerImpl.setView() 的源码:
    ]]]
    这里似乎有问题,setView方法应该是ViewRootImpl类中的方法?也可能是我的代码是16年的代码,和当时的有出入?

    1. 感谢指出,确实是笔误了 (^_^)

添加新评论