[Android]从源码的角度理解为什么Dialog不能在Application中展示

public void show() {

mWindowManager.addView(mDecor, l);

}

这里调用的mWindowManager的addView方法,其形参mDecor是我们解析到的Dialog的DecorView, l 是WindowManager.LayoutParams 属性; mWindowManager的具体是现实WindowManagerImpl。

WindowManagerImpl调用WindowManagerGlobal#addView继续执行:

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {


root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}

这里创建了viewRootImpl并执行了ViewRootImpl#setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;

try {
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
} finally {

}

if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
mAdded = false;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window – token " + attrs.token

  • " is not valid; is your activity running?");

    }
    throw new RuntimeException(
    "Unable to add window – unknown error code " + res);
    }

    }
    }
    }

在这个方法中,我们找到了抛出异常的地方,当res的值为WindowManagerGlobal.ADD_BAD_APP_TOKEN或则WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN时,会抛出文章开头处的错误。 这个res是IWindowSession#addToDisplay方法的返回值。IWindowSession是一个Binder接口,负责ViewRootImpl和WindowManagerService的通信,在ViewRootImpl对象创建时获取。在这里IWindowSession#addToDisplay最终会调用WindowManagerService#addWindow方法:

// com.android.server.wm.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) {

synchronized(mWindowMap) {

if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
parentWindow = windowForClientLocked(null, attrs.token, false);
if (parentWindow == null) {
Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "

  • attrs.token + “. Aborting.”);
    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
    }
    if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
    && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
  • attrs.token + “. Aborting.”);
    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
    }
    }
    WindowToken token = displayContent.getWindowToken(
    hasParent ? parentWindow.mAttrs.token : attrs.token);
    if (token == null) {
    if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
  • attrs.token + “. Aborting.”);
    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
    }

    }

    return res;
    }

通过这个方法,可以确定方法返回WindowManagerGlobal.ADD_BAD_APP_TOKEN或则WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN的条件,即Window的类型为子窗口时,parentWindow为空会返回WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN,当Window类型为APPLICATION_WINDOW时,当对应的WindowToken为空就会返回WindowManagerGlobal.ADD_BAD_APP_TOKEN

2. WindowToken是什么

在第一章节的最后一部分,我们引入了一个新概念:WindowToken,它和这个异常的判断息息相关,那么它是在哪里创建的呢?作用又是什么呢?

看下它的获取方式:

WindowToken getWindowToken(IBinder binder) {
return mTokenMap.get(binder);
}

先看下AppWindowToken的继承体系: image.png

回顾一下Activity的启动流程,在Activity的启动过程中,会调用ActivityStack#startActivityLocked方法:

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

TaskRecord task = null;
if (!newTask) {
boolean startIt = true;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
task = mTaskHistory.get(taskNdx);
if (task.getTopActivity() == null) {
continue;
}
if (task == rTask) {
if (!startIt) {
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task "

  • task, new RuntimeException(“here”).fillInStackTrace());
    // zhangyulong 生成WindowContainer对象
    r.createWindowContainer();
    ActivityOptions.abort(options);
    return;
    }
    break;
    } else if (task.numFullscreen > 0) {
    startIt = false;
    }
    }
    }

    }

在这个方法中会调用ActivityRecord#createWindowContainer方法,在这个方法里会创建AppWindowContainer的实例,进而创建AppWindowContainerController实例,在AppWindowContainerController中会创建AppWindowToken的实例,在创建实例时会调用AppWindowToken的构造方法。从上图AppWindowToken的继承关系图我们可以知道,AppWindowToken的父类是WindowToken,其构造方法如下:

// com.android.server.wm.WindowToken
WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
DisplayContent dc, boolean ownerCanManageAppTokens) {
mService = service;
token = _token;
windowType = type;
mPersistOnEmpty = persistOnEmpty;
mOwnerCanManageAppTokens = ownerCanManageAppTokens;
onDisplayChanged(dc);
}

看下onDisplayChanged方法:

// com.android.server.wm.onDisplayChanged
void onDisplayChanged(DisplayContent dc) {
dc.reParentWindowToken(this);

}

DisplayContent#reParentWindowToken会将该WindowToken加入到一个叫做mTokenMap的Map中,其定义为HashMap<IBinder, WindowToken> mTokenMap. 而作为key的IBinder对象,就是我们在Activity启动流程中创建的AppToken对象。也就是说,每个AppToken会对应一个AppWindowToken,简单的示例图如下:

image.png

3. 问题产生的根本原因

要想探寻这个错误产生的根本原因,我们只需要寻确认在异常产生时,Window的Type和appToken的值是什么即可。

Window的Type是比较好确认的,在创建Dialog的Window时,便生成了其对应的WindowManager.LayoutParams, 在其构造方法中已确定了Dialog对应Window的type:

public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}

而appToken的情况则稍显复杂,我们首先要确定这个appToken是从哪里获取的。

先看一下Dialog的构造方法:

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

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

}

这个WindowManger是我们在show方法中用于执行addView的WindowManger。

那么接下来,我们就要确定在Application中启动Dialog和在Activity中启动Dialog这两种方式WindowManger的处理有什么不同就可以了。

首先看Activity的情况:

还是Dialog的构造方法,这个Context的实际类型是Activity的对象,而Activity重写了getSystemService方法:

Android开发除了flutter还有什么是必须掌握的吗?

相信大多数从事Android开发的朋友们越来越发现,找工作越来越难了,面试的要求越来越高了

除了基础扎实的java知识,数据结构算法,设计模式还要求会底层源码,NDK技术,性能调优,还有会些小程序和跨平台,比如说flutter,以思维脑图的方式展示在下图;

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
面试的要求越来越高了

除了基础扎实的java知识,数据结构算法,设计模式还要求会底层源码,NDK技术,性能调优,还有会些小程序和跨平台,比如说flutter,以思维脑图的方式展示在下图;

[外链图片转存中…(img-GKcTc9ek-1715758494343)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值