Android碎片。 在屏幕旋转或configuration更改期间保留AsyncTask
我正在使用智能手机/平板电脑应用程序,只使用一个APK,根据屏幕大小需要加载资源,最好的deviseselect似乎是通过ACL使用碎片。
这个应用程序一直工作正常,直到现在只是基于活动。 这是我如何处理活动中的AsyncTasks和ProgressDialogs的一个模拟类,以便即使在屏幕旋转或通信中发生configuration更改时也能使其工作。
我不会改变清单,以避免娱乐活动,有很多原因,我不想这样做,但主要是因为官方文件说这不是推荐,我没有它pipe理,所以请不要build议路线。
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用户没有投诉,所以这似乎合乎逻辑,只是复制这个逻辑到新的基于片段的devise,但是,当然,它不工作。
这里是LoginFragment:
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()
。 我在这里读了一些类似的问题,没有回答。
我明白,这可能需要一些工作,以获得这些东西碎片正确组织,这就是说,我想保持相同的基本devise逻辑。
在configuration更改期间保留AsyncTask的正确方法是什么?如果仍在运行,则显示progressDialog,同时考虑到AsyncTask是Fragment的内部类,并且是调用AsyncTask.execute的Fragment本身()?
片段实际上可以使这更容易。 只需使用方法Fragment.setRetainInstance(boolean)来使您的片段实例在configuration更改中保留。 请注意,这是文档中Activity.onRetainnonConfigurationInstance()的推荐替代scheme。
如果由于某种原因,您确实不想使用保留的片段,那么您可以采取其他方法。 请注意,每个片段都有一个由Fragment.getId()返回的唯一标识符。 您还可以通过Fragment.getActivity()。isChangingConfigurations()来查看是否正在拆分片段以进行configuration更改。 所以,在你决定停止你的AsyncTask(在onStop()或onDestroy()很可能),你可以例如检查configuration是否正在改变,如果是这样的坚持它在一个静态SparseArray的片段的标识符下,然后在你的onCreate()或onStart()函数中查看是否有可用的稀疏数组中的AsyncTask。
我想你会喜欢我下面详细介绍的非常全面和实用的例子。
- 旋转工作,并对话生存。
- 您可以通过按下后退button来取消任务和对话框(如果需要这种行为)。
- 它使用碎片。
- 设备旋转时,活动下面的碎片布局会正确更改。
- 有一个完整的源代码下载和预编译APK,所以你可以看到,如果行为是你想要的。
编辑
按照Brad Larson的要求,我已经复制了下面大部分链接解决scheme。 另外,因为我发布它,我一直指向AsyncTaskLoader
。 我不确定它是否完全适用于同样的问题,但无论如何你都应该检查一下。
使用AsyncTask
进度对话框和设备旋转。
工作解决scheme!
我终于得到了一切工作。 我的代码有以下特点:
- 布局随着方向而变化的
Fragment
。 - 一个
AsyncTask
,你可以在其中做一些工作。 - 一个
DialogFragment
,它显示进度条中的任务进度(而不仅仅是一个不确定的微调)。 - 旋转在不中断任务或解除对话的情况下工作。
- 后退buttonclosures对话框并取消任务(尽pipe可以很容易地改变这种行为)。
我不认为工作的结合可以在其他地方find。
基本思路如下。 有一个MainActivity
类包含一个片段 – MainFragment
。 MainFragment
对于水平和垂直方向具有不同的布局,并且setRetainInstance()
为false,因此布局可以改变。 这意味着当设备方向改变时, MainActivity
和MainFragment
都被完全销毁并重新创build。
另外我们有MyTask
(从AsyncTask
扩展)完成所有的工作。 我们不能将它存储在MainFragment
因为这将被销毁,并且Google已经不赞成使用setRetainNonInstanceConfiguration()
类的东西。 这并不总是可用的,充其量是一个丑陋的黑客。 相反,我们将MyTask
存储在另一个片段中,一个名为TaskFragment
。 这个片段将 setRetainInstance()
设置为true,所以当设备旋转时,这个片段不会被销毁,并且MyTask
被保留。
最后,我们需要告诉TaskFragment
何时完成通知,我们在创build时使用setTargetFragment(<the MainFragment>)
来完成。 当设备被旋转并且MainFragment
被销毁并且一个新的实例被创build时,我们使用FragmentManager
来查找对话框(基于它的标签)并且执行setTargetFragment(<the new MainFragment>)
。 这是非常多的。
还有两件事我需要做:首先取消任务,当对话框被解散时,第二次将解除消息设置为空,否则在旋转设备时奇怪地解除对话。
代码
我不会列出布局,它们非常明显,您可以在下面的项目下载中find它们。
主要活动
这很简单。 我在这个活动中添加了一个callback,以便知道任务何时完成,但是您可能不需要。 主要我只是想显示片段活动callback机制,因为它很整洁,你可能以前没有看到它。
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 YU NO ENUM? } }
MainFragment
这很长,但值得!
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(); } }
TaskFragment
// 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); } }
MyTask
// 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, eg 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坚持要在添加支持库之前让我做一个项目。 我相信你可以删除它。
我最近发表了一篇文章,介绍如何使用保留的Fragment
来处理configuration更改。 它很好地解决了旋转更改中保留AsyncTask
的问题。
TL; DR是在一个Fragment
使用你的AsyncTask
在Fragment
调用setRetainInstance(true)
,并且将AsyncTask
的进度/结果报告给它的Activity
(或者它的目标Fragment
,如果你select使用描述的方法由@Timmmm)通过保留的Fragment
。
我的第一个build议是避免内部的AsyncTasks ,你可以阅读一个问题,我问这个和答案: Android:AsyncTaskbuild议:私人类或公共类?
之后,我开始使用非内部和…现在我看到很多好处。
第二个是,在Application
类中保留一个正在运行的AsyncTask的引用 – http://developer.android.com/reference/android/app/Application.html
每次你启动一个AsyncTask,在应用程序上设置它,当它完成时将它设置为null。
当一个片段/活动开始时,您可以检查是否有任何AsyncTask正在运行(通过检查应用程序中是否为null),然后将引用设置为任何您想要的(活动,片段等,以便您可以执行callback)。
这将解决您的问题:如果您只有一个AsyncTask在任何确定的时间运行,您可以添加一个简单的参考:
AsyncTask<?,?,?> asyncTask = null;
否则,在应用程序中有一个HashMap引用它们。
进度对话框可以遵循完全相同的原则。
我想出了一个使用AsyncTaskLoaders的方法。 这是非常容易使用,需要更less的海事IMO ..
基本上你可以像这样创build一个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; } }
然后在你点击一个button时使用上面的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 } }
这似乎处理方向的变化很好,你的后台任务将继续在旋转。
有几件事要注意:
- 如果在onCreate中,您重新连接到asynctaskloader,您将在之前的结果(即使您已经被告知请求已完成)中返回onLoadFinished()。 这在大多数情况下是非常好的行为,但有时候可能会很棘手。 虽然我想有很多方法来处理这个,我所做的就是在onLoadFinished中调用loader.abandon()。 然后,我添加了检查onCreate只重新附加到加载器,如果它还没有被放弃。 如果您再次需要结果数据,则不会希望这样做。 在大多数情况下,你想要的数据。
我有更多的细节在这里使用这个http调用
我创build了一个非常小的开源后台任务库,这个库很大程度上基于Marshmallow AsyncTask
但是具有额外的function,例如:
- 通过configuration更改自动保留任务;
- UIcallback(侦听器);
- 设备旋转时不重新启动或取消任务(就像Loaders所做的那样);
该库在内部使用一个没有任何用户界面的Fragment
,这个用户界面是通过configuration更改( setRetainInstance(true)
)保留的。
你可以在GitHub上find它: https : //github.com/NeoTech-Software/Android-Rainainable-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(); } }
我的方法是使用委托devise模式,一般来说,我们可以在您的AysncTask.doInBackground()方法中将实际的业务逻辑(从互联网或数据库读取数据)从AsyncTask(委托人)分离到BusinessDAO(委托人) ,将实际的任务委托给BusinessDAO,然后在BusinessDAO中实现一个单例进程机制,这样多次调用BusinessDAO.doSomething()就会触发一次每次运行的实际任务,等待任务结果。 这个想法是保留委托(即BusinessDAO)在configuration更改,而不是委托(即AsyncTask)。
-
创build/实现我们自己的应用程序,目的是在这里创build/初始化BusinessDAO,以便我们的BusinessDAO的生命周期是应用程序范围,而不是活动范围,请注意,您需要更改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; } }
-
我们现有的Activity / Fragement大部分是不变的,仍然把AsyncTask作为一个内部类来实现,并且涉及到Activity / Fragement中的AsyncTask.execute(),现在区别在于AsyncTask将实际任务委托给了BusinessDAO,所以在configuration更改期间,第二个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. } } ... ... }
-
在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%确定这是否可行,而且,示例代码片段应被视为伪代码。 我只是想从devise层面给你提供一些线索。 任何意见或build议,欢迎和赞赏。
如果有人发现他们的方式,这个线程然后我发现一个干净的方法是从一个app.Service
(启动START_STICKY)运行asynchronous任务,然后重新运行的服务迭代,以找出服务(因此是asynchronous任务)仍在运行;
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
方法和查看分页的问题。 此外,通过不保留实例,您可以使用不同的资源集重新创build活动(即新方向的不同视图布局)
你可以使AsyncTask成为一个静态字段。 If you need a context, you should ship your application context. This will avoid memory leaks, otherwise you'd keep a reference to your entire activity.
I write samepl code to solve this problem
First step is make Application class:
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); } }
In 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">
Code in activity:
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; } } }
When activity orientation changes variable mTask is inited from app context. When task is finished variable is set to null and remove from memory.
For me its enough.
Have a look at below example , how to use retained fragment to retain background task:
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; } } } }
看看这里 。
There is a solution based on Timmmm's solution.
But I improved it:
-
Now the solution is extendable – you only need to extend
FragmentAbleToStartTask
-
You able to keep running several tasks at the same time.
And in my opinion it's as easy as startActivityForResult and receive result
-
You also can stop a running task and check whether particular task is running
Sorry for my English