Android 进度对话框和后台线程处于活动状态时如何处理屏幕方向变化?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1111980/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 02:42:47  来源:igfitidea点击:

How to handle screen orientation change when progress dialog and background thread active?

androidandroid-activityandroid-dialog

提问by Heikki Toivonen

My program does some network activity in a background thread. Before starting, it pops up a progress dialog. The dialog is dismissed on the handler. This all works fine, except when screen orientation changes while the dialog is up (and the background thread is going). At this point the app either crashes, or deadlocks, or gets into a weird stage where the app does not work at all until all the threads have been killed.

我的程序在后台线程中执行一些网络活动。在开始之前,它会弹出一个进度对话框。该对话框在处理程序上被关闭。这一切都很好,除非在对话框启动时屏幕方向发生变化(并且后台线程正在运行)。在这一点上,应用程序要么崩溃,要么死锁,要么进入一个奇怪的阶段,直到所有线程都被杀死,应用程序才能工作。

How can I handle the screen orientation change gracefully?

如何优雅地处理屏幕方向变化?

The sample code below matches roughly what my real program does:

下面的示例代码与我的真实程序所做的大致相符:

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

Stack:

堆:

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

I have tried to dismiss the progress dialog in onSaveInstanceState, but that just prevents an immediate crash. The background thread is still going, and the UI is in partially drawn state. Need to kill the whole app before it starts working again.

我试图关闭 onSaveInstanceState 中的进度对话框,但这只会防止立即崩溃。后台线程还在继续,UI处于部分绘制状态。需要在它再次开始工作之前杀死整个应用程序。

采纳答案by haseman

When you switch orientations, Android will create a new View. You're probably getting crashes because your background thread is trying to change the state on the old one. (It may also be having trouble because your background thread isn't on the UI thread)

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

I'd suggest making that mHandler volatile and updating it when the orientation changes.

我建议让 mHandler 变得易变,并在方向改变时更新它。

回答by sonxurxo

Edit:Google engineers do not recommend this approach, as described by Dianne Hackborn (a.k.a. hackbod) in this StackOverflow post. Check out this blog postfor more information.

编辑:Google 工程师不推荐这种方法,正如 Dianne Hackborn(又名hackbod)在StackOverflow 帖子中所述。查看此博客文章了解更多信息。



You have to add this to the activity declaration in the manifest:

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

android:configChanges="orientation|screenSize"

so it looks like

所以它看起来像

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

The matter is that the system destroys the activity when a change in the configuration occurs. See ConfigurationChanges.

问题是当配置发生变化时,系统会破坏活动。请参阅配置更改

So putting that in the configuration file avoids the system to destroy your activity. Instead it invokes the onConfigurationChanged(Configuration)method.

所以把它放在配置文件中可以避免系统破坏你的活动。相反,它调用该onConfigurationChanged(Configuration)方法。

回答by jfelectron

I came up with a rock-solid solution for these issues that conforms with the 'Android Way' of things. I have all my long-running operations using the IntentService pattern.

对于这些问题,我想出了一个坚如磐石的解决方案,它符合“Android 方式”的事物。我使用 IntentService 模式进行了所有长时间运行的操作。

That is, my activities broadcast intents, the IntentService does the work, saves the data in the DB and then broadcasts stickyintents. The sticky part is important, such that even if the Activity was paused during during the time after the user initiated the work and misses the real time broadcast from the IntentService we can still respond and pick up the data from the calling Activity. ProgressDialogs can work with this pattern quite nicely with onSaveInstanceState().

也就是说,我的活动广播意图,IntentService 完成工作,将数据保存在数据库中,然后广播粘性意图。粘性部分很重要,这样即使 Activity 在用户启动工作后的时间内暂停并且错过了来自 IntentService 的实时广播,我们仍然可以响应并从调用的 Activity 中获取数据。ProgressDialogs 可以很好地使用这种模式onSaveInstanceState()

Basically, you need to save a flag that you have a progress dialog running in the saved instance bundle. Do notsave the progress dialog object because this will leak the entire Activity. To have a persistent handle to the progress dialog, I store it as a weak reference in the application object. On orientation change or anything else that causes the Activity to pause (phone call, user hits home etc.) and then resume, I dismiss the old dialog and recreate a new dialog in the newly created Activity.

基本上,您需要保存一个标志,表明您在保存的实例包中运行了一个进度对话框。不要保存进度对话框对象,因为这会泄漏整个 Activity。为了获得进度对话框的持久句柄,我将其作为弱引用存储在应用程序对象中。在方向改变或其他任何导致 Activity 暂停(电话、用户回家等)然后恢复的情况下,我关闭旧对话框并在新创建的 Activity 中重新创建一个新对话框。

For indefinite progress dialogs this is easy. For progress bar style, you have to put the last known progress in the bundle and whatever information you're using locally in the activity to keep track of the progress. On restoring the progress, you'll use this information to re-spawn the progress bar in the same state as before and then update based on the current state of things.

对于不确定的进度对话框,这很容易。对于进度条样式,您必须将最后一个已知进度放入包中,以及您在活动中本地使用的任何信息以跟踪进度。在恢复进度时,您将使用此信息以与以前相同的状态重新生成进度条,然后根据当前状态进行更新。

So to summarize, putting long-running tasks into an IntentService coupled with judicious use of onSaveInstanceState()allows you to efficiently keep track of dialogs and restore then across the Activity life-cycle events. Relevant bits of Activity code are below. You'll also need logic in your BroadcastReceiver to handle Sticky intents appropriately, but that is beyond the scope of this.

总而言之,将长时间运行的任务放入 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));
    }
}

回答by samsonsu

I met the same problem. My activity needs to parse some data from a URL and it's slow. So I create a thread to do so, then show a progress dialog. I let the thread post a message back to UI thread via Handlerwhen it's finished. In Handler.handleMessage, I get the data object (ready now) from thread and populate it to UI. So it's very similar to your example.

我遇到了同样的问题。我的活动需要从 URL 解析一些数据,而且速度很慢。所以我创建了一个线程来这样做,然后显示一个进度对话框。Handler当线程完成时,我让线程通过它向 UI 线程发回一条消息。在 中Handler.handleMessage,我从线程获取数据对象(现在准备就绪)并将其填充到 UI。所以它与你的例子非常相似。

After a lot of trial and error it looks like I found a solution. At least now I can rotate screen at any moment, before or after the thread is done. In all tests, the dialog is properly closed and all behaviors are as expected.

经过大量的反复试验,我似乎找到了解决方案。至少现在我可以在线程完成之前或之后随时旋转屏幕。在所有测试中,对话框都正确关闭,所有行为都符合预期。

What I did is shown below. The goal is to fill my data model (mDataObject) and then populate it to UI. Should allow screen rotation at any moment without surprise.

我所做的如下所示。目标是填充我的数据模型 ( 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;

    // constructor with handler param
    public MyHandler(..., Handler h) {
        ...
        mHandler = h;
    }

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

That's what works for me. I don't know if this is the "correct" method as designed by Android -- they claim this "destroy/recreate activity during screen rotation" actually makes things easier, so I guess it shouldn't be too tricky.

这就是对我有用的东西。我不知道这是否是 Android 设计的“正确”方法——他们声称这种“在屏幕旋转期间销毁/重新创建活动”实际上使事情变得更容易,所以我想它不应该太棘手。

Let me know if you see a problem in my code. As said above I don't really know if there is any side effect.

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

回答by gymshoe

The original perceived problem was that the code would not survive a screen orientation change. Apparently this was "solved" by having the program handle the screen orientation change itself, instead of letting the UI framework do it (via calling onDestroy)).

最初发现的问题是代码无法在屏幕方向更改后继续存在。显然,这是通过让程序自行处理屏幕方向更改来“解决”的,而不是让 UI 框架来完成(通过调用 onDestroy))。

I would submit that if the underlying problem is that the program will not survive onDestroy(), then the accepted solution is just a workaround that leaves the program with serious other problems and vulnerabilities. Remember that the Android framework specifically states that your activity is at risk for being destroyed almost at any time due to circumstances outside your control. Therefore, your activity must be able to survive onDestroy() and subsequent onCreate() for any reason, not just a screen orientation change.

我认为,如果根本问题是程序在 Destroy() 上无法生存,那么公认的解决方案只是一种使程序存在严重其他问题和漏洞的解决方法。请记住,Android 框架明确指出,由于您无法控制的情况,您的活动几乎随时都有被破坏的风险。因此,您的活动必须能够出于任何原因在 onDestroy() 和随后的 onCreate() 中存活,而不仅仅是屏幕方向更改。

If you are going to accept handling screen orientation changes yourself to solve the OP's problem, you need to verify that other causes of onDestroy() do not result in the same error. Are you able to do this? If not, I would question whether the "accepted" answer is really a very good one.

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

回答by Oli

My solution was to extend the ProgressDialogclass to get my own MyProgressDialog.
I redefined show()and dismiss()methods to lock the orientation before showing the Dialogand unlock it back when Dialogis dismissed. So when the Dialogis shown and the orientation of the device changes, the orientation of the screen remains until dismiss()is called, then screen-orientation changes according to sensor-values/device-orientation.

我的解决方案是扩展ProgressDialog课程以获得我自己的MyProgressDialog.
我重新定义show()dismiss()方法,以显示之前锁定的方位Dialog时并解锁回Dialog被解雇。因此,当Dialog显示 并且设备的方向发生变化时,屏幕的方向保持不变,直到dismiss()被调用,然后屏幕方向根据传感器值/设备方向发生变化。

Here is my code:

这是我的代码:

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

}

回答by Pzanno

I faced this same problem, and I came up with a solution that didn't invole using the ProgressDialog and I get faster results.

我遇到了同样的问题,我想出了一个不使用 ProgressDialog 的解决方案,我得到了更快的结果。

What I did was create a layout that has a ProgressBar in it.

我所做的是创建一个布局,其中包含一个 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>

Then in the onCreate method do the following

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

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

Then do the long task in a thread, and when that's finished have a Runnable set the content view to the real layout you want to use for this activity.

然后在线程中执行长任务,完成后让 Runnable 将内容视图设置为要用于此活动的真实布局。

For example:

例如:

mHandler.post(new Runnable(){

public void run() {
        setContentView(R.layout.my_layout);
    } 
});

This is what I did, and I've found that it runs faster than showing the ProgressDialog and it's less intrusive and has a better look in my opinion.

这就是我所做的,我发现它比显示 ProgressDialog 运行得更快,而且它的侵入性更小,而且在我看来更好看。

However, if you're wanting to use the ProgressDialog, then this answer isn't for you.

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

回答by Heikki Toivonen

I discovered a solution to this that I haven't yet seen elsewhere. You can use a custom application object that knows if you have background tasks going, instead of trying to do this in the activity that gets destroyed and recreated on orientation change. I blogged about this in here.

我发现了一个解决方案,我还没有在其他地方看到过。您可以使用自定义应用程序对象,该对象知道您是否有后台任务,而不是尝试在因方向更改而被破坏和重新创建的活动中执行此操作。我在这里写了关于这个的博客。

回答by Anders

I going to contribute my approach to handling this rotation issue. This may not be relevant to OP as he's not using AsyncTask, but maybe others will find it useful. It's pretty simple but it seems to do the job for me:

我将贡献我的方法来处理这个轮换问题。这可能与 OP 无关,因为他没有使用AsyncTask,但也许其他人会发现它很有用。这很简单,但似乎对我有用:

I have a login activity with a nested AsyncTaskclass called BackgroundLoginTask.

我有一个AsyncTask名为BackgroundLoginTask.

In my BackgroundLoginTaskI don't do anything out of the ordinary except to add a null check upon calling ProgressDialog's dismiss:

在我BackgroundLoginTask中,除了在 callProgressDialog的解除时添加空检查之外,我没有做任何不寻常的事情:

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

This is to handle the case where the background task finishes while the Activityis not visible and, therefore, the progress dialog has already been dismissed by the onPause()method.

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

Next, in my parent Activityclass, I create global static handles to my AsyncTaskclass and my ProgressDialog(the AsyncTask, being nested, can access these variables):

接下来,在我的父Activity类中,我为我的AsyncTask类和我的ProgressDialogAsyncTask嵌套的 可以访问这些变量)创建全局静态句柄:

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

This serves two purposes: First, it allows my Activityto always access the AsyncTaskobject even from a new, post-rotated activity. Second, it allows my BackgroundLoginTaskto access and dismiss the ProgressDialogeven after a rotate.

这有两个目的:首先,它允许我Activity始终访问AsyncTask对象,即使是从一个新的、旋转后的活动。其次,它允许我在轮换后BackgroundLoginTask访问和关闭ProgressDialog即使。

Next, I add this to onPause(), causing the progress dialog to disappear when our Activityis leaving the foreground (preventing that ugly "force close" crash):

接下来,我将其添加到onPause(),当我们Activity离开前景时,导致进度对话框消失(防止丑陋的“强制关闭”崩溃):

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

Finally, I have the following in my onResume()method:

最后,我的方法中有以下内容onResume()

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

This allows the Dialogto reappear after the Activityis recreated.

这允许在Dialog重新Activity创建后重新出现。

Here is the entire 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();
            } 
        }
        }

    }
}

I am by no means a seasoned Android developer, so feel free to comment.

我绝不是经验丰富的 Android 开发人员,因此请随时发表评论。

回答by n224576

The trick is to show/dismiss the dialog within AsyncTask during onPreExecute/onPostExecute as usual, though in case of orientation-change create/show a new instance of the dialog in the activity and pass its reference to the task.

诀窍是像往常一样在 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;
        }       
    }
}