当进度对话框和后台线程激活时如何处理屏幕方向变化?

我的程序在后台线程中执行一些networking活动。 在开始之前,它popup一个进度对话框。 对话框在处理程序中被解除。 这一切都工作正常,除非屏幕方向改变,而对话框(和后台线程正在进行)。 在这一点上,应用程序崩溃或死锁,或进入一个奇怪的阶段,应用程序根本不工作,直到所有的线程已被杀死。

我怎样才能妥善处理屏幕方向的变化?

下面的示例代码大致匹配我真正的程序所做的:

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) 

我试图closuresonSaveInstanceState中的进度对话框,但这只是防止立即崩溃。 后台线程仍在进行,UI处于部分绘制状态。 在重新开始工作之前,需要杀死整个应用程序。

当您切换方向时,Android将创build一个新的视图。 你可能会崩溃,因为你的后台线程正试图改变旧的状态。 (它也可能有麻烦,因为你的后台线程不在UI线程上)

我build议让这个mHandler变化,并在方向改变时更新它。

编辑: Google工程师不推荐这种方法,正如Dianne Hackborn (aka hackbod )在这个StackOverflow文章中所描述的那样。 看看这个博客文章了解更多信息。


您必须将其添加到清单中的活动声明中:

 android:configChanges="orientation|screenSize" 

所以它看起来像

 <activity android:label="@string/app_name" android:configChanges="orientation|screenSize|keyboardHidden" android:name=".your.package"> 

问题是当configuration发生改变时,系统破坏活动。 请参阅ConfigurationChanges 。

所以把它放在configuration文件中可以避免系统破坏你的活动。 而是调用onConfigurationChanged(Configuration)方法。

我为这些符合“Android方式”的问题提出了一个坚实的解决scheme。 我有使用IntentService模式的所有长时间运行的操作。
这是我的活动广播意图,IntentService做的工作,将数据保存在数据库,然后广播STICKY意图。
粘滞的部分是重要的,即使在用户启动工作之后的时间内活动被暂停,并且错过了来自ItentService的实时广播,我们仍然可以响应并从调用活动中获取数据。
ProgressDialogs可以用onSaveInstanceSate()很好地工作到这个模式。
基本上,您需要保存一个标志,在已保存的实例包中运行进度对话框。 不要保存进度对话框对象,因为这会泄漏整个活动。
为了对进度对话框有一个持久的句柄,我把它作为一个弱引用存储在应用程序对象中。
在方向改变或任何其他导致活动暂停(电话,用户点击回家等),然后恢复,我closures旧的对话框,并在新创build的活动中重新创build一个新的对话框。
对于无限期的进度对话框,这很容易。 对于进度条风格,您必须将最后一个已知的进度放在包中,以及在活动中本地使用的任何信息以跟踪进度。
在恢复进度时,您将使用此信息重新生成与之前相同状态的进度栏,然后根据事物的当前状态进行更新。
总而言之,将长时间运行的任务放到IntentService中,再加上使用onSaveInstanceState()可以有效地跟踪对话并在整个Activity生命周期事件中恢复。 活动代码的相关位如下。 您还需要在您的BroadcastReceiver中使用逻辑来正确处理粘滞意图,但这超出了这个范围。

  public void doSignIn(View view) { waiting=true; AppClass app=(AppClass) getApplication(); String logingon=getString(R.string.signon); app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true)); ..} @Override protected void onSaveInstanceState(Bundle saveState) { super.onSaveInstanceState(saveState); saveState.putBoolean("waiting",waiting); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(savedInstanceState!=null) { restoreProgress(savedInstanceState); } ...} private void restoreProgress(Bundle savedInstanceState) { waiting=savedInstanceState.getBoolean("waiting"); if (waiting) { AppClass app=(AppClass) getApplication(); ProgressDialog refresher=(ProgressDialog) app.Dialog.get(); refresher.dismiss(); String logingon=getString(R.string.signon); app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true)); } } 

我遇到了同样的问题。 我的活动需要parsingURL中的一些数据,速度很慢。 所以我创build一个线程来完成,然后显示一个进度对话框。 当线程完成时,我让线程通过Handler把一个msg发回到UI线程。 在Handler.handleMessage中,我从线程获取数据对象(就绪),并将其填充到UI。 所以这和你的例子非常相似。

经过许多试验和错误,看起来我find了解决scheme。 至less现在我可以随时旋转屏幕,在线程完成之前或之后。 在所有testing中,对话框都被正确closures,所有行为都如预期的那样。

我做了什么如下所示。 目标是填充我的数据模型(mDataObject),然后将其填充到UI。 应该让屏幕随时旋转而不会感到意外。

 class MyActivity { private MyDataObject mDataObject = null; private static MyThread mParserThread = null; // static, or make it singleton OnCreate() { ... Object retained = this.getLastNonConfigurationInstance(); if(retained != null) { // data is already completely obtained before config change // by my previous self. // no need to create thread or show dialog at all mDataObject = (MyDataObject) retained; populateUI(); } else if(mParserThread != null && mParserThread.isAlive()){ // note: mParserThread is a static member or singleton object. // config changed during parsing in previous instance. swap handler // then wait for it to finish. mParserThread.setHandler(new MyHandler()); } else { // no data and no thread. likely initial run // create thread, show dialog mParserThread = new MyThread(..., new MyHandler()); mParserThread.start(); showDialog(DIALOG_PROGRESS); } } // http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html public Object onRetainNonConfigurationInstance() { // my future self can get this without re-downloading // if it's already ready. return mDataObject; } // use Activity.showDialog instead of ProgressDialog.show // so the dialog can be automatically managed across config change @Override protected Dialog onCreateDialog(int id) { // show progress dialog here } // inner class of MyActivity private class MyHandler extends Handler { public void handleMessage(msg) { mDataObject = mParserThread.getDataObject(); populateUI(); dismissDialog(DIALOG_PROGRESS); } } } class MyThread extends Thread { Handler mHandler; MyDataObject mDataObject; public MyHandler(..., Handler h) {...; mHandler = h;} // constructor with handler param public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller public void run() { mDataObject = new MyDataObject(); // do the lengthy task to fill mDataObject with data lengthyTask(mDataObject); // done. notify activity mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data. } } 

这对我很有用。 我不知道这是否是Androiddevise的“正确”方法 – 他们声称“在屏幕旋转期间销毁/重新创build活动”实际上使事情变得更容易,所以我猜测它不应该太棘手。

如果您在我的代码中看到问题,请告诉我。 如上所述,我真的不知道是否有任何副作用。

我的解决scheme是扩展ProgressDialog类来获得我自己的MyProgressDialog
在显示Dialog之前,我重新定义了show()dismiss()方法来locking方向,并在Dialogclosures时将其解锁。 所以当显示Dialog并且设备的方向改变时,屏幕的方向一直保持到调用dismiss() ,屏幕方向将根据传感器值/设备方向而改变。

这是我的代码:

 public class MyProgressDialog extends ProgressDialog { private Context mContext; public MyProgressDialog(Context context) { super(context); mContext = context; } public MyProgressDialog(Context context, int theme) { super(context, theme); mContext = context; } public void show() { if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); else ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); super.show(); } public void dismiss() { super.dismiss(); ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); } } 

原来认为的问题是,代码将无法在屏幕方向更改中生存。 显然这是通过让程序自己处理屏幕方向改变来“解决”的,而不是让UI框架(通过调用onDestroy)来实现)。

我会提出,如果潜在的问题是,该程序将无法生存onDestroy(),那么接受的解决scheme只是一个解决scheme,使程序严重的其他问题和漏洞。 请记住,Android框架明确指出,由于您无法控制的情况,您的活动几乎可能随时被摧毁。 因此,您的活动必须能够以任何理由在onDestroy()和随后的onCreate()上生存,而不仅仅是屏幕方向更改。

如果您要自己接受处理屏幕方向更改以解决OP的问题,则需要validationonDestroy()的其他原因不会导致相同的错误。 你能做到吗? 如果不是的话,我会怀疑“接受”的答案是否真的是一个非常好的答案。

我面临同样的问题,我想出了一个解决scheme,没有使用ProgressDialog,我得到更快的结果。

我所做的是创build一个有ProgressBar的布局。

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ProgressBar android:id="@+id/progressImage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </RelativeLayout> 

然后在onCreate方法中执行以下操作

 public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.progress); } 

然后在一个线程中执行长时间的任务,当完成时有一个Runnable将内容视图设置为您想要用于此活动的真实布局。

例如:

 mHandler.post(new Runnable(){ public void run() { setContentView(R.layout.my_layout); } }); 

这就是我所做的,而且我发现它比显示ProgressDialog的速度更快,而且它的侵扰性更小,在我看来更好看。

但是,如果你想使用ProgressDialog,那么这个答案不适合你。

我发现了一个我在其他地方还没有看到的解决scheme。 你可以使用一个自定义的应用程序对象,知道你是否有后台任务,而不是试图做到这一点在活动中被取消和重新定位改变。 我在这里博客了。

我将提供我的方法来处理这个轮换问题。 这可能与OP不相关,因为他没有使用AsyncTask ,但也许其他人会觉得它有用。 这很简单,但似乎为我做的工作:

我有一个名为BackgroundLoginTask的嵌套AsyncTask类的login活动。

在我的BackgroundLoginTask中,除了在调用ProgressDialog的closures时添加一个空的检查外,我什么也不做。

 @Override protected void onPostExecute(Boolean result) { if (pleaseWaitDialog != null) pleaseWaitDialog.dismiss(); [...] } 

这是为了处理后台任务完成而Activity不可见的情况,因此进程对话框已经被onPause()方法解除了。

接下来,在我的父Activity类中,我创build了AsyncTask类的全局静态句柄,我的ProgressDialog (嵌套的AsyncTask可以访问这些variables):

 private static BackgroundLoginTask backgroundLoginTask; private static ProgressDialog pleaseWaitDialog; 

这有两个目的:首先,它允许我的Activity始终访问AsyncTask对象,即使是从一个新的,后置的活动。 其次,它允许我的BackgroundLoginTask访问并closuresProgressDialog即使在旋转之后。

接下来,我将这个添加到onPause() ,导致进度对话框消失,当我们的Activity离开前景(防止丑陋的“逼近”崩溃):

  if (pleaseWaitDialog != null) pleaseWaitDialog.dismiss(); 

最后,我在我的onResume()方法中有以下几点:

 if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING)) { if (pleaseWaitDialog != null) pleaseWaitDialog.show(); } 

这允许在重新创buildActivity后重新出现Dialog

这是整个class级:

 public class NSFkioskLoginActivity extends NSFkioskBaseActivity { private static BackgroundLoginTask backgroundLoginTask; private static ProgressDialog pleaseWaitDialog; private Controller cont; // This is the app entry point. /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (CredentialsAvailableAndValidated()) { //Go to main menu and don't run rest of onCreate method. gotoMainMenu(); return; } setContentView(R.layout.login); populateStoredCredentials(); } //Save current progress to options when app is leaving foreground @Override public void onPause() { super.onPause(); saveCredentialsToPreferences(false); //Get rid of progress dialog in the event of a screen rotation. Prevents a crash. if (pleaseWaitDialog != null) pleaseWaitDialog.dismiss(); } @Override public void onResume() { super.onResume(); if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING)) { if (pleaseWaitDialog != null) pleaseWaitDialog.show(); } } /** * Go to main menu, finishing this activity */ private void gotoMainMenu() { startActivity(new Intent(getApplicationContext(), NSFkioskMainMenuActivity.class)); finish(); } /** * * @param setValidatedBooleanTrue If set true, method will set CREDS_HAVE_BEEN_VALIDATED to true in addition to saving username/password. */ private void saveCredentialsToPreferences(boolean setValidatedBooleanTrue) { SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE); SharedPreferences.Editor prefEditor = settings.edit(); EditText usernameText = (EditText) findViewById(R.id.editTextUsername); EditText pswText = (EditText) findViewById(R.id.editTextPassword); prefEditor.putString(USERNAME, usernameText.getText().toString()); prefEditor.putString(PASSWORD, pswText.getText().toString()); if (setValidatedBooleanTrue) prefEditor.putBoolean(CREDS_HAVE_BEEN_VALIDATED, true); prefEditor.commit(); } /** * Checks if user is already signed in */ private boolean CredentialsAvailableAndValidated() { SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE); if (settings.contains(USERNAME) && settings.contains(PASSWORD) && settings.getBoolean(CREDS_HAVE_BEEN_VALIDATED, false) == true) return true; else return false; } //Populate stored credentials, if any available private void populateStoredCredentials() { SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE); settings.getString(USERNAME, ""); EditText usernameText = (EditText) findViewById(R.id.editTextUsername); usernameText.setText(settings.getString(USERNAME, "")); EditText pswText = (EditText) findViewById(R.id.editTextPassword); pswText.setText(settings.getString(PASSWORD, "")); } /** * Validate credentials in a seperate thread, displaying a progress circle in the meantime * If successful, save credentials in preferences and proceed to main menu activity * If not, display an error message */ public void loginButtonClick(View view) { if (phoneIsOnline()) { EditText usernameText = (EditText) findViewById(R.id.editTextUsername); EditText pswText = (EditText) findViewById(R.id.editTextPassword); //Call background task worker with username and password params backgroundLoginTask = new BackgroundLoginTask(); backgroundLoginTask.execute(usernameText.getText().toString(), pswText.getText().toString()); } else { //Display toast informing of no internet access String notOnlineMessage = getResources().getString(R.string.noNetworkAccessAvailable); Toast toast = Toast.makeText(getApplicationContext(), notOnlineMessage, Toast.LENGTH_SHORT); toast.show(); } } /** * * Takes two params: username and password * */ public class BackgroundLoginTask extends AsyncTask<Object, String, Boolean> { private Exception e = null; @Override protected void onPreExecute() { cont = Controller.getInstance(); //Show progress dialog String pleaseWait = getResources().getString(R.string.pleaseWait); String commWithServer = getResources().getString(R.string.communicatingWithServer); if (pleaseWaitDialog == null) pleaseWaitDialog= ProgressDialog.show(NSFkioskLoginActivity.this, pleaseWait, commWithServer, true); } @Override protected Boolean doInBackground(Object... params) { try { //Returns true if credentials were valid. False if not. Exception if server could not be reached. return cont.validateCredentials((String)params[0], (String)params[1]); } catch (Exception e) { this.e=e; return false; } } /** * result is passed from doInBackground. Indicates whether credentials were validated. */ @Override protected void onPostExecute(Boolean result) { //Hide progress dialog and handle exceptions //Progress dialog may be null if rotation has been switched if (pleaseWaitDialog != null) { pleaseWaitDialog.dismiss(); pleaseWaitDialog = null; } if (e != null) { //Show toast with exception text String networkError = getResources().getString(R.string.serverErrorException); Toast toast = Toast.makeText(getApplicationContext(), networkError, Toast.LENGTH_SHORT); toast.show(); } else { if (result == true) { saveCredentialsToPreferences(true); gotoMainMenu(); } else { String toastText = getResources().getString(R.string.invalidCredentialsEntered); Toast toast = Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT); toast.show(); } } } } } 

我绝不是经验丰富的Android开发者,所以随时发表评论。

将长期任务移到一个单独的class级。 实施它作为一个主题观察员模式。 每当创build活动时注册,同时closures取消任务类的注册。 任务类可以使用AsyncTask。

诀窍是像往常一样在onPreExecute / onPostExecute期间在AsyncTask中显示/closures对话框,但是在方向更改的情况下,在活动中创build/显示对话框的新实例并将其引用传递给任务。

 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; } } } 

我已经这样做了:

  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 { } } } } 

你也可以尝试让我知道它适用于你或不

如果你创build一个后台Service来完成所有繁重的工作(tcp request / response,unmarshalling), ViewActivity可以被销毁并重新创build而不会泄露窗口或丢失数据。 这允许Android推荐的行为,即在每个configuration更改 (例如,每个方向更改) 上销毁活动 。

这有点复杂,但它是调用服务器请求,数据前/后处理等最好的方法。

您甚至可以使用您的Service将每个请求排列到服务器上,这样可以轻松高效地处理这些事情。

开发指南有关于Services的完整章节 。

我有一个实现,允许活动在屏幕方向更改销毁,但仍然成功地销毁重新创build的活动中的对话框。 我使用...NonConfigurationInstance将后台任务附加到重新创build的活动。 正常的Android框架处理重新创build对话框本身,没有什么改变。

我subclassed 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); } ... } 

In my activity class I added a field backgroundTask referring to the 'owned' backgroundtask, and I update this field using onRetainNonConfigurationInstance and 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; } ... } 

Suggestions for further improvement:

  • Clear the backgroundTask reference in the activity after the task is finished to release any memory or other resources associated with it.
  • Clear the ownerActivity reference in the backgroundtask before the activity is destroyed in case it will not be recreated immediately.
  • Create a BackgroundTask interface and/or collection to allow different types of tasks to run from the same owning activity.

If you maintain two layouts, all UI thread should be terminated.

If you use AsynTask, then you can easily call .cancel() method inside onDestroy() method of current activity.

 @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(); } 

For AsyncTask, read more in "Cancelling a task" section at here .

Update: Added condition to check status, as it can be only cancelled if it is in running state. Also note that the AsyncTask can only be executed one time.

Tried to implement jfelectron 's solution because it is a " rock-solid solution to these issues that conforms with the 'Android Way' of things " but it took some time to look up and put together all the elements mentioned. Ended up with this slightly different, and I think more elegant, solution posted here in it's entirety.

Uses an IntentService fired from an activity to perform the long running task on a separate thread. The service fires back sticky Broadcast Intents to the activity which update the dialog. The Activity uses showDialog(), onCreateDialog() and onPrepareDialog() to eliminate the need to have persistent data passed in the application object or the savedInstanceState bundle. This should work no matter how your application is interrupted.

Activity Class:

 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 Class:

 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); } }} 

Manifest file entries:

before application section:

 uses-permission android:name="com.mindspiker.Tester.MyService.TEST" uses-permission android:name="android.permission.BROADCAST_STICKY" 

inside application section

 service android:name=".MyService" 

This is my proposed solution:

  • 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.

I've tried EVERYTHING. Spent days experimenting. I didn't want to block the activity from rotating. My scenario was:

  1. A progress dialog showing dynamic information to the user. Eg: "Connecting to server…", "Downloading data…", etc.
  2. A thread doing the heavy stuff and updating the dialog
  3. Updating the UI with the results at the end.

The problem was, when rotating the screen, every solution on the book failed. Even with the AsyncTask class, which is the correct Android way of dealing with this situations. When rotating the screen, the current Context that the starting thread is working with, is gone, and that messes up with the dialog that is showing. The problem was always the Dialog, no matter how many tricks I added to the code (passing new contexts to running threads, retaining thread states through rotations, etc…). The code complexity at the end was always huge and there was always something that could go wrong.

The only solution that worked for me was the Activity/Dialog trick. It's simple and genius and it's all rotation proof:

  1. Instead of creating a Dialog and ask to show it, create an Activity that has been set in the manifest with android:theme="@android:style/Theme.Dialog". So, it just looks like a dialog.

  2. Replace showDialog(DIALOG_ID) with startActivityForResult(yourActivityDialog, yourCode);

  3. Use onActivityResult in the calling Activity to get the results from the executing thread (even the errors) and update the UI.

  4. On your 'ActivityDialog', use threads or AsyncTask to execute long tasks and onRetainNonConfigurationInstance to save "dialog" state when rotating the screen.

This is fast and works fine. I still use dialogs for other tasks and the AsyncTask for something that doesn't require a constant dialog on screen. But with this scenario, I always go for the Activity/Dialog pattern.

And, I didn't try it, but it's even possible to block that Activity/Dialog from rotating, when the thread is running, speeding things up, while allowing the calling Activity to rotate.

These days there is a much more distinct way to handle these types of issues. The typical approach is:

1. Ensure your data is properly seperated from the UI:

Anything that is a background process should be in a retained Fragment (set this with Fragment.setRetainInstance() . This becomes your 'persistent data storage' where anything data based that you would like retained is kept. After the orientation change event, this Fragment will still be accessible in its original state through a FragmentManager.findFragmentByTag() call (when you create it you should give it a tag not an ID as it is not attached to a View ).

See the Handling Runtime Changes developed guide for information about doing this correctly and why it is the best option.

2. Ensure you are interfacing correctly and safely between the background processs and your UI:

You must reverse your linking process. At the moment your background process attaches itself to a View – instead your View should be attaching itself to the background process. It makes more sense right? The View 's action is dependent on the background process, whereas the background process is not dependent on the View .This means changing the link to a standard Listener interface. Say your process (whatever class it is – whether it is an AsyncTask , Runnable or whatever) defines a OnProcessFinishedListener , when the process is done it should call that listener if it exists.

This answer is a nice concise description of how to do custom listeners.

3. Link your UI into the data process whenever the UI is created (including orientation changes):

Now you must worry about interfacing the background task with whatever your current View structure is. If you are handling your orientation changes properly (not the configChanges hack people always recommend), then your Dialog will be recreated by the system. This is important, it means that on the orientation change, all your Dialog 's lifecycle methods are recalled. So in any of these methods ( onCreateDialog is usually a good place), you could do a call like the following:

 DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG"); if (f != null) { f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() { public void onProcessFinished() { dismiss(); } }); } 

See the Fragment lifecycle for deciding where setting the listener best fits in your individual implementation.

This is a general approach to providing a robust and complete solution to the generic problem asked in this question. There is probably a few minor pieces missing in this answer depending on your individual scenario, but this is generally the most correct approach for properly handling orientation change events.

我面临同样的情况。 What I did was get only one instance for my progress dialog in the entire application.

First, I created a DialogSingleton class to get only one instance (Singleton pattern)

 public class DialogSingleton { private static Dialog dialog; private static final Object mLock = new Object(); private static DialogSingleton instance; private DialogSingleton() { } public static DialogSingleton GetInstance() { synchronized (mLock) { if(instance == null) { instance = new DialogSingleton(); } return instance; } } public void DialogShow(Context context, String title) { if(!((Activity)context).isFinishing()) { dialog = new ProgressDialog(context, 2); dialog.setCanceledOnTouchOutside(false); dialog.setTitle(title); dialog.show(); } } public void DialogDismiss(Context context) { if(!((Activity)context).isFinishing() && dialog.isShowing()) { dialog.dismiss(); } } } 

As I show in this class, I have the progress dialog as attribute. Every time I need to show a progress dialog, I get the unique instance and create a new ProgressDialog.

 DialogSingleton.GetInstance().DialogShow(this, "My title here!"); 

When I am done with the background task, I call again the unique instance and dismiss its dialog.

 DialogSingleton.GetInstance().DialogDismiss(this); 

I save the background task status in my shared preferences. When I rotate the screen, I ask if I have a task running for this activity: (onCreate)

 if(Boolean.parseBoolean(preference.GetValue(IS_TASK_NAME_EXECUTED_KEY, "boolean").toString())) { DialogSingleton.GetInstance().DialogShow(this, "Checking credentials!"); } // preference object gets the info from shared preferences (my own implementation to get and put data to shared preferences) and IS_TASK_NAME_EXECUTED_KEY is the key to save this flag (flag to know if this activity has a background task already running). 

When I start running a background task:

 preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean"); DialogSingleton.GetInstance().DialogShow(this, "My title here!"); 

When I finish running a background task:

 preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean"); DialogSingleton.GetInstance().DialogDismiss(ActivityName.this); 

我希望它有帮助。

This is a very old question that came up on the sidebar for some reason.

If the background task only needs to survive while the activity is in the foreground, the "new" solution is to host the background thread (or, preferably, AsyncTask ) in a retained fragment , as described in this developer guide and numerous Q&As .

A retained fragment survives if the activity is destroyed for a configuration change, but not when the activity is destroyed in the background or back stack. Therefore, the background task should still be interrupted if isChangingConfigurations() is false in onPause() .

Seems far too 'quick and dirty' to be true so please point out the flaws but what I found worked was…

Within the onPostExecute method of my AsyncTask, I simply wrapped the '.dismiss' for the progress dialog in a try/catch block (with an empty catch) and then simply ignored the exception that was raised. Seems wrong to do but appears there are no ill effects (at least for what I am doing subsequently which is to start another activity passing in the result of my long running query as an Extra)

The simplest and most flexible solution is to use an AsyncTask with a static reference to ProgressBar . This provides an encapsulated and thus reusable solution to orientation change problems. This solution has served me well for varying asyncronous tasks including internet downloads, communicating with Services , and filesystem scans. The solution has been well tested on multiple android versions and phone models. A complete demo can be found here with specific interest in DownloadFile.java

I present the following as a concept example

 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(); } } } 

Usage in an Android Activity is simple

 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(); } } 

i have found and easier solution to handle threads when orientation change. You can just keep an static reference to your activity/fragment and verify if its null before acting on the ui. I suggest using a try catch too:

  public class DashListFragment extends Fragment { private static DashListFragment ACTIVE_INSTANCE; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ACTIVE_INSTANCE = this; new Handler().postDelayed(new Runnable() { public void run() { try { if (ACTIVE_INSTANCE != null) { setAdapter(); // this method do something on ui or use context } } catch (Exception e) {} } }, 1500l); } @Override public void onDestroy() { super.onDestroy(); ACTIVE_INSTANCE = 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. 这是我的例子:

build立

 public class MyContentView extends View{ public MyContentView(Context context){ super(context); } @Override public void onConfigurationChanged(Configuration newConfig){ super.onConfigurationChanged(newConfig); //DO SOMETHING HERE!! :D } } 

Implementation 1 – Dialog

 Dialog dialog = new Dialog(context); //set up dialog dialog.setContentView(new MyContentView(context)); dialog.show(); 

Implementation 2 – AlertDialog.Builder

 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(); 

Implementation 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(); 

I am a fresher in android and I tried this and it's worked.

 public class loadTotalMemberByBranch extends AsyncTask<Void, Void,Void> { ProgressDialog progressDialog = new ProgressDialog(Login.this); int ranSucess=0; @Override protected void onPreExecute() { // TODO Auto-generated method stub super.onPreExecute(); progressDialog.setTitle(""); progressDialog.isIndeterminate(); progressDialog.setCancelable(false); progressDialog.show(); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); } @Override protected Void doInBackground(Void... params) { // TODO Auto-generated method stub return null; } @Override protected void onPostExecute(Void result) { // TODO Auto-generated method stub super.onPostExecute(result); progressDialog.dismiss(); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); } }