我的程序在后台线程中执行一些网络活动。在开始之前,它会弹出一个进度对话框。该对话框在处理程序上被解除。这一切都很好,除了当对话框打开时屏幕方向发生变化(背景线程正在运行)。此时,应用程序要么崩溃,要么死锁,要么进入一个奇怪的阶段,在所有线程被杀死之前,应用程序根本无法工作。
我如何处理屏幕方向的变化优雅?
下面的示例代码大致匹配我的实际程序:
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处于部分绘制状态。需要在它重新开始工作之前杀死整个应用程序。
我是这样做的:
package com.palewar;
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
public class ThreadActivity extends Activity {
static ProgressDialog dialog;
private Thread downloadThread;
final static Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
dialog.dismiss();
}
};
protected void onDestroy() {
super.onDestroy();
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
downloadThread = (Thread) getLastNonConfigurationInstance();
if (downloadThread != null && downloadThread.isAlive()) {
dialog = ProgressDialog.show(ThreadActivity.this, "",
"Signing in...", false);
}
dialog = ProgressDialog.show(ThreadActivity.this, "",
"Signing in ...", false);
downloadThread = new MyThread();
downloadThread.start();
// processThread();
}
// Save the thread
@Override
public Object onRetainNonConfigurationInstance() {
return downloadThread;
}
static public class MyThread extends Thread {
@Override
public void run() {
try {
// Simulate a slow network
try {
new Thread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
} finally {
}
}
}
}
你也可以试着让我知道它对你是否有效
诀窍是在onPreExecute/onPostExecute期间像往常一样在AsyncTask中显示/解散对话框,尽管在方向改变的情况下,在活动中创建/显示一个对话框的新实例,并将其引用传递给任务。
public class MainActivity extends Activity {
private Button mButton;
private MyTask mTask = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyTask task = (MyTask) getLastNonConfigurationInstance();
if(task != null){
mTask = task;
mTask.mContext = this;
mTask.mDialog = ProgressDialog.show(this, "", "", true);
}
mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
mTask = new MyTask(MainActivity.this);
mTask.execute();
}
});
}
@Override
public Object onRetainNonConfigurationInstance() {
String str = "null";
if(mTask != null){
str = mTask.toString();
mTask.mDialog.dismiss();
}
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
return mTask;
}
private class MyTask extends AsyncTask<Void, Void, Void>{
private ProgressDialog mDialog;
private MainActivity mContext;
public MyTask(MainActivity context){
super();
mContext = context;
}
protected void onPreExecute() {
mDialog = ProgressDialog.show(MainActivity.this, "", "", true);
}
protected void onPostExecute(Void result) {
mContext.mTask = null;
mDialog.dismiss();
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(5000);
return null;
}
}
}
如果你维护两个布局,所有的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只能执行一次。
这是我提出的解决方案:
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.