后台任务、进度对话框、方向改变 - 有没有 100% 有效的解决方案?

发布于 2024-09-25 05:54:43 字数 247 浏览 7 评论 0原文

我在后台线程中从互联网下载一些数据(我使用 AsyncTask)并在下载时显示进度对话框。方向发生变化,活动重新启动,然后我的 AsyncTask 完成 - 我想关闭进度对话框并启动一个新的活动。但是调用dismissDialog有时会抛出异常(可能是因为Activity被销毁并且新的Activity尚未启动)。

处理此类问题的最佳方法是什么(从后台线程更新 UI,即使用户改变方向也能正常工作)?谷歌有人提供了一些“官方解决方案”吗?

I download some data from internet in background thread (I use AsyncTask) and display a progress dialog while downloading. Orientation changes, Activity is restarted and then my AsyncTask is completed - I want to dismiss the progess dialog and start a new Activity. But calling dismissDialog sometimes throws an exception (probably because the Activity was destroyed and new Activity hasn't been started yet).

What is the best way to handle this kind of problem (updating UI from background thread that works even if user changes orientation)? Did someone from Google provide some "official solution"?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

陌路终见情 2024-10-02 05:54:44

Google 是否有人提供了一些“官方解决方案”?

是的。

该解决方案更多的是一个应用程序架构提案,而不仅仅是一些代码。

他们提出了3 种设计模式,允许应用程序与服务器同步工作,而不管应用程序状态如何(即使用户完成了应用程序,用户更改屏幕,应用程序被终止,后台数据操作可能被中断的所有其他可能的状态,这涵盖了它)

youtube.com/watch?v=xHXn3Kg2IQE" rel="noreferrer">Android REST 客户端应用程序,由 Virgil Doobjanschi 在 Google I/O 2010 期间发表演讲。它时长 1 小时,但非常值得一看。

它的基础是将网络操作抽象为独立于任何Activity工作的Service > 在应用程序中。如果您正在使用数据库,则使用 ContentResolverCursor 将为您提供开箱即用的观察者模式,即使用获取的远程数据更新本地数据库后,无需任何附加逻辑即可方便地更新 UI。任何其他操作后代码都将通过传递给 Service 的回调来运行(我为此使用 ResultReceiver 子类)。

无论如何,我的解释实际上很模糊,你一定要看看演讲。

Did someone from Google provide some "official solution"?

Yes.

The solution is more of an application architecture proposal rather that just some code.

They proposed 3 design patterns that allows an application to work in-sync with a server, regardless of the application state (it will work even if the user finishes the app, the user changes screen, the app gets terminated, every other possible state where a background data operation could be interrumpted, this covers it)

The proposal is explained in the Android REST client applications speech during Google I/O 2010 by Virgil Dobjanschi. It is 1 hour long, but it is extremely worth watching.

The basis of it is abstracting network operations to a Service that works independently to any Activity in the application. If you're working with databases, the use of ContentResolver and Cursor would give you an out-of-the-box Observer pattern that is convenient to update UI without any aditional logic, once you updated your local database with the fetched remote data. Any other after-operation code would be run via a callback passed to the Service (I use a ResultReceiver subclass for this).

Anyway, my explanation is actually pretty vague, you should definititely watch the speech.

我也只是我 2024-10-02 05:54:44

虽然 Mark(CommonsWare)的答案确实适用于方向更改,但如果直接销毁 Activity(例如打电话的情况),则会失败。

您可以通过使用 Application 对象引用 ASyncTask 来处理方向更改和罕见的被破坏的 Activity 事件。

对问题和解决方案有很好的解释 此处

完全归功于 Ryan 解决了这个问题。

While Mark's (CommonsWare) answer does indeed work for orientation changes, it fails if the Activity is destroyed directly (like in the case of a phone call).

You can handle the orientation changes AND the rare destroyed Activity events by using an Application object to reference your ASyncTask.

There's an excellent explanation of the problem and the solution here:

Credit goes completely to Ryan for figuring this one out.

無心 2024-10-02 05:54:44

4年后,Google仅在Activity onCreate中调用setRetainInstance(true)解决了这个问题。它将在设备轮换期间保留您的活动实例。我还有一个针对旧版 Android 的简单解决方案。

After 4 years Google solved the problem just calling setRetainInstance(true) in Activity onCreate. It will preserve your activity instance during device rotation. I have also a simple solution for older Android.

韵柒 2024-10-02 05:54:44

您应该使用活动处理程序调用所有活动操作。因此,如果您在某个线程中,您应该创建一个 Runnable 并使用 Activitie 的 Handler 进行发布。否则你的应用程序有时会崩溃并出现致命异常。

you should call all activity actions using activity handler. So if you are in some thread you should create a Runnable and posted using Activitie's Handler. Otherwise your app will crash sometimes with fatal exception.

温暖的光 2024-10-02 05:54:44

这是我的解决方案: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

基本上步骤是:

  1. 如果任务仍然存在,我使用 onSaveInstanceState 来保存任务
    加工。
  2. onCreate 中,如果任务已保存,我会得到该任务。
  3. onPause 中,如果显示 ProgressDialog,我会丢弃它。
  4. onResume 中,如果任务仍在进行,我会显示 ProgressDialog
    加工。

This is my solution: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

Basically the steps are:

  1. I use onSaveInstanceState to save the task if it is still
    processing.
  2. In onCreate I get the task if it was saved.
  3. In onPause I discard the ProgressDialog if it is shown.
  4. In onResume I show the ProgressDialog if the task is still
    processing.
蛮可爱 2024-10-02 05:54:43

步骤#1:使您的AsyncTask成为static嵌套类,或完全独立的类,而不是内部(非静态嵌套)类。

步骤#2:让 AsyncTask 通过数据成员保留 Activity,并通过构造函数和 setter 进行设置。

步骤#3:创建 AsyncTask 时,将当前的 Activity 提供给构造函数。

步骤 #4:在 onRetainNonConfigurationInstance() 中,将 AsyncTask 从原始的、即将离开的 Activity 中分离出来后返回。

步骤 #5:在 onCreate() 中,如果 getLastNonConfigurationInstance() 不是 null,则将其转换为 AsyncTask类并调用您的 setter 将您的新活动与任务关联起来。

步骤#6:不要从doInBackground()引用活动数据成员。

如果你遵循上面的食谱,一切都会成功。 onProgressUpdate()onPostExecute()onRetainNonConfigurationInstance() 开始和后续 onCreate()< 结束之间暂停/代码>。

这是一个演示该技术的示例项目

另一种方法是放弃 AsyncTask 并将工作转移到 IntentService 中。如果要完成的工作可能很长并且无论用户在活动方面做什么(例如,下载大文件)都应该继续,那么这特别有用。您可以使用有序广播 Intent 来让 Activity 响应正在完成的工作(如果它仍在前台)或引发 Notification 让用户知道如果工作已经完成。 这是一篇博客文章,其中包含更多相关内容图案。

Step #1: Make your AsyncTask a static nested class, or an entirely separate class, just not an inner (non-static nested) class.

Step #2: Have the AsyncTask hold onto the Activity via a data member, set via the constructor and a setter.

Step #3: When creating the AsyncTask, supply the current Activity to the constructor.

Step #4: In onRetainNonConfigurationInstance(), return the AsyncTask, after detaching it from the original, now-going-away activity.

Step #5: In onCreate(), if getLastNonConfigurationInstance() is not null, cast it to your AsyncTask class and call your setter to associate your new activity with the task.

Step #6: Do not refer to the activity data member from doInBackground().

If you follow the above recipe, it will all work. onProgressUpdate() and onPostExecute() are suspended between the start of onRetainNonConfigurationInstance() and the end of the subsequent onCreate().

Here is a sample project demonstrating the technique.

Another approach is to ditch the AsyncTask and move your work into an IntentService. This is particularly useful if the work to be done may be long and should go on regardless of what the user does in terms of activities (e.g., downloading a large file). You can use an ordered broadcast Intent to either have the activity respond to the work being done (if it is still in the foreground) or raise a Notification to let the user know if the work has been done. Here is a blog post with more on this pattern.

深海蓝天 2024-10-02 05:54:43

接受的答案非常有帮助,但它没有进度对话框。

幸运的是,读者,我创建了一个 非常全面且有效的带有进度对话框的 AsyncTask 示例

  1. 旋转有效,对话得以保留。
  2. 您可以通过按后退按钮取消任务和对话框(如果您想要此行为)。
  3. 它使用片段。
  4. 当设备旋转时,活动下方片段的布局会正确更改。

The accepted answer was very helpful, but it doesn't have a progress dialog.

Fortunately for you, reader, I have created an extremely comprehensive and working example of an AsyncTask with a progress dialog!

  1. Rotation works, and the dialog survives.
  2. You can cancel the task and dialog by pressing the back button (if you want this behaviour).
  3. It uses fragments.
  4. The layout of the fragment underneath the activity changes properly when the device rotates.
永不分离 2024-10-02 05:54:43

我花了一周的时间寻找解决这个困境的方法,而无需编辑清单文件。此解决方案的假设是:

  1. 您始终需要使用进度对话框
  2. 一次仅执行一项任务
  3. 您需要在旋转手机时保留该任务,并且进度对话框自动关闭。

实现

您需要将本文底部的两个文件复制到您的工作区中。只需确保:

  1. 所有 Activity 都应扩展 BaseActivity

  2. onCreate() 中,应在初始化 ASyncTask 需要访问的任何成员后调用 super.onCreate() 。另外,重写getContentViewId()以提供表单布局id。

  3. 重写onCreateDialog() 像往常一样来创建由 Activity 管理的对话框。

  4. 请参阅下面的代码以获取示例静态内部类来创建 AsyncTasks。您可以将结果存储在 mResult 中以便稍后访问。


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

最后,启动您的新任务:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

就是这样!我希望这个强大的解决方案能够对某人有所帮助。

BaseActivity.java(自己组织导入)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}

I've toiled for a week to find a solution to this dilemma without resorting to editing the manifest file. The assumptions for this solution are:

  1. You always need to use a progress dialog
  2. Only one task is performed at a time
  3. You need the task to persist when the phone is rotated and the progress dialog to be automatically dismisses.

Implementation

You will need to copy the two files found at the bottom of this post into your workspace. Just make sure that:

  1. All your Activitys should extend BaseActivity

  2. In onCreate(), super.onCreate() should be called after you initialize any members that need to be accessed by your ASyncTasks. Also, override getContentViewId() to provide the form layout id.

  3. Override onCreateDialog() like usual to create dialogs managed by the activity.

  4. See code below for a sample static inner class to make your AsyncTasks. You can store your result in mResult to access later.


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

And finally, to launch your new task:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

That's it! I hope this robust solution will help someone.

BaseActivity.java (organize imports yourself)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文