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

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方法:

@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
“System services not available to Activities before onCreate()”);
}

if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}

因此,在Dialog的构造方法中,我们获取到的实际是Activity中定义的WindowMnager,我们看下这个WindowMnager的赋值,这部分逻辑在Activity#attach方法中:

final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值