我的程序在后台线程中执行一些网络活动。在开始之前,它会弹出一个进度对话框。该对话框在处理程序上被解除。这一切都很好,除了当对话框打开时屏幕方向发生变化(背景线程正在运行)。此时,应用程序要么崩溃,要么死锁,要么进入一个奇怪的阶段,在所有线程被杀死之前,应用程序根本无法工作。
我如何处理屏幕方向的变化优雅?
下面的示例代码大致匹配我的实际程序:
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处于部分绘制状态。需要在它重新开始工作之前杀死整个应用程序。
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();
编辑:谷歌工程师不推荐这种方法,正如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)方法。
我有一个实现,允许活动在屏幕方向改变上被销毁,但仍然在重新创建的活动中成功地销毁对话框。
我用…NonConfigurationInstance将后台任务附加到重新创建的活动。
正常的Android框架处理重新创建对话框本身,没有任何改变。
我子类化了AsyncTask,为“拥有”活动添加了一个字段,以及一个更新这个所有者的方法。
class MyBackgroundTask extends AsyncTask<...> {
MyBackgroundTask (Activity a, ...) {
super();
this.ownerActivity = a;
}
public void attach(Activity a) {
ownerActivity = a;
}
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
ownerActivity.dismissDialog(DIALOG_PROGRESS);
}
...
}
在我的活动类中,我添加了一个字段backgroundTask引用'owned' backgroundTask,我使用onRetainNonConfigurationInstance和getLastNonConfigurationInstance更新这个字段。
class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
...
if (getLastNonConfigurationInstance() != null) {
backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
backgroundTask.attach(this);
}
}
void startBackgroundTask() {
backgroundTask = new MyBackgroundTask(this, ...);
showDialog(DIALOG_PROGRESS);
backgroundTask.execute(...);
}
public Object onRetainNonConfigurationInstance() {
if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
return backgroundTask;
return null;
}
...
}
进一步改进建议:
在任务完成后,清除活动中的backgroundTask引用以释放与之关联的所有内存或其他资源。
在活动被销毁之前,清除后台任务中的ownerActivity引用,以防它不会立即被重新创建。
创建一个BackgroundTask接口和/或集合,以允许不同类型的任务从相同的活动中运行。