Dialog使用Application作为context为什么会报错?(二)

第一篇文章主要讲了Binder能够在系统中作为Token使用,作为一种验证机制。接下来就从token的产生、注册、验证。其中产生和注册关联较紧密,放在一起阐述;验证的流程相对比较复杂,分开来进行分析。本篇就以产生和注册进行展开。

从Activity启动说起,启动时会涉及到四五十个函数的调用,就不一一列举了。抓出其中跟token相关的部分。

token的传递流程如下描述:

AMS创建ActivityRecord(产生token)

static class Token extends IApplicationToken.Stub

在ActivityRecord的内部类中可以看出token是继承于一个Binder,同时这个token内部还记录了:

1.含有该ActivityRecord的对象

2.指向AMS的对象

在ActivtiyRecord的构造函数中就有一句

appToken = new Token(this, service);

从此Token就诞生了!!!!

诞生之后就会涉及到他的传递了,流程较长,直接给到结论:

通过ActivityClientRecord传递到APP进程端

app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken...);

可见该函数的第二个参数就是Token,由此将其传递给app所在进程

         public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident...) {
          
             updateProcessState(procState, false);
          
             ActivityClientRecord r = new ActivityClientRecord();
          
             r.token = token;

可见在APP端会创建一个ActivityClientRecord来记录,同时存下这个token

===>APP进程端通过LayoutParameter传递给WMS端(AMS和WMS同进程可以直接验证)

Activity中有一个成员mWindow,mWindow调用setWindowManager将该token又保存到了Window成员中去

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
 567             boolean hardwareAccelerated) {
 568         mAppToken = appToken;
 569         mAppName = appName;
 570         mHardwareAccelerated = hardwareAccelerated
 571                 || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
 572         if (wm == null) {
 573             wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
 574         }        
 575         mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
 576     }            

到这个地方就存在分歧了,针对普通的Activity窗口和Dialog窗口:区别在于如果window是Dialog,则该函数的第二个参数appToken是null

这里看一下Dialog的构造函数

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
 172         if (createContextThemeWrapper) {  
 173             if (themeResId == 0) {        
 174                 final TypedValue outValue = new TypedValue();
 175                 context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
 176                 themeResId = outValue.resourceId;
 177             }                             
 178             mContext = new ContextThemeWrapper(context, themeResId);
 179         } else {                          
 180             mContext = context;           
 181         }                                 
 182                                           
 183         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
 184                                           
 185         final Window w = new PhoneWindow(mContext); //这里构造的window使用的如果是Application作为Context                                                                                                       
 186         mWindow = w;                      
 187         w.setCallback(this);              
 188         w.setOnWindowDismissedCallback(this);
//这里第二个参数在一般的Activity添加页面是会是Token,但是对于这里会是null
//那按理讲,不论用什么样的Context,在app进程端的token都应该是null
 189         w.setWindowManager(mWindowManager, null, null);
 190         w.setGravity(Gravity.CENTER);     
 191                                           
 192         mListenersHandler = new ListenersHandler(this);
 193     }                   

在看Dialgo的show方法

public void show() {
          if (mShowing) {
              if (mDecor != null) {
                  if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {                                                                                       
                      mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                  }
                  mDecor.setVisibility(View.VISIBLE);
              }
              return;
          }
           
          mCanceled = false;
           
          if (!mCreated) {
              dispatchOnCreate(null);
          }
           
          onStart();
          mDecor = mWindow.getDecorView();
           
          if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
              final ApplicationInfo info = mContext.getApplicationInfo();
              mWindow.setDefaultIcon(info.icon);
              mWindow.setDefaultLogo(info.logo);
              mActionBar = new WindowDecorActionBar(this);
          }
           
          WindowManager.LayoutParams l = mWindow.getAttributes();
//将这个window的所有属性抓出来,做成一个布局参数交给wms
          if ((l.softInputMode
                  & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
              WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
              nl.copyFrom(l);
              nl.softInputMode |=
                      WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
              l = nl;
          }
           
          try {
              mWindowManager.addView(mDecor, l);//这里就会跳到WindowManagerGlobal去

这里就会继续trace到WindowManager的addView上去

针对非Activity的窗口没有token,下面这个函数的目标就是找到其依附的窗口的Activity的token赋值给该Dialog的LayoutParameter中的token

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp)

到此未知,正常状况下,app的客户端会有四处数据有token:

1.ActivityClientRecord 2.Activity 3.Window 4.LayoutParameter

接下来关注的函数流程就集中在WindowManagerService中的

public int addWindow(Session session, IWindow client, int seq,

WindowManager.LayoutParams attrs, int viewVisibility, int displayId,

Rect outContentInsets, Rect outStableInsets, Rect outOutsets,

InputChannel outInputChannel)

这里有很多很有趣味的判断

这里先补充一下在AMS启动Activity的流程中会调用wms的addAppToken接口,在wms中为已经启动的Activity进行备案

在往wms中备案的时候,调用addWindowToken的有

./services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java:2631:

mWindowManagerService.addWindowToken(mOverlayWindowToken,

./services/core/java/com/android/server/InputMethodManagerService.java:1448: mIWindowManager.addWindowToken(mCurToken,

./services/core/java/com/android/server/wallpaper/WallpaperManagerService.java:1049: mIWindowManager.addWindowToken(newConn.mToken,

./services/core/java/com/android/server/dreams/DreamController.java:137: mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM);

都是一些输入法,墙纸,休眠等功能,这样在wms中添加的WindowToken中的appWindowToken都是空的,也就意味着这个这些window都不是app的窗口

而Activity启动的过程中调用的是addAppWindowToken,所以这样的WindowToken中的AppWindowToken就非空了

那么现在问题就比较迷惑了,dialog的token说是从父窗口那里继承过来的

但是WMS端的报错log如下:

WindowManager: Attempted to add window with non-application token WindowToken{e638664 null}. Aborting.

意思就是从父窗口那里拿到的这个WindowToken并不是一个有效的app token从而造成添加window失败??????????

到这里把token的绝大多数流程已经理清了,但是总感觉还缺一点东西,两类Context在过程中的区别,从前面的分析着实是看不出来有什么差别!

暂时分析到这里卡住了。。。

    原文作者:Kelvin wu
    原文地址: https://zhuanlan.zhihu.com/p/20377272
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞