我的程序在后台线程中执行一些网络活动。在开始之前,它会弹出一个进度对话框。该对话框在处理程序上被解除。这一切都很好,除了当对话框打开时屏幕方向发生变化(背景线程正在运行)。此时,应用程序要么崩溃,要么死锁,要么进入一个奇怪的阶段,在所有线程被杀死之前,应用程序根本无法工作。
我如何处理屏幕方向的变化优雅?
下面的示例代码大致匹配我的实际程序:
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只能执行一次。
诀窍是在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;
}
}
}
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();
最简单和最灵活的解决方案是使用带有ProgressBar静态引用的AsyncTask。这为方向更改问题提供了一个封装的、因此可重用的解决方案。这个解决方案很好地帮助我完成了各种异步任务,包括互联网下载、与服务通信和文件系统扫描。该解决方案已经在多个安卓版本和手机型号上进行了良好的测试。完整的演示可以在DownloadFile.java中找到
下面是一个概念示例
public class SimpleAsync extends AsyncTask<String, Integer, String> {
private static ProgressDialog mProgressDialog = null;
private final Context mContext;
public SimpleAsync(Context context) {
mContext = context;
if ( mProgressDialog != null ) {
onPreExecute();
}
}
@Override
protected void onPreExecute() {
mProgressDialog = new ProgressDialog( mContext );
mProgressDialog.show();
}
@Override
protected void onPostExecute(String result) {
if ( mProgressDialog != null ) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
}
@Override
protected void onProgressUpdate(Integer... progress) {
mProgressDialog.setProgress( progress[0] );
}
@Override
protected String doInBackground(String... sUrl) {
// Do some work here
publishProgress(1);
return null;
}
public void dismiss() {
if ( mProgressDialog != null ) {
mProgressDialog.dismiss();
}
}
}
在Android Activity中的使用很简单
public class MainActivity extends Activity {
DemoServiceClient mClient = null;
DownloadFile mDownloadFile = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView( R.layout.main );
mDownloadFile = new DownloadFile( this );
Button downloadButton = (Button) findViewById( R.id.download_file_button );
downloadButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View view) {
mDownloadFile.execute( "http://www.textfiles.com/food/bakebred.txt");
}
});
}
@Override
public void onPause() {
super.onPause();
mDownloadFile.dismiss();
}
}