86

我正在开发智能手机/平板电脑应用程序,仅使用一个 APK,并根据屏幕大小按需加载资源,最佳设计选择似乎是通过 ACL 使用片段。

该应用程序一直运行良好,直到现在仅基于活动。这是我如何在活动中处理 AsyncTasks 和 ProgressDialogs 的模拟类,以使它们即使在屏幕旋转或在通信中发生配置更改时也能正常工作。

我不会更改清单以避免重新创建活动,我不想这样做的原因有很多,但主要是因为官方文档说它不推荐并且我到目前为止没有它,所以请不要推荐路线。

public class Login extends Activity {

    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.login);
        //SETUP UI OBJECTS
        restoreAsyncTask();
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (pd != null) pd.dismiss();
        if (asyncLoginThread != null) return (asyncLoginThread);
        return super.onRetainNonConfigurationInstance();
    }

    private void restoreAsyncTask();() {
        pd = new ProgressDialog(Login.this);
        if (getLastNonConfigurationInstance() != null) {
            asyncLoginThread = (AsyncTask<String, Void, Boolean>) getLastNonConfigurationInstance();
            if (asyncLoginThread != null) {
                if (!(asyncLoginThread.getStatus()
                        .equals(AsyncTask.Status.FINISHED))) {
                    showProgressDialog();
                }
            }
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
        @Override
        protected Boolean doInBackground(String... args) {
            try {
                //Connect to WS, recieve a JSON/XML Response
                //Place it somewhere I can use it.
            } catch (Exception e) {
                return true;
            }
            return true;
        }

        protected void onPostExecute(Boolean result) {
            if (result) {
                pd.dismiss();
                //Handle the response. Either deny entry or launch new Login Succesful Activity
            }
        }
    }
}

这段代码运行良好,我有大约 10.000 个用户没有抱怨,所以将这个逻辑复制到新的基于片段的设计中似乎是合乎逻辑的,但是,当然,它不起作用。

这是登录片段:

public class LoginFragment extends Fragment {

    FragmentActivity parentActivity;
    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    public interface OnLoginSuccessfulListener {
        public void onLoginSuccessful(GlobalContainer globalContainer);
    }

    public void onSaveInstanceState(Bundle outState){
        super.onSaveInstanceState(outState);
        //Save some stuff for the UI State
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setRetainInstance(true);
        //If I setRetainInstance(true), savedInstanceState is always null. Besides that, when loading UI State, a NPE is thrown when looking for UI Objects.
        parentActivity = getActivity();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            loginSuccessfulListener = (OnLoginSuccessfulListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnLoginSuccessfulListener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        RelativeLayout loginLayout = (RelativeLayout) inflater.inflate(R.layout.login, container, false);
        return loginLayout;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //SETUP UI OBJECTS
        if(savedInstanceState != null){
            //Reload UI state. Im doing this properly, keeping the content of the UI objects, not the object it self to avoid memory leaks.
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
            @Override
            protected Boolean doInBackground(String... args) {
                try {
                    //Connect to WS, recieve a JSON/XML Response
                    //Place it somewhere I can use it.
                } catch (Exception e) {
                    return true;
                }
                return true;
            }

            protected void onPostExecute(Boolean result) {
                if (result) {
                    pd.dismiss();
                    //Handle the response. Either deny entry or launch new Login Succesful Activity
                }
            }
        }
    }
}

我不能使用onRetainNonConfigurationInstance(),因为它必须从 Activity 而不是从 Fragment 调用,与getLastNonConfigurationInstance(). 我在这里读过一些类似的问题,但没有答案。

我知道可能需要一些工作才能将这些东西正确组织成碎片,也就是说,我想保持相同的基本设计逻辑。

在配置更改期间保留 AsyncTask 的正确方法是什么,如果它仍在运行,则显示一个 progressDialog,考虑到 AsyncTask 是 Fragment 的内部类,并且调用 AsyncTask.execute 的是 Fragment 本身()?

4

12 回答 12

75

片段实际上可以使这变得容易得多。只需使用方法Fragment.setRetainInstance(boolean)让您的片段实例在配置更改期间保留。请注意,这是文档中推荐的Activity.onRetainnonConfigurationInstance()替代品。

如果由于某种原因您真的不想使用保留的片段,您可以采取其他方法。请注意,每个片段都有一个由Fragment.getId()返回的唯一标识符。您还可以通过Fragment.getActivity().isChangingConfigurations()了解是否正在拆除片段以进行配置更改。因此,当您决定停止 AsyncTask(最有可能在 onStop() 或 onDestroy() 中)时,您可以检查配置是否正在更改,如果是这样,请将其粘贴在片段标识符下的静态 SparseArray 中,然后在您的 onCreate() 或 onStart() 中查看稀疏数组中是否有可用的 AsyncTask。

于 2011-12-18T06:57:39.253 回答
66

我想你会喜欢下面详细介绍的我非常全面和有效的示例。

  1. 旋转有效,对话继续存在。
  2. 您可以通过按后退按钮取消任务和对话框(如果您想要此行为)。
  3. 它使用片段。
  4. 当设备旋转时,活动下方的片段布局会正确更改。
  5. 有完整的源代码下载和预编译的 APK,因此您可以查看行为是否符合您的要求。

编辑

按照 Brad Larson 的要求,我已经复制了下面的大部分链接解决方案。此外,自从我发布它以来,我一直被指出AsyncTaskLoader。我不确定它是否完全适用于相同的问题,但无论如何你都应该检查一下。

AsyncTask与进度对话框和设备旋转一起使用。

一个有效的解决方案!

我终于得到了一切工作。我的代码具有以下功能:

  1. A Fragment,其布局随方向变化。
  2. 您可以AsyncTask在其中做一些工作。
  3. ADialogFragment在进度条中显示任务的进度(不仅仅是不确定的微调器)。
  4. 旋转不会中断任务或关闭对话框。
  5. 后退按钮关闭对话框并取消任务(尽管您可以相当容易地更改此行为)。

我认为在其他任何地方都找不到工作能力的组合。

基本思路如下。有一个MainActivity包含单个片段的类 - MainFragment. MainFragment对于水平和垂直方向有不同的布局,并且setRetainInstance()是假的,以便布局可以改变。这意味着当设备方向改变时,两者都MainActivityMainFragment完全销毁并重新创建。

另外,我们有MyTask(扩展自AsyncTask)来完成所有工作。我们不能存储它,MainFragment因为它会被销毁,而且谷歌已经弃用了类似setRetainNonInstanceConfiguration(). 无论如何,这并不总是可用的,充其量只是一个丑陋的黑客。相反,我们将存储MyTask在另一个片段中,DialogFragment称为TaskFragment. 片段设置setRetainInstance()为 true,因此当设备旋转时,该片段不会被破坏,MyTask而是被保留。

最后,我们需要告诉TaskFragment谁在它完成时通知谁,我们setTargetFragment(<the MainFragment>)在创建它时使用它。当设备旋转并MainFragment销毁并创建新实例时,我们使用FragmentManager来查找对话框(基于其标签)并执行setTargetFragment(<the new MainFragment>). 差不多就是这样。

我还需要做两件事:首先在关闭对话框时取消任务,然后将关闭消息设置为 null,否则当设备旋转时对话框会奇怪地关闭。

编码

我不会列出布局,它们非常明显,您可以在下面的项目下载中找到它们。

主要活动

这很简单。我在此活动中添加了一个回调,以便它知道任务何时完成,但您可能不需要它。主要是我只是想展示片段活动回调机制,因为它非常简洁,你可能以前没有见过。

public class MainActivity extends Activity implements MainFragment.Callbacks
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public void onTaskFinished()
    {
        // Hooray. A toast to our success.
        Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
        // NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't*
        // the duration in milliseconds. ANDROID Y U NO ENUM? 
    }
}

主要片段

它很长,但值得!

public class MainFragment extends Fragment implements OnClickListener
{
    // This code up to onDetach() is all to get easy callbacks to the Activity. 
    private Callbacks mCallbacks = sDummyCallbacks;

    public interface Callbacks
    {
        public void onTaskFinished();
    }
    private static Callbacks sDummyCallbacks = new Callbacks()
    {
        public void onTaskFinished() { }
    };

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        if (!(activity instanceof Callbacks))
        {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }
        mCallbacks = (Callbacks) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        mCallbacks = sDummyCallbacks;
    }

    // Save a reference to the fragment manager. This is initialised in onCreate().
    private FragmentManager mFM;

    // Code to identify the fragment that is calling onActivityResult(). We don't really need
    // this since we only have one fragment to deal with.
    static final int TASK_FRAGMENT = 0;

    // Tag so we can find the task fragment again, in another instance of this fragment after rotation.
    static final String TASK_FRAGMENT_TAG = "task";

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

        // At this point the fragment may have been recreated due to a rotation,
        // and there may be a TaskFragment lying around. So see if we can find it.
        mFM = getFragmentManager();
        // Check to see if we have retained the worker fragment.
        TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);

        if (taskFragment != null)
        {
            // Update the target fragment so it goes to this fragment instead of the old one.
            // This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
            // keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
            // use weak references. To be sure you aren't leaking, you may wish to make your own
            // setTargetFragment() which does.
            taskFragment.setTargetFragment(this, TASK_FRAGMENT);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);

        // Callback for the "start task" button. I originally used the XML onClick()
        // but it goes to the Activity instead.
        view.findViewById(R.id.taskButton).setOnClickListener(this);
    }

    @Override
    public void onClick(View v)
    {
        // We only have one click listener so we know it is the "Start Task" button.

        // We will create a new TaskFragment.
        TaskFragment taskFragment = new TaskFragment();
        // And create a task for it to monitor. In this implementation the taskFragment
        // executes the task, but you could change it so that it is started here.
        taskFragment.setTask(new MyTask());
        // And tell it to call onActivityResult() on this fragment.
        taskFragment.setTargetFragment(this, TASK_FRAGMENT);

        // Show the fragment.
        // I'm not sure which of the following two lines is best to use but this one works well.
        taskFragment.show(mFM, TASK_FRAGMENT_TAG);
//      mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
        {
            // Inform the activity. 
            mCallbacks.onTaskFinished();
        }
    }

任务片段

    // This and the other inner class can be in separate files if you like.
    // There's no reason they need to be inner classes other than keeping everything together.
    public static class TaskFragment extends DialogFragment
    {
        // The task we are running.
        MyTask mTask;
        ProgressBar mProgressBar;

        public void setTask(MyTask task)
        {
            mTask = task;

            // Tell the AsyncTask to call updateProgress() and taskFinished() on this fragment.
            mTask.setFragment(this);
        }

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

            // Retain this instance so it isn't destroyed when MainActivity and
            // MainFragment change configuration.
            setRetainInstance(true);

            // Start the task! You could move this outside this activity if you want.
            if (mTask != null)
                mTask.execute();
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState)
        {
            View view = inflater.inflate(R.layout.fragment_task, container);
            mProgressBar = (ProgressBar)view.findViewById(R.id.progressBar);

            getDialog().setTitle("Progress Dialog");

            // If you're doing a long task, you probably don't want people to cancel
            // it just by tapping the screen!
            getDialog().setCanceledOnTouchOutside(false);

            return view;
        }

        // This is to work around what is apparently a bug. If you don't have it
        // here the dialog will be dismissed on rotation, so tell it not to dismiss.
        @Override
        public void onDestroyView()
        {
            if (getDialog() != null && getRetainInstance())
                getDialog().setDismissMessage(null);
            super.onDestroyView();
        }

        // Also when we are dismissed we need to cancel the task.
        @Override
        public void onDismiss(DialogInterface dialog)
        {
            super.onDismiss(dialog);
            // If true, the thread is interrupted immediately, which may do bad things.
            // If false, it guarantees a result is never returned (onPostExecute() isn't called)
            // but you have to repeatedly call isCancelled() in your doInBackground()
            // function to check if it should exit. For some tasks that might not be feasible.
            if (mTask != null) {
                mTask.cancel(false);
            }

            // You don't really need this if you don't want.
            if (getTargetFragment() != null)
                getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
        }

        @Override
        public void onResume()
        {
            super.onResume();
            // This is a little hacky, but we will see if the task has finished while we weren't
            // in this activity, and then we can dismiss ourselves.
            if (mTask == null)
                dismiss();
        }

        // This is called by the AsyncTask.
        public void updateProgress(int percent)
        {
            mProgressBar.setProgress(percent);
        }

        // This is also called by the AsyncTask.
        public void taskFinished()
        {
            // Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
            // after the user has switched to another app.
            if (isResumed())
                dismiss();

            // If we aren't resumed, setting the task to null will allow us to dimiss ourselves in
            // onResume().
            mTask = null;

            // Tell the fragment that we are done.
            if (getTargetFragment() != null)
                getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_OK, null);
        }
    }

我的任务

    // This is a fairly standard AsyncTask that does some dummy work.
    public static class MyTask extends AsyncTask<Void, Void, Void>
    {
        TaskFragment mFragment;
        int mProgress = 0;

        void setFragment(TaskFragment fragment)
        {
            mFragment = fragment;
        }

        @Override
        protected Void doInBackground(Void... params)
        {
            // Do some longish task. This should be a task that we don't really
            // care about continuing
            // if the user exits the app.
            // Examples of these things:
            // * Logging in to an app.
            // * Downloading something for the user to view.
            // * Calculating something for the user to view.
            // Examples of where you should probably use a service instead:
            // * Downloading files for the user to save (like the browser does).
            // * Sending messages to people.
            // * Uploading data to a server.
            for (int i = 0; i < 10; i++)
            {
                // Check if this has been cancelled, e.g. when the dialog is dismissed.
                if (isCancelled())
                    return null;

                SystemClock.sleep(500);
                mProgress = i * 10;
                publishProgress();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Void... unused)
        {
            if (mFragment == null)
                return;
            mFragment.updateProgress(mProgress);
        }

        @Override
        protected void onPostExecute(Void unused)
        {
            if (mFragment == null)
                return;
            mFragment.taskFinished();
        }
    }
}

下载示例项目

这是源代码APK。抱歉,ADT 坚持要添加支持库才让我做项目。我相信你可以删除它。

于 2012-09-06T15:39:55.670 回答
16

我最近发布了一篇文章,描述了如何使用 reserved 处理配置更改Fragment。它很好地解决了保持AsyncTask跨旋转变化的问题。

TL;DR 是使用 host your AsyncTaskinside a Fragment,调用setRetainInstance(true)Fragment并通过 reserved 将AsyncTask进度/结果报告给它Activity(或者它的目标Fragment,如果你选择使用@Timmmm 描述的方法)Fragment

于 2013-04-30T16:46:35.993 回答
13

我的第一个建议是避免内部 AsyncTasks,您可以阅读我提出的有关此问题的问题和答案:Android:AsyncTask 建议:私有类还是公共类?

在那之后,我开始使用非内在...现在我看到了很多好处。

第二个是,在Application类中保留您正在运行的 AsyncTask 的引用 - http://developer.android.com/reference/android/app/Application.html

每次启动 AsyncTask 时,将其设置在应用程序上,并在完成时将其设置为 null。

当片段/活动启动时,您可以检查是否有任何 AsyncTask 正在运行(通过检查它是否在应用程序上为空),然后将内部引用设置为您想要的任何内容(活动、片段等,以便您可以进行回调)。

这将解决您的问题:如果您在任何确定的时间只有 1 个 AsyncTask 运行,您可以添加一个简单的参考:

AsyncTask<?,?,?> asyncTask = null;

否则,在应用程序中有一个带有对它们的引用的 HashMap。

进度对话框可以遵循完全相同的原则。

于 2011-12-15T14:41:51.917 回答
4

为此,我想出了一种使用 AsyncTaskLoaders 的方法。它非常易于使用,并且需要更少的 IMO 开销。

基本上你会像这样创建一个 AsyncTaskLoader:

public class MyAsyncTaskLoader extends AsyncTaskLoader {
    Result mResult;
    public HttpAsyncTaskLoader(Context context) {
        super(context);
    }

    protected void onStartLoading() {
        super.onStartLoading();
        if (mResult != null) {
            deliverResult(mResult);
        }
        if (takeContentChanged() ||  mResult == null) {
            forceLoad();
        }
    }

    @Override
    public Result loadInBackground() {
        SystemClock.sleep(500);
        mResult = new Result();
        return mResult;
    }
}

然后在单击按钮时使用上述 AsyncTaskLoader 的活动中:

public class MyActivityWithBackgroundWork extends FragmentActivity implements LoaderManager.LoaderCallbacks<Result> {

    private String username,password;       
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mylayout);
        //this is only used to reconnect to the loader if it already started
        //before the orientation changed
        Loader loader = getSupportLoaderManager().getLoader(0);
        if (loader != null) {
            getSupportLoaderManager().initLoader(0, null, this);
        }
    }

    public void doBackgroundWorkOnClick(View button) {
        //might want to disable the button while you are doing work
        //to prevent user from pressing it again.

        //Call resetLoader because calling initLoader will return
        //the previous result if there was one and we may want to do new work
        //each time
        getSupportLoaderManager().resetLoader(0, null, this);
    }   


    @Override
    public Loader<Result> onCreateLoader(int i, Bundle bundle) {
        //might want to start a progress bar
        return new MyAsyncTaskLoader(this);
    }


    @Override
    public void onLoadFinished(Loader<LoginResponse> loginLoader,
                               LoginResponse loginResponse)
    {
        //handle result
    }

    @Override
    public void onLoaderReset(Loader<LoginResponse> responseAndJsonHolderLoader)
    {
        //remove references to previous loader resources

    }
}

这似乎可以很好地处理方向更改,并且您的后台任务将在旋转期间继续。

需要注意的几点:

  1. 如果在 onCreate 中您重新连接到 asynctaskloader,您将在 onLoadFinished() 中被调用并返回之前的结果(即使您已经被告知请求已完成)。这实际上在大多数情况下都是好的行为,但有时可能很难处理。虽然我想有很多方法可以处理这个问题,但我在 onLoadFinished 中调用了 loader.abandon()。然后我添加了检查 onCreate 以仅在尚未放弃时才重新附加到加载器。如果您再次需要结果数据,您将不想这样做。在大多数情况下,您需要数据。

我有更多关于在此处使用它进行 http 调用的详细信息

于 2013-11-03T08:15:17.007 回答
3

我创建了一个非常小的开源后台任务库,它在很大程度上基于 Marshmallow AsyncTask,但具有额外的功能,例如:

  1. 跨配置更改自动保留任务;
  2. UI 回调(监听器);
  3. 设备旋转时不会重新启动或取消任务(就像装载机那样);

该库在内部使用Fragment没有任何用户界面的 a,该界面在配置更改时保留 ( setRetainInstance(true))。

你可以在 GitHub 上找到它:https ://github.com/NeoTech-Software/Android-Retainable-Tasks

最基本的示例(版本 0.2.0):

这个例子完全保留了任务,使用了非常有限的代码。

任务:

private class ExampleTask extends Task<Integer, String> {

    public ExampleTask(String tag){
        super(tag);
    }

    protected String doInBackground() {
        for(int i = 0; i < 100; i++) {
            if(isCancelled()){
                break;
            }
            SystemClock.sleep(50);
            publishProgress(i);
        }
        return "Result";
    }
}

活动:

public class Main extends TaskActivityCompat implements Task.Callback {

    @Override
    public void onClick(View view){
        ExampleTask task = new ExampleTask("activity-unique-tag");
        getTaskManager().execute(task, this);
    }

    @Override
    public Task.Callback onPreAttach(Task<?, ?> task) {
        //Restore the user-interface based on the tasks state
        return this; //This Activity implements Task.Callback
    }

    @Override
    public void onPreExecute(Task<?, ?> task) {
        //Task started
    }

    @Override
    public void onPostExecute(Task<?, ?> task) {
        //Task finished
        Toast.makeText(this, "Task finished", Toast.LENGTH_SHORT).show();
    }
}
于 2016-03-05T01:08:40.960 回答
1

我的方法是使用委托设计模式,一般来说,我们可以在 AysncTask.doInBackground() 方法中将实际业务逻辑(从 Internet 或数据库读取数据或其他)从 AsyncTask(委托者)隔离到 BusinessDAO(委托) ,将实际任务委托给BusinessDAO,然后在BusinessDAO中实现单例流程机制,这样多次调用BusinessDAO.doSomething()每次只会触发一个实际任务运行并等待任务结果。这个想法是在配置更改期间保留委托(即 BusinessDAO),而不是委托人(即 AsyncTask)。

  1. 创建/实现我们自己的Application,目的是在这里创建/初始化BusinessDAO,使我们的BusinessDAO的生命周期是application scoped,而不是activity scoped,注意你需要修改AndroidManifest.xml来使用MyApplication:

    public class MyApplication extends android.app.Application {
      private BusinessDAO businessDAO;
    
      @Override
      public void onCreate() {
        super.onCreate();
        businessDAO = new BusinessDAO();
      }
    
      pubilc BusinessDAO getBusinessDAO() {
        return businessDAO;
      }
    
    }
    
  2. 我们现有的 Activity/Fragement 大部分没有变化,仍然将 AsyncTask 实现为内部类,并涉及 Activity/Fragement 中的 AsyncTask.execute(),现在不同的是 AsyncTask 会将实际任务委托给 BusinessDAO,因此在配置更改期间,第二个 AsyncTask将被初始化并执行,并第二次调用 BusinessDAO.doSomething(),但是,第二次调用 BusinessDAO.doSomething() 不会触发新的正在运行的任务,而是等待当前正在运行的任务完成:

    public class LoginFragment extends Fragment {
      ... ...
    
      public class LoginAsyncTask extends AsyncTask<String, Void, Boolean> {
        // get a reference of BusinessDAO from application scope.
        BusinessDAO businessDAO = ((MyApplication) getApplication()).getBusinessDAO();
    
        @Override
        protected Boolean doInBackground(String... args) {
            businessDAO.doSomething();
            return true;
        }
    
        protected void onPostExecute(Boolean result) {
          //Handle task result and update UI stuff.
        }
      }
    
      ... ...
    }
    
  3. 在 BusinessDAO 内部,实现单例进程机制,例如:

    public class BusinessDAO {
      ExecutorCompletionService<MyTask> completionExecutor = new ExecutorCompletionService<MyTask(Executors.newFixedThreadPool(1));
      Future<MyTask> myFutureTask = null;
    
      public void doSomething() {
        if (myFutureTask == null) {
          // nothing running at the moment, submit a new callable task to run.
          MyTask myTask = new MyTask();
          myFutureTask = completionExecutor.submit(myTask);
        }
        // Task already submitted and running, waiting for the running task to finish.
        myFutureTask.get();
      }
    
      // If you've never used this before, Callable is similar with Runnable, with ability to return result and throw exception.
      private class MyTask extends Callable<MyTask> {
        public MyAsyncTask call() {
          // do your job here.
          return this;
        }
      }
    
    }
    

我不是 100% 确定这是否可行,此外,示例代码片段应被视为伪代码。我只是想从设计层面给你一些线索。欢迎和赞赏任何反馈或建议。

于 2011-12-16T22:16:45.983 回答
1

您可以将 AsyncTask 设为静态字段。如果你需要一个上下文,你应该发布你的应用程序上下文。这将避免内存泄漏,否则您将保留对整个活动的引用。

于 2011-12-17T18:41:21.103 回答
1

如果有人找到通往该线程的路,那么我发现一种干净的方法是从app.Service(以 START_STICKY 开始)运行异步任务,然后重新创建迭代正在运行的服务以找出服务(以及异步任务)是否仍然跑步;

    public boolean isServiceRunning(String serviceClassName) {
    final ActivityManager activityManager = (ActivityManager) Application.getContext().getSystemService(Context.ACTIVITY_SERVICE);
    final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);

    for (RunningServiceInfo runningServiceInfo : services) {
        if (runningServiceInfo.service.getClassName().equals(serviceClassName)){
            return true;
        }
    }
    return false;
 }

如果是,请重新添加DialogFragment(或其他),如果不是,请确保对话框已被关闭。

如果您正在使用这些v4.support.*库,这一点尤其相关,因为(在撰写本文时)他们知道setRetainInstance方法和视图分页存在问题。此外,通过不保留实例,您可以使用一组不同的资源重新创建您的活动(即新方向的不同视图布局)

于 2012-07-24T10:16:09.367 回答
0

我写了samepl代码来解决这个问题

第一步是制作应用程序类:

public class TheApp extends Application {

private static TheApp sTheApp;
private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>();

@Override
public void onCreate() {
    super.onCreate();
    sTheApp = this;
}

public static TheApp get() {
    return sTheApp;
}

public void registerTask(String tag, AsyncTask<?,?,?> task) {
    tasks.put(tag, task);
}

public void unregisterTask(String tag) {
    tasks.remove(tag);
}

public AsyncTask<?,?,?> getTask(String tag) {
    return tasks.get(tag);
}
}

在 AndroidManifest.xml 中

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:name="com.example.tasktest.TheApp">

活动代码:

public class MainActivity extends Activity {

private Task1 mTask1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mTask1 = (Task1)TheApp.get().getTask("task1");

}

/*
 * start task is not running jet
 */
public void handletask1(View v) {
    if (mTask1 == null) {
        mTask1 = new Task1();
        TheApp.get().registerTask("task1", mTask1);
        mTask1.execute();
    } else
        Toast.makeText(this, "Task is running...", Toast.LENGTH_SHORT).show();

}

/*
 * cancel task if is not finished
 */
public void handelCancel(View v) {
    if (mTask1 != null)
        mTask1.cancel(false);
}

public class Task1 extends AsyncTask<Void, Void, Void>{

    @Override
    protected Void doInBackground(Void... params) {
        try {
            for(int i=0; i<120; i++) {
                Thread.sleep(1000);
                Log.i("tests", "loop=" + i);
                if (this.isCancelled()) {
                    Log.e("tests", "tssk cancelled");
                    break;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onCancelled(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }

    @Override
    protected void onPostExecute(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }
}

}

当活动方向改变时,变量 mTask 是从应用程序上下文中初始化的。当任务完成时,变量设置为 null 并从内存中删除。

对我来说已经足够了。

于 2013-12-03T20:41:26.037 回答
0

看看下面的例子,如何使用保留的片段来保留后台任务:

public class NetworkRequestFragment extends Fragment {

    // Declare some sort of interface that your AsyncTask will use to communicate with the Activity
    public interface NetworkRequestListener {
        void onRequestStarted();
        void onRequestProgressUpdate(int progress);
        void onRequestFinished(SomeObject result);
    }

    private NetworkTask mTask;
    private NetworkRequestListener mListener;

    private SomeObject mResult;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Try to use the Activity as a listener
        if (activity instanceof NetworkRequestListener) {
            mListener = (NetworkRequestListener) activity;
        } else {
            // You can decide if you want to mandate that the Activity implements your callback interface
            // in which case you should throw an exception if it doesn't:
            throw new IllegalStateException("Parent activity must implement NetworkRequestListener");
            // or you could just swallow it and allow a state where nobody is listening
        }
    }

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

        // Retain this Fragment so that it will not be destroyed when an orientation
        // change happens and we can keep our AsyncTask running
        setRetainInstance(true);
    }

    /**
     * The Activity can call this when it wants to start the task
     */
    public void startTask(String url) {
        mTask = new NetworkTask(url);
        mTask.execute();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // If the AsyncTask finished when we didn't have a listener we can
        // deliver the result here
        if ((mResult != null) && (mListener != null)) {
            mListener.onRequestFinished(mResult);
            mResult = null;
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        // We still have to cancel the task in onDestroy because if the user exits the app or
        // finishes the Activity, we don't want the task to keep running
        // Since we are retaining the Fragment, onDestroy won't be called for an orientation change
        // so this won't affect our ability to keep the task running when the user rotates the device
        if ((mTask != null) && (mTask.getStatus == AsyncTask.Status.RUNNING)) {
            mTask.cancel(true);
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();

        // This is VERY important to avoid a memory leak (because mListener is really a reference to an Activity)
        // When the orientation change occurs, onDetach will be called and since the Activity is being destroyed
        // we don't want to keep any references to it
        // When the Activity is being re-created, onAttach will be called and we will get our listener back
        mListener = null;
    }

    private class NetworkTask extends AsyncTask<String, Integer, SomeObject> {

        @Override
        protected void onPreExecute() {
            if (mListener != null) {
                mListener.onRequestStarted();
            }
        }

        @Override
        protected SomeObject doInBackground(String... urls) {
           // Make the network request
           ...
           // Whenever we want to update our progress:
           publishProgress(progress);
           ...
           return result;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            if (mListener != null) {
                mListener.onRequestProgressUpdate(progress[0]);
            }
        }

        @Override
        protected void onPostExecute(SomeObject result) {
            if (mListener != null) {
                mListener.onRequestFinished(result);
            } else {
                // If the task finishes while the orientation change is happening and while
                // the Fragment is not attached to an Activity, our mListener might be null
                // If you need to make sure that the result eventually gets to the Activity
                // you could save the result here, then in onActivityCreated you can pass it back
                // to the Activity
                mResult = result;
            }
        }

    }
}
于 2017-07-15T15:17:34.040 回答
-1

看看这里

有一个基于Timmmm 解决方案的解决方案。

但我改进了它:

  • 现在解决方案是可扩展的 - 您只需要扩展FragmentAbleToStartTask

  • 您可以同时继续运行多个任务。

    在我看来,它就像 startActivityForResult 和接收结果一样简单

  • 您还可以停止正在运行的任务并检查特定任务是否正在运行

对不起我的英语不好

于 2014-04-24T14:06:50.340 回答