我的程序在后台线程中执行一些网络活动。在开始之前,它会弹出一个进度对话框。该对话框在处理程序上被解除。这一切都很好,除了当对话框打开时屏幕方向发生变化(背景线程正在运行)。此时,应用程序要么崩溃,要么死锁,要么进入一个奇怪的阶段,在所有线程被杀死之前,应用程序根本无法工作。
我如何处理屏幕方向的变化优雅?
下面的示例代码大致匹配我的实际程序:
public class MyAct extends Activity implements Runnable {
public ProgressDialog mProgress;
// UI has a button that when pressed calls send
public void send() {
mProgress = ProgressDialog.show(this, "Please wait",
"Please wait",
true, true);
Thread thread = new Thread(this);
thread.start();
}
public void run() {
Thread.sleep(10000);
Message msg = new Message();
mHandler.sendMessage(msg);
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mProgress.dismiss();
}
};
}
栈:
E/WindowManager( 244): Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager( 244): android.view.WindowLeaked: Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager( 244): at android.view.ViewRoot.<init>(ViewRoot.java:178)
E/WindowManager( 244): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:147)
E/WindowManager( 244): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
E/WindowManager( 244): at android.view.Window$LocalWindowManager.addView(Window.java:393)
E/WindowManager( 244): at android.app.Dialog.show(Dialog.java:212)
E/WindowManager( 244): at android.app.ProgressDialog.show(ProgressDialog.java:103)
E/WindowManager( 244): at android.app.ProgressDialog.show(ProgressDialog.java:91)
E/WindowManager( 244): at MyAct.send(MyAct.java:294)
E/WindowManager( 244): at MyAct$4.onClick(MyAct.java:174)
E/WindowManager( 244): at android.view.View.performClick(View.java:2129)
E/WindowManager( 244): at android.view.View.onTouchEvent(View.java:3543)
E/WindowManager( 244): at android.widget.TextView.onTouchEvent(TextView.java:4664)
E/WindowManager( 244): at android.view.View.dispatchTouchEvent(View.java:3198)
我已经尝试在onSaveInstanceState中取消进度对话框,但这只是防止了立即崩溃。背景线程仍在运行,UI处于部分绘制状态。需要在它重新开始工作之前杀死整个应用程序。
如果你维护两个布局,所有的UI线程都应该终止。
如果你使用AsynTask,那么你可以很容易地在当前活动的onDestroy()方法中调用.cancel()方法。
@Override
protected void onDestroy (){
removeDialog(DIALOG_LOGIN_ID); // remove loading dialog
if (loginTask != null){
if (loginTask.getStatus() != AsyncTask.Status.FINISHED)
loginTask.cancel(true); //cancel AsyncTask
}
super.onDestroy();
}
对于AsyncTask,请在“取消任务”部分阅读更多信息。
更新:
增加了检查状态的条件,因为只有当它处于运行状态时才能取消。
还要注意,AsyncTask只能执行一次。
编辑:谷歌工程师不推荐这种方法,正如Dianne Hackborn(又名hackbod)在StackOverflow的帖子中所描述的那样。查看这篇博客文章了解更多信息。
你必须把这个添加到manifest中的activity声明中:
android:configChanges="orientation|screenSize"
看起来是这样的
<activity android:label="@string/app_name"
android:configChanges="orientation|screenSize|keyboardHidden"
android:name=".your.package">
问题是,当配置发生更改时,系统将破坏活动。看到ConfigurationChanges。
所以把它放在配置文件中可以避免系统破坏你的活动。相反,它调用onConfigurationChanged(Configuration)方法。
If you're struggling with detecting orientation change events of a dialog INDEPENDENT OF AN ACTIVITY REFERENCE, this method works excitingly well. I use this because I have my own dialog class that can be shown in multiple different Activities so I don't always know which Activity it's being shown in. With this method you don't need to change the AndroidManifest, worry about Activity references, and you don't need a custom dialog (as I have). You do need, however, a custom content view so you can detect the orientation changes using that particular view. Here's my example:
设置
public class MyContentView extends View{
public MyContentView(Context context){
super(context);
}
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
//DO SOMETHING HERE!! :D
}
}
实现1 -对话
Dialog dialog = new Dialog(context);
//set up dialog
dialog.setContentView(new MyContentView(context));
dialog.show();
实现2 - AlertDialog。构建器
AlertDialog.Builder builder = new AlertDialog.Builder(context);
//set up dialog builder
builder.setView(new MyContentView(context)); //Can use this method
builder.setCustomTitle(new MycontentView(context)); // or this method
builder.build().show();
实现3 - ProgressDialog / AlertDialog
ProgressDialog progress = new ProgressDialog(context);
//set up progress dialog
progress.setView(new MyContentView(context)); //Can use this method
progress.setCustomTitle(new MyContentView(context)); // or this method
progress.show();
最初的问题是代码无法在屏幕方向改变时存活下来。显然,这是“解决”的程序处理屏幕方向变化本身,而不是让UI框架做它(通过调用onDestroy)。
I would submit that if the underlying problem is that the program will not survive onDestroy(), then the accepted solution is just a workaround that leaves the program with serious other problems and vulnerabilities. Remember that the Android framework specifically states that your activity is at risk for being destroyed almost at any time due to circumstances outside your control. Therefore, your activity must be able to survive onDestroy() and subsequent onCreate() for any reason, not just a screen orientation change.
如果你要接受自己处理屏幕方向变化来解决OP的问题,你需要验证onDestroy()的其他原因不会导致相同的错误。你能做到吗?如果不是,我会质疑这个“公认的”答案是否真的是一个很好的答案。
这是我提出的解决方案:
Move the AsyncTask or Thread to a retained Fragment, as explained here. I believe it is a good practice to move all network calls to fragments. If you are already using fragments, one of them could be made responsible for the calls. Otherwise, you can create a fragment just for doing the request, as the linked article proposes.
The fragment will use a listener interface to signal the task completion/failure. You don't have to worry for orientation changes there. The fragment will always have the correct link to the current activity and progress dialog can be safely resumed.
Make your progress dialog a member of your class. In fact you should do that for all dialogs. In the onPause method you should dismiss them, otherwise you will leak a window on the configuration change. The busy state should be kept by the fragment. When the fragment is attached to the activity, you can bring up the progress dialog again, if the call is still running. A void showProgressDialog() method can be added to the fragment-activity listener interface for this purpose.