View系列:View问题:WindowManager$BadTokenException: Unable to add window

为什么不能使用 Application Context 显示 Dialog? - 掘金

写个简单代码

Dialog dialog = new Dialog(getApplicationContext());
dialog.show();
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:951)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:96)
        at android.app.Dialog.show(Dialog.java:344)
        at luyao.android.context.ContextActivity.showDialog(ContextActivity.java:31)

1:为什么不能使用 Application Context 显示 Dialog?

     主要分析思路:

    1:Dialog dialog = new Dialog(Context) ;  dialog.show()  ----->是怎么将Dialog添加到Window窗口上的

        通过源码发现:显示通过 Dialog构造函数传入的Context得到WinadowManager,然后在Dialog.show()函数中,windowManager.addView()来弹框Dialog.

# Dialog的构造函数

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    ......

    // 获取 WindowManager
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    final Window w = new PhoneWindow(mContext);
    mWindow = w;
    ......
}
# Dialog.show() 函数实现
public void show() {
      
        onStart();  // alled when the dialog is starting.

        mDecor = mWindow.getDecorView(); // 获取DecorView

        WindowManager.LayoutParams l = mWindow.getAttributes();


        try {
            // 通过构造函数得到的WindowManager.addView(DecorView)
            mWindowManager.addView(mDecor, l);

            mShowing = true;
    
            sendShowMessage();
        } finally {
        }
    }

   2:Dialog 构造函数中的Context和 Token有什么关系

        在Dialog的构造函数忠,根据传入的Context调用getSystemService(Context.WINDOW_SERVICE)方法来得到WindowManager对象mWindowManager.

      如果传入的Context是Activity,那么返回的是在  Ativity.attach()方法中创建的mWindowManager对象,这个时候mToken也已经绑定了

> Activity.java

@Override
public Object getSystemService(@ServiceName @NonNull String name) {
    ......

    if (WINDOW_SERVICE.equals(name)) {
        return mWindowManager; // 在 attach() 方法中已经实例化
    } else if (SEARCH_SERVICE.equals(name)) {
        ensureSearchManager();
        return mSearchManager;
    }
    return super.getSystemService(name);
}

       如果传入的 Context 是 Application,最终调用的是父类 ContextImpl 的方法 

       SystemServiceRegistry.getSystemService(this, name) 拿到的是已经提前初始化完成并缓存下来的系统服务,并没有携带任何的 Token。

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}
registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
            }});

 所以:Android是不允许Activity以外的Context来创建和显示普通的 Dialog , 这里的普通Dialog指的是文章开头的普通Dialog , 并不是 Toast  SystemDialog 等等

  Android系统为了安全考虑,不想在App进入后台之后仍然可以弹出Dialog , 因为这样会在其他App弹窗的场景,造成一定的安全隐患。

---------------------------------------------------------------------------------------------------------------------------------

2:谁创建了Token,什么地方创建

     下面来 Read the Fuck AOSP, 下面来看看 Token到底是个什么样的类

     Token 是 ActivityRecord 的静态内部类,它持有外部 ActivityRecord 的弱引用。继承自 IApplicationToken.Stub ,是一个 Binder 对象。它在 ActivityRecord 的构造函数中初始化。

# ActivityRecord.java

static class Token extends IApplicationToken.Stub {
    private final WeakReference<ActivityRecord> weakActivity;
    private final String name;

    Token(ActivityRecord activity, Intent intent) {
        weakActivity = new WeakReference<>(activity);
        name = intent.getComponent().flattenToShortString();
    }

    ......
}
> ActivityRecord.java

ActivityRecord(ActivityManagerService _service, ProcessRecord _caller, int _launchedFromPid,
            int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
            ActivityInfo aInfo, Configuration _configuration,
            ActivityRecord _resultTo, String _resultWho, int _reqCode,
            boolean _componentSpecified, boolean _rootVoiceInteraction,
            ActivityStackSupervisor supervisor, ActivityOptions options,
            ActivityRecord sourceRecord) {
    service = _service;
	// 初始化 appToken
    appToken = new Token(this, _intent);
    ......
}

       一个 ActivtyRecord 代表一个 Activity 实例, 它包含了 Activity 的所有信息。在 Activity 的启动过程中,当执行到 ActivityStarter.startActivity() 时,会创建待启动的 ActivityRecord 对象,也间接创建了 Token 对象

       

> ActivityStarter.java

private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
            PendingIntentRecord originatingPendingIntent) {

            ......

            // 构建 ActivityRecord,其中会初始化 token
            ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
                callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
                resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
                mSupervisor, checkedOptions, sourceRecord);

            ......
            return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
                true /* doResume */, checkedOptions, inTask, outActivity);
}

所以 到这里, ActivityRecord.appToken 已经被赋值。所以 Token 是在 AMS 的 startActivity 流程中创建的。但是 Token 的校验显然是发生在 WMS 中的,所以 AMS 还得把 Token 交到 WMS 。

--------------------------------------------------------------------------------------------------------------------------------

3:  WMS如何拿到Token

     继续跟下去,startActivity() 最后会调用到 ActivityStack.startActivityLocked(),这个方法就是把 Token 给到 WMS 的关键。重点关注 r.createWindowContainer(),这里的 r 就是一开始创建的 ActivityRecord 对象。

> ActivityStack.java

void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
            boolean newTask, boolean keepCurTransition, ActivityOptions options) {

            ......

            if (r.getWindowContainerController() == null) {
			// 创建 AppWindowContainerController 对象,其中包含 token 对象
            r.createWindowContainer();

            ......
        
}
> ActivityRecord.java

void createWindowContainer() {
    if (mWindowContainerController != null) {
        throw new IllegalArgumentException("Window container=" + mWindowContainerController
                + " already created for r=" + this);
    }

    inHistory = true;

    final TaskWindowContainerController taskController = task.getWindowContainerController();

    ......

	// 构造函数中会调用 createAppWindow() 创建 AppWindowToken 对象
    mWindowContainerController = new AppWindowContainerController(taskController, appToken,
            this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen,
            (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
            task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
            appInfo.targetSdkVersion, mRotationAnimationHint,
            ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);

    task.addActivityToTop(this);

    ......
}

在 AppWindowContainerController 的构造函数中传入了之前已经初始化过的 appToken 。 

createAppWindow() 方法中会创建 AppWindowToken 对象,注意传入的 token 参数。

> AppWindowContainerController.java

public AppWindowContainerController(TaskWindowContainerController taskController,
        IApplicationToken token, AppWindowContainerListener listener, int index,
        int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
        boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
        int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
        WindowManagerService service) {
    super(listener, service);
    mHandler = new H(service.mH.getLooper());
    mToken = token;
    synchronized(mWindowMap) {
           ......

        atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
                inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
                requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
                alwaysFocusable, this);
        ......
    }
}
> AppWindowContainerController.java

AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token,
        boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
        boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
        int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
        boolean alwaysFocusable, AppWindowContainerController controller) {
    return new AppWindowToken(service, token, voiceInteraction, dc,
            inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
            rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
            controller);
}
> AppWindowToken.java

AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
            DisplayContent dc, boolean fillsParent) {
        // 父类是 WindowToken
        super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
                false /* ownerCanManageAppTokens */);
        appToken = token;
        mVoiceInteraction = voiceInteraction;
        mFillsParent = fillsParent;
        mInputApplicationHandle = new InputApplicationHandle(this);
}

 这里调用了父类的构造函数,AppWindowToken 的父类是 WindowToken 。

> WindowToken.java

WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
            DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {
        super(service);
        token = _token;
        windowType = type;
        mPersistOnEmpty = persistOnEmpty;
        mOwnerCanManageAppTokens = ownerCanManageAppTokens;
        mRoundedCornerOverlay = roundedCornerOverlay;
        // 接着跟进去
        onDisplayChanged(dc);
    }
> WindowToken.java

void onDisplayChanged(DisplayContent dc) {
        // 调用 DisplayContent.reParentWindowToken()
        dc.reParentWindowToken(this);
        mDisplayContent = dc;
        ......
}

mTokenMap 是一个 HashMap<IBinder, WindowToken> 对象,保存了 WindowToken 以及其 Token 信息。我们从 AMS 启动 Activity 一路追到这里,其实已经走到了 WMS 的逻辑。AMS 和 WMS 都是运行在 system_server 进程的,并不存在 binder 调用。AMS 就是按照上面的调用链把 Token 传递给了 WMS

> DisplayContent.java
    
void reParentWindowToken(WindowToken token) {
    ......
    addWindowToken(token.token, token);
}

private void addWindowToken(IBinder binder, WindowToken token) {
    ......
	// mTokenMap 是一个 HashMap<IBinder, WindowToken>,
    mTokenMap.put(binder, token);
    ......
}

 --------------------------------------------------------------------------------------------------------------------------------

4:  WMS如何校验Token

没错,的确是从哈希表 mTokenMap 中直接获取。写到这里,整个流程已经走通了。

  • AMS 在启动 Activity 的时候,会构建表示 Activity 信息的 ActivityRecord 对象,其构造函数中会实例化 Token 对象
  • AMS 在接着上一步之后,会利用创建的 Token 构建 AppWindowContainerController 对象,最终将 Token 存储到 WMS 中的 mTokenMap 中
  • WMS 在 addWindow 时,会根据当前 Window 对象的 Token 进行校验

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值