android解决dialog和popupwindow的BadTokenException:Unable to add window

本文介绍了在Android中遇到Dialog和PopupWindow显示时抛出BadTokenException的问题,分析了异常产生的原因:活动(Activity)在Dialog或PopupWindow显示前已被销毁。提出了解决方案:在Activity销毁时关闭所有线程,但这种方法可能导致应用闪退和后台线程重启问题,不推荐使用。建议寻找更优的处理方式来避免此类异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        案例:今天修改公司一个项目BUG,有个效果是进APP首页,APP弹出弹窗广告,广告内容是实时从服务器获取的,(一般APP不推荐这么做,很多都是每次次进入获取下次要显示的广告),因为是实时获取,可能在这过程中网络不好,用户误点到某个模块进行跳转,那么已经开始show的dialog或者popupwindow就会出现BadTokenException.

        异常信息:android.view.WindowManager&BadTokenException:Unable to add window这个错误,说reference on a null performance

       

        正确异常出现原因:

        1.由于dialog和popupwindow都要依附于activity,所以dialog和popupwindow在show之前,activity的实例都要存在,不能被销毁.

        2.因为是开启线程进行联网耗时操作,耗时操作完毕再showdialog和popupwindow.用户在实际操作的时候,很有可能没有等到dialog和popupwindow在show之前就进行页面跳转,销毁上一个依赖的activity实例.导致在showdialog的时候报错.

         因此解决思路就很明朗了.那就是:在activityfinish进行跳转的时候,在onDestroy里面去killprogess(); 即杀掉进程下所有的线程.


             解决方法:
        1.看了网上杀线程的方法.很多都在说android.os.Process.killProcess(android.os.Process.myPid());这个方法是杀掉当前所有的进程和线程.直接上代码,在跳转的时候直接全杀,在onDestroy的时候去实现跳转.

            因为笔者,只想到了这么做,无论是跳转前和跳转后去杀线程,都会导致整个APP程序关闭.只有这样才能实现杀了在跳转的效果.

            但是,这样做非常不好.第一.用户体验很差,跳转的时候会出现一瞬间黑屏.第二,跳转完成之后,你还得考虑之前被误杀的在后台跑的线程重启的问题,如果是需要在后台挂长连接的APP,这么做会很影响性能,且效果很低,所以,相当不推荐这个做法.简直是误导人.

         

protected void killprogess() {
		android.os.Process.killProcess(android.os.Process.myPid());
	}

@Override
	protected void onDestroy() {
		super.onDestroy();
		startActivity(new Intent(MainActivity.this, SecondActivity.class));
		finish();
	}
           2 .第二种方法,笔者经过综合考虑,还是觉得比较合适.推荐用这种方法.主要还是通过主线程的handler去remove掉当前跑的runnable.这样做不会有连带的影响,直接上代码,很简单,相信大家都能看懂.不解释了.

/**
 * 进主界面有个广告弹窗,弹窗内容需要联网获取.
 * --------------这边可以设计成,首次登陆去获取下次要弹出的广告,一般的APP都推荐这么做, 不过我们公司有点逆天,反正需求是要每次都实时去获取------
 * 在跳转的时候,关闭activity下面的线程,避免出现异常
 * 有两种实现方式,推荐用handler.removeCallbacks关闭执行的线程
 * @author max
 *
 */
public class MainActivity extends Activity {
	private static final String TAG = "com.max.threadshutdowndemo";
	private Handler mHandler = new Handler();
	Runnable runnable = new Runnable() {
		
		@Override
		public void run() {
			//显示广告弹窗的dialog
			showAD();
		}
	};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		//我用postdelayed来模拟联网获取广告的告示操作
		mHandler.postDelayed(runnable, 3000);
		Button button = (Button) findViewById(R.id.first_button);
		//如果用户在中途误点了某个模块,发生了页面跳转.那么久先啥子当前activity的线程,在跳转,避免出现异常崩溃
		button.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				killprogess();
			}
		});
	}

	/**
	 * 第一种方式,杀死当前的进程以及下面的线程
	 */
	protected void killprogess() {
		startActivity(new Intent(MainActivity.this, SecondActivity.class));
		finish();
	}

	/**
	 * 显示一个dialog或者popupwindow
	 * 弹窗显示的内容有可能是需要用thread去联网获取的耗时操作
	 * 这里为了方便用postDelayed来表示耗时操作
	 */
	protected void showAD() {
		//这边为了保险起见,加入了try catch捕获异常,避免崩溃,不过已经remove了子线程,这样做是多余的.有兴趣的童鞋可以尝试哈
		try {
			Dialog dialog = new AlertDialog.Builder(MainActivity.this).create();
			dialog.show();
			dialog.setCanceledOnTouchOutside(false);
			Window window = dialog.getWindow();
			window.setContentView(R.layout.dialog_main);
		} catch (Exception e) {
			Log.e(TAG, e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * 在ondestroy中执行关闭子线程的操作
	 */
	@Override
	protected void onDestroy() {
		super.onDestroy();
		//这个方法极力不推荐.
//		android.os.Process.killProcess(android.os.Process.myPid());
		//关键的操作,当前页面finish的时候,关闭下面还在跑得线程,防止出现异常.
		//也可以用try catch捕获异常防止crash掉
		mHandler.removeCallbacks(runnable);
	}
}


最后是源码,效果我就不截图了,这边有兴趣的童鞋,可以下源码自己泡一下.源码下载请点击这里.

08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: FATAL EXCEPTION: main 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: Process: com.android.provision, PID: 2483 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.view.ViewRootImpl.setView(ViewRootImpl.java:2101) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:546) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:402) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.app.Dialog.show(Dialog.java:366) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.app.AlertDialog$Builder.show(AlertDialog.java:1131) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.widget.VideoView$5.onError(VideoView.java:615) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.media.MediaPlayer$EventHandler.handleMessage(MediaPlayer.java:3640) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:107) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:249) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.os.Looper.loop(Looper.java:337) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:9562) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:648) 08-04 10:45:35.399 1000 2483 2483 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1005) 08-04 10:45:35.402 1000 2483 2483 D OOMEventManagerFK: checkEventAndDumpForJE: 0
最新发布
08-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值