Android 7.1 GUI系统-窗口管理WMS-窗口添加(三)

快来打我* 2022-06-04 10:35 353阅读 0赞

窗口的添加过程。

Android中窗口通常分为两大类,一是系统窗口,一是应用窗口。添加的过程上,WMS不会特别区分这两类窗口,只是在权限和层级有差别。

1)系统窗口的添加,以状态栏为例。

  1. private void addStatusBarWindow() @PhoneStatusBar.java{
  2. //把R.layout.super_status_bar资源inflate为View对象mStatusBarWindow,
  3. makeStatusBarView();
  4. mStatusBarWindowManager = new StatusBarWindowManager(mContext);
  5. mRemoteInputController = new RemoteInputController(mStatusBarWindowManager,
  6. mHeadsUpManager);
  7. //getStatusBarHeight()获取状态栏的高度。
  8. mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
  9. }

//添加statusbar view到WindowManager。

  1. public void add(View statusBarView, int barHeight) @StatusBarWindowManager.java{
  2. //设置窗口属性和类型。
  3. mLp = new WindowManager.LayoutParams(
  4. ViewGroup.LayoutParams.MATCH_PARENT,
  5. barHeight,
  6. WindowManager.LayoutParams.TYPE_STATUS_BAR,
  7. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  8. | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
  9. | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
  10. | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
  11. | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
  12. PixelFormat.TRANSLUCENT);
  13. mLp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
  14. mLp.gravity = Gravity.TOP;
  15. mLp.setTitle("StatusBar");
  16. mStatusBarView = statusBarView;
  17. //调用WindowManagerImpl.java的addView方法。
  18. mWindowManager.addView(mStatusBarView, mLp);
  19. }

先看下mWindowManager对象的获取,在StatusBarWindowManager的构造函数中有:

mWindowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

这里的Context.WINDOW_SERVICE字符串值:StringWINDOW_SERVICE = “window”;

//通过类名查找一个系统级别的service。

  1. public final <T> T getSystemService(Class<T> serviceClass) @Context.java{
  2. //子类会重写参数为string的 getSystemService方法,这里先把class映射到Service name,然后调用string参数的getSystemService。
  3. String serviceName = getSystemServiceName(serviceClass);
  4. return serviceName != null ? (T)getSystemService(serviceName) : null;
  5. }

//上面两个函数的实现在ContextImpl.java中。

  1. public String getSystemServiceName(Class<?> serviceClass) @ContextImpl.java{
  2. return SystemServiceRegistry.getSystemServiceName(serviceClass);
  3. }
  4. public Object getSystemService(String name) @ContextImpl.java{
  5. return SystemServiceRegistry.getSystemService(this, name);
  6. }

为了能够快速的获取常用的系统服务,Android把这些service放在两个MAP中,一个是:

HashMap,String> SYSTEM_SERVICE_NAMES = new HashMap,String>();,

映射的是class到String类型的serviceName;

一个是HashMap> SYSTEM_SERVICE_FETCHERS =newHashMap>();

映射是ServiceName到实际返回的Service对象。

//这两个map的初始化在SystemServiceRegistry的静态代码块中。

  1. Static @SystemServiceRegistry.java{
  2. //可以看到 Context.WINDOW_SERVICE对应的Service是 WindowManagerImpl,其他系统service也是类似的实现方式。
  3. registerService(Context.WINDOW_SERVICE, WindowManager.class,
  4. new CachedServiceFetcher<WindowManager>() {
  5. public WindowManager createService(ContextImpl ctx) {
  6. return new WindowManagerImpl(ctx);
  7. }});
  8. }

然后接着看addView的实现。WindowManagerImpl继承自WindowManager,又间接继承自ViewManager。

WindowManager.java这个接口是app跟系统的窗口管理员通信的接口,很多的窗口属性都是在这里定义的。

  1. public final class WindowManagerImpl implements WindowManager {
  2. public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
  3. mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
  4. }
  5. }

//mGlobal是WindowManagerGlobal.java类型的对象,是全局变量,也是单实例,所有的窗口都会在mGlobal中有记录。

  1. public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) @WindowManagerGlobal.java{
  2. //窗口属性
  3. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
  4. //ViewTree的管理者 ViewRootImpl。
  5. ViewRootImpl root;
  6. //遍历mViews列表,检查这个view是否已经添加过,如果已经添加过,禁止重复添加。
  7. int index = findViewLocked(view, false);
  8. //如果是一个子窗口,要找到它的父窗口。
  9. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
  10. wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
  11. final int count = mViews.size();
  12. for (int i = 0; i < count; i++) {
  13. //mWindow是ViewRootImpl中的变量,用于WMS访问app的接口。
  14. if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
  15. panelParentView = mViews.get(i);
  16. }
  17. }
  18. }
  19. //实例化 ViewRootImpl对象。
  20. root = new ViewRootImpl(view.getContext(), display);
  21. view.setLayoutParams(wparams);
  22. // mViews的元素对应了要添加的ViewTree, mRoots中元素对应了ViewTree的管理者, mParams中的元素对应了窗口的属性,同一个索引值在这三个列表中描述的是同一个对象。
  23. mViews.add(view);
  24. mRoots.add(root);
  25. mParams.add(wparams);
  26. }

//从ViewRootImpl的setView开始,将会跟WMS通信,主要分析窗口的添加,其他代码略。

  1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView)@ViewRootImpl.java {
  2. if (mView == null) {
  3. //一个ViewRootImpl对应一棵ViewTree,用mView保存这个ViewTree。
  4. mView = view;
  5. //执行第一次layout,在把window添加Window manager之前,因为WMS除了管理窗口,还负责分发事件,所以执行relayout是做好接收系统事件的准备。
  6. RequestLayout();
  7. //通过WindowSession向WMS申请添加窗口,IWindowSession是ViewRootImpl跟WMS通信的桥梁,由aidl接口描述,它是匿名的BinderServer,获取方式是通过实名的Binderserver类WindowManagerService的接口openSession实现。
  8. res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
  9. getHostVisibility(), mDisplay.getDisplayId(),
  10. mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
  11. mAttachInfo.mOutsets, mInputChannel);
  12. }
  13. }

IWindowSession对应Server端的实现是Session.java。

  1. public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
  2. int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
  3. Rect outOutsets, InputChannel outInputChannel) {
  4. return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
  5. outContentInsets, outStableInsets, outOutsets, outInputChannel);
  6. }

直接调用WindowManagerService的实现addWindow。

  1. public int addWindow(Session session, IWindow client, int seq,WindowManager.LayoutParams attrs, int viewVisibility, int displayId,Rect outContentInsets, Rect outStableInsets, Rect outOutsets,InputChannel outInputChannel)@WindowManagerService.java{
  2. int[] appOp = new int[1];
  3. //权限检查,mPolicy是PhoneWindowManager.java实例,如果是应用窗口,直接返回ok,如果系统窗口会细分,有些系统窗口的添加需要相应权限,比如TYPE_SYSTEM_ALERT,TYPE_SYSTEM_ERROR等。
  4. int res = mPolicy.checkAddPermission(attrs, appOp);
  5. WindowState attachedWindow = null;
  6. final int callingUid = Binder.getCallingUid();
  7. final int type = attrs.type;
  8. //获取指定Displayid的显示内容。
  9. final DisplayContent displayContent = getDisplayContentLocked(displayId);
  10. //判断user是由有权限访问这个display。
  11. if (!displayContent.hasAccess(session.mUid)) {
  12. return WindowManagerGlobal.ADD_INVALID_DISPLAY;
  13. }
  14. //判断是否已经添加过。
  15. if (mWindowMap.containsKey(client.asBinder())) {
  16. return WindowManagerGlobal.ADD_DUPLICATE_ADD;
  17. }
  18. //如果是子窗口,找到其父窗口,但是父窗口不能再是别的窗口的子窗口。
  19. if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
  20. attachedWindow = windowForClientLocked(null, attrs.token, false);
  21. if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
  22. && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
  23. return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
  24. }
  25. }
  26. boolean addToken = false;
  27. WindowToken token = mTokenMap.get(attrs.token);
  28. AppWindowToken atoken = null;
  29. //根据窗口的类型检查有效性,
  30. if (token == null) {
  31. if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
  32. return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  33. }
  34. }else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
  35. atoken = token.appWindowToken;
  36. }
  37. //生成一个表达窗口相关信息的WindowState对象,其中的 session是IWindowSession;client是IWindow,是由ViewRootImpl中的W类对象; token是WindowToken对象。
  38. WindowState win = new WindowState(this, session, client, token,
  39. attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
  40. //如果前面新增加了token(WindowToken),把它添加到全局变量mTokenMap中。
  41. if (addToken) {
  42. mTokenMap.put(attrs.token, token);
  43. }
  44. // client是IWindow对象,有ViewRootImpl传过来,作为WMS访问ViewRoot的通道,win是WindowState。
  45. mWindowMap.put(client.asBinder(), win);
  46. //把将窗口win按顺序添加到windowlist中,
  47. addWindowToListInOrderLocked(win, true);
  48. //通过getInsetHintLw计算窗口的内容区域。
  49. if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, mRotation,
  50. displayInfo.logicalWidth, displayInfo.logicalHeight, outContentInsets,
  51. outStableInsets, outOutsets)) {
  52. res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
  53. }
  54. //分配层级值。
  55. mLayersController.assignLayersLocked(displayContent.getWindowList());
  56. }

分析几个相近的变量,

//从IWindow到服务端窗口的映射,其中的IWindow是ViewRootImpl中的W实例,在addWindow过程中添加这个映射关系,其中IWindow.asBinder转成IBinder作为key。

HashMap mWindowMap = new HashMap<>();

//从token IBinder到WindowToken的映射。其中IBinder代表了这个窗口的主人,比如上面函数中的attrs.token,其中的IBinder对应了ActivityRecord(AMS中的)中的appToken,是在添加窗口时通过WindowManager.layoutParams设置的。

HashMap mTokenMap = new HashMap<>();

在startActivity的过程中,其中一步startActivityLocked,会调用mWindowManager.addAppToken(…r.appToken…);通过mTokenMap.put(token.asBinder(),atoken);在WMS中备案;其中的token是AppWindowToken,其父类WindowToken。

在申请窗口的过程中,如果attrs.token找不到匹配的WindowToken,也就是说它在AMS中没有记录,如果窗口类型是:

FIRST_APPLICATION_WINDOW~LAST_APPLICATION_WINDOW,

TYPE_INPUT_METHOD,

TYPE_WALLPAPER,

TYPE_DREAM,

TYPE_QS_DIALOG.

等还有几类,这些窗口类型在mTokenMap一定要有对应关系,就是在startActivity的过程中完成的,如果找不到对应关系,说明启动步骤出错了,窗口的申请也会报错返回。

除去前面说的哪几类窗口类型外,允许在AMS中没有记录,程序会为这个窗口生成一个WindowToken,并把addToken置为true,随后把这个WindowToken添加到mTokenMap中(mTokenMap.put(attrs.token,token);)。

2)应用窗口的添加。

以Activity为主体的应用为例,startactivity时,AMS会判断该Activity所属的应用进程是否已经在运行,如果是,在resumeTopActivityInnerLocked过程中,就调用ApplicationThread的函数scheduleResumeActivity,向这个进程发出启动指定Activity的指令。ApplicationThread是主线程ActivityThread的内部类,也是应用进程跟AMS通信的桥梁。

scheduleResumeActivity会通过RESUME_ACTIVITY消息,调用handleResumeActivity函数。

如果应用进程还没启动,就要先创建应用进程,运行ActivityThread主线程,然后attachApplication回调AMS,AMS在判断有最顶部的可见Activity等待这个进程启动,就会调用ApplicationThread的函数scheduleLaunchActivity来启动指定的Activity。scheduleLaunchActivity通过LAUNCH_ACTIVITY消息,调用handleLaunchActivity,handleLaunchActivity在调用performLaunchActivity后,也会调用handleResumeActivity。

Activity从启动到显示,会经过onCreate,onStart,onResume,其中onResume就是在窗口即将可见时被调用的,在onResume被调用后,主线程ActivityThread会通过该WindowManagerImpl把应用窗口添加到系统中。

  1. final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward,
  2. boolean reallyResume, int seq, String reason) @ActivityThread.java{
  3. //performResumeActivity 会通过Instrumentation调用Activity的onResume()。
  4. ActivityClientRecord r = mActivities.get(token);
  5. r = performResumeActivity(token, clearHide, reason);
  6. //如果窗口还没添加到WindowManager,并且Activity的状态不是finished,又是即将变为visible,那就把这棵ViewTree添加到WindowManager。
  7. if (r.window == null && !a.mFinished && willBeVisible) {
  8. r.window = r.activity.getWindow();
  9. //一个Activity对应的View树的根是DecorView,在onCreate中通过setContentView设置的是DecorView的内容,不包括标题栏等装饰部分。
  10. View decor = r.window.getDecorView();
  11. ViewManager wm = a.getWindowManager();
  12. WindowManager.LayoutParams l = r.window.getAttributes();
  13. //指定窗口类型TYPE_BASE_APPLICATION;
  14. l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
  15. //wm实际是WindowManagerImpl实例,通过它把窗口添加到WMS。
  16. wm.addView(decor, l);
  17. }
  18. }

WindowManagerImpl的addView后面的处理过程跟系统窗口的添加是一致的,不在重复。

窗口添加的过程中有一步把窗口添加到全局的窗口列表中,具体看看这个过程。

  1. private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) @WindowManagerService.java{
  2. //没有父窗口的情况。
  3. if (win.mAttachedWindow == null) {
  4. final WindowToken token = win.mToken;
  5. int tokenWindowsPos = 0;
  6. //.appWindowToken不为null,说明Activity相关的窗口,应用程序窗口的添加就是走这里。
  7. if (token.appWindowToken != null) {
  8. tokenWindowsPos = addAppWindowToListLocked(win);
  9. }else{
  10. addFreeWindowToListLocked(win);
  11. }
  12. if (addToToken) {
  13. token.windows.add(tokenWindowsPos, win);
  14. }
  15. }else{
  16. addAttachedWindowToListLocked(win, addToToken);
  17. }
  18. //把与Activity有关的窗口添加到allAppWindows,这个列表包含了属于这个token的所有窗口,包括子窗口。
  19. final AppWindowToken appToken = win.mAppToken;
  20. if (appToken != null) {
  21. if (addToToken) {
  22. appToken.addWindow(win);
  23. }
  24. }
  25. }
  26. private int addAppWindowToListLocked(final WindowState win) @WindowManagerService.java{
  27. final DisplayContent displayContent = win.getDisplayContent();
  28. final WindowToken token = win.mToken;
  29. //当前显示设备下的所有窗口。
  30. final WindowList windows = displayContent.getWindowList();
  31. //这个token下的所有窗口,一个应用程序下的所有窗口,可以通过token.windows获取。
  32. WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
  33. //应用下已经有至少一个窗口了,通过placeWindowBefore 简单的把新窗口放到顶部,WindowState lowestWindow = tokenWindowList.get(0);placeWindowBefore(lowestWindow, win);。
  34. if (!tokenWindowList.isEmpty()) {
  35. return addAppWindowToTokenListLocked(win, token, windows, tokenWindowList);
  36. }
  37. //下面的代码就是应用程序下目前还没有窗口的情况,这种情况新窗口添加的位置,要基于这个应用的位置。
  38. WindowState pos = null;
  39. //获取所有的task,循环遍历,找到匹配的token,
  40. final ArrayList<Task> tasks = displayContent.getTasks();
  41. for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
  42. AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
  43. for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
  44. final AppWindowToken t = tokens.get(tokenNdx);
  45. if (t == token) {
  46. --tokenNdx;
  47. break;
  48. }
  49. }
  50. //获取这个token的所有窗口的列表,获得一个插入位置pos。
  51. tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
  52. pos = tokenWindowList.get(0);
  53. }
  54. //pos不为null,说明找到了窗口插入的位置,接着在mTokenMap 中找到pos窗口对应的WindowToken,最后把需要添加的 窗口的添加到参考窗口的下面 placeWindowBefore。
  55. if (pos != null) {
  56. WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
  57. if (atoken != null) {
  58. tokenWindowList =getTokenWindowsOnDisplay(atoken, displayContent);
  59. }
  60. final int NC = tokenWindowList.size();
  61. if (NC > 0) {
  62. WindowState bottom = tokenWindowList.get(0);
  63. pos = bottom;
  64. }
  65. placeWindowBefore(pos, win);
  66. }
  67. 如果前一步没有找到合适的位置,还没继续查找剩下AppTokenList tokens中第一个带有窗口的token,然后在这个token的窗口列表中查找参考位置。直到循环结束,如果依然找不到合适的位置,就以这个窗口的BaseLayer为参考位置。
  68. final int myLayer = win.mBaseLayer;
  69. }

发表评论

表情:
评论列表 (有 0 条评论,353人围观)

还没有评论,来说两句吧...

相关阅读