
为什么创建 Dialog 不能使用 Application Context一、核心结论Dialog 是子窗口Sub-Window必须依附于一个父窗口PhoneWindow。Application Context 没有窗口也没有 WindowToken所以无法挂载 Dialog。二、Dialog 的创建流程当你调用Dialog.show()时内部发生了这些// Dialog.java 简化版publicvoidshow(){// 1. 创建 WindowmWindownewPhoneWindow(mContext);// 需要 Context 来创建窗口// 2. 获取 WindowManagermWindowManager(WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);// 3. 把 Dialog 的 DecorView 添加到 WindowmWindowManager.addView(mDecor,params);// 这里需要 WindowToken}关键点在第 3 步WindowManager.addView()需要一个WindowToken。三、WindowToken 是什么WindowToken 是窗口的身份证Android 窗口管理系统WMS用它来识别和管控窗口。窗口层级关系 PhoneWindowActivity 的根窗口 ↓ WindowToken由 AMS 分配绑定到 Activity 的 token ↓ Dialog 的窗口子窗口必须挂在某个父窗口下Dialog 是子窗口Sub-Window它必须挂载在一个父窗口通常是 Activity 的 PhoneWindow下面。这个挂载关系靠WindowToken维系。四、Application Context 为什么不行1. Application 没有 WindowToken// Application 继承关系Application→ContextWrapper→Context// Activity 继承关系Activity→ContextThemeWrapper→ContextWrapper→Context↑ 这里多了ContextThemeWrapper携带了窗口信息Activity 在创建时AMS 会分配一个IBinder token给它这个 token 就是 WindowToken 的来源// ActivityThread.javafinalActivityClientRecordrnewActivityClientRecord();r.tokentoken;// 来自 AMS 的 WindowTokenApplication 没有参与 Activity 的启动流程AMS 不会给它分配 token。2. 报错现场当你用 Application Context 创建 Dialog 并show()// 崩溃日志android.view.WindowManager$BadTokenException:Unabletoaddwindow--tokennullis not valid;is your activity running?token null指的就是 WindowToken 为 null。五、Activity Context 为什么可以Activity 的mToken在attach()方法中被设置// Activity.javafinalvoidattach(Contextcontext,ActivityThreadaThread,Instrumentationinstr,IBindertoken,// ← 这个 token 就是 WindowTokenApplicationapplication,Intentintent,...){mWindownewPhoneWindow(this);mWindow.setWindowManager(...,token,...);// 把 token 传给 WindowManagermTokentoken;}所以当你用 Activity Context 创建 DialogDialogdialognewDialog(activity);// activity 的 mToken 有值dialog.show();Dialog 内部会复用 Activity 的 WindowToken把自己作为子窗口挂载到 Activity 的窗口树下。六、错误示例与正确做法错误Application Context 创建 Dialog// ❌ 崩溃android.view.WindowManager$BadTokenExceptionAlertDialog.BuilderbuildernewAlertDialog.Builder(getApplicationContext());AlertDialogdialogbuilder.create();dialog.show();正确使用 Activity Context// ✅ 正确使用 Activity ContextAlertDialog.BuilderbuildernewAlertDialog.Builder(MainActivity.this);AlertDialogdialogbuilder.create();dialog.show();七、如果非要用 Application Context 显示弹窗怎么办方案 1用 System Alert 窗口需要权限// AndroidManifest.xml 中声明权限uses-permission android:nameandroid.permission.SYSTEM_ALERT_WINDOW/// 代码DialogdialognewDialog(getApplicationContext());dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);dialog.show();这种窗口不依附于任何 Activity是系统级浮窗类似微信视频通话的小窗。方案 2用 Toast本身就是系统窗口Toast.makeText(getApplicationContext(),提示,Toast.LENGTH_SHORT).show();Toast 使用系统窗口TYPE_TOAST不需要 Activity Token。方案 3启动一个透明的 Activity// 用 Application Context 启动一个透明主题的 Activity再在里面显示 DialogIntentintentnewIntent(getApplicationContext(),TransparentDialogActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);getApplicationContext().startActivity(intent);八、BroadcastReceiver 中的 Context 限制BroadcastReceiver 的onReceive中传入的 Context 也不是 Activity Context同样不能创建 Dialog// ❌ 错误BroadcastReceiver 没有窗口publicclassMyReceiverextendsBroadcastReceiver{OverridepublicvoidonReceive(Contextcontext,Intentintent){// 这里 context 是 Application Context 或 ReceiverRestrictedContextAlertDialog.BuilderbuildernewAlertDialog.Builder(context);builder.show();// 崩溃}}// ✅ 正确启动一个 Activity 来显示 DialogpublicclassMyReceiverextendsBroadcastReceiver{OverridepublicvoidonReceive(Contextcontext,Intentintent){IntentactivityIntentnewIntent(context,DialogActivity.class);activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(activityIntent);}}九、总结问题答案Dialog 为什么需要窗口因为 Dialog 是子窗口必须挂载到父窗口下父窗口怎么识别靠 WindowTokenAMS 分配的 IBinderActivity 有没有 WindowToken有AMS 在启动 Activity 时分配Application 有没有 WindowToken没有Application 不参与窗口管理不用 Activity 怎么显示弹窗System Alert 窗口、Toast、透明 Activity一句话Dialog 是窗中窗Application 不是窗所以挂不上。