我的程序在后台线程中执行一些网络活动。在开始之前,它会弹出一个进度对话框。该对话框在处理程序上被解除。这一切都很好,除了当对话框打开时屏幕方向发生变化(背景线程正在运行)。此时,应用程序要么崩溃,要么死锁,要么进入一个奇怪的阶段,在所有线程被杀死之前,应用程序根本无法工作。
我如何处理屏幕方向的变化优雅?
下面的示例代码大致匹配我的实际程序:
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只能执行一次。
我尝试执行jfelectron的解决方案,因为它是“解决这些问题的坚如磐石的解决方案,符合‘Android方式’”,但我花了一些时间来查找并整合所有提到的元素。最终得到了这个略有不同的,我认为更优雅的解决方案,完整地贴在这里。
使用从活动中触发的IntentService在单独的线程上执行长时间运行的任务。该服务向更新对话框的活动发出sticky Broadcast intent。Activity使用showDialog(), onCreateDialog()和onPrepareDialog()来消除在应用程序对象或savedInstanceState包中传递持久化数据的需要。无论应用程序如何中断,这都应该工作。
活动类:
public class TesterActivity extends Activity {
private ProgressDialog mProgressDialog;
private static final int PROGRESS_DIALOG = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b = (Button) this.findViewById(R.id.test_button);
b.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
buttonClick();
}
});
}
private void buttonClick(){
clearPriorBroadcast();
showDialog(PROGRESS_DIALOG);
Intent svc = new Intent(this, MyService.class);
startService(svc);
}
protected Dialog onCreateDialog(int id) {
switch(id) {
case PROGRESS_DIALOG:
mProgressDialog = new ProgressDialog(TesterActivity.this);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setMax(MyService.MAX_COUNTER);
mProgressDialog.setMessage("Processing...");
return mProgressDialog;
default:
return null;
}
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch(id) {
case PROGRESS_DIALOG:
// setup a broadcast receiver to receive update events from the long running process
IntentFilter filter = new IntentFilter();
filter.addAction(MyService.BG_PROCESS_INTENT);
registerReceiver(new MyBroadcastReceiver(), filter);
break;
}
}
public class MyBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if (intent.hasExtra(MyService.KEY_COUNTER)){
int count = intent.getIntExtra(MyService.KEY_COUNTER, 0);
mProgressDialog.setProgress(count);
if (count >= MyService.MAX_COUNTER){
dismissDialog(PROGRESS_DIALOG);
}
}
}
}
/*
* Sticky broadcasts persist and any prior broadcast will trigger in the
* broadcast receiver as soon as it is registered.
* To clear any prior broadcast this code sends a blank broadcast to clear
* the last sticky broadcast.
* This broadcast has no extras it will be ignored in the broadcast receiver
* setup in onPrepareDialog()
*/
private void clearPriorBroadcast(){
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(MyService.BG_PROCESS_INTENT);
sendStickyBroadcast(broadcastIntent);
}}
IntentService经济舱:
public class MyService extends IntentService {
public static final String BG_PROCESS_INTENT = "com.mindspiker.Tester.MyService.TEST";
public static final String KEY_COUNTER = "counter";
public static final int MAX_COUNTER = 100;
public MyService() {
super("");
}
@Override
protected void onHandleIntent(Intent intent) {
for (int i = 0; i <= MAX_COUNTER; i++) {
Log.e("Service Example", " " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(BG_PROCESS_INTENT);
broadcastIntent.putExtra(KEY_COUNTER, i);
sendStickyBroadcast(broadcastIntent);
}
}}
清单文件条目:
应用前部分:
uses-permission android:name="com.mindspiker.Tester.MyService.TEST"
uses-permission android:name="android.permission.BROADCAST_STICKY"
应用程序内部部分
service android:name=".MyService"
我是这样做的:
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 {
}
}
}
}
你也可以试着让我知道它对你是否有效
最简单和最灵活的解决方案是使用带有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();
}
}