案例:今天修改公司一个项目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);
}
}
最后是源码,效果我就不截图了,这边有兴趣的童鞋,可以下源码自己泡一下.源码下载请点击这里.