Я работаю над приложением для смартфона / планшета, использую только один APK и загружаю ресурсы по мере необходимости в зависимости от размера экрана. Лучшим вариантом дизайна, казалось, было использование фрагментов через ACL.
Это приложение до сих пор работало нормально, основываясь только на активности. Это фиктивный класс того, как я обрабатываю AsyncTasks и ProgressDialogs в Activity, чтобы они работали даже при повороте экрана или изменении конфигурации во время общения.
Я не буду изменять манифест, чтобы избежать воссоздания Activity, есть много причин, по которым я не хочу этого делать, но в основном потому, что официальные документы говорят, что это не рекомендуется, и я обошелся без этого до сих пор, поэтому, пожалуйста, не рекомендуйте это маршрут.
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 пользователей без жалоб, поэтому казалось логичным просто скопировать эту логику в новый дизайн на основе фрагментов, но, конечно, это не работает.
Вот 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, а не из фрагмента, то же самое происходит с getLastNonConfigurationInstance()
. Я читал здесь несколько похожих вопросов, но без ответа.
Я понимаю, что может потребоваться некоторая работа, чтобы правильно организовать этот материал по фрагментам, при этом я хотел бы сохранить ту же базовую логику дизайна.
Каким будет правильный способ сохранить AsyncTask во время изменения конфигурации, и если он все еще работает, покажите progressDialog, учитывая, что AsyncTask является внутренним классом для фрагмента, и именно сам фрагмент вызывает AsyncTask.execute ()?
Fragment
sОтветы:
Фрагменты могут сделать это намного проще. Просто используйте метод Fragment.setRetainInstance (boolean), чтобы ваш экземпляр фрагмента сохранялся при изменении конфигурации. Обратите внимание, что это рекомендуемая замена Activity.onRetainnonConfigurationInstance () в документации.
Если по какой-то причине вы действительно не хотите использовать сохраненный фрагмент, вы можете использовать другие подходы. Обратите внимание, что каждый фрагмент имеет уникальный идентификатор, возвращаемый Fragment.getId () . Вы также можете узнать, удаляется ли фрагмент для изменения конфигурации, с помощью Fragment.getActivity (). IsChangingConfigurations () . Итак, в момент, когда вы решите остановить свою AsyncTask (скорее всего, в onStop () или onDestroy ()), вы можете, например, проверить, изменяется ли конфигурация, и, если да, вставить ее в статический SparseArray под идентификатором фрагмента, а затем в onCreate () или onStart () посмотрите, есть ли у вас AsyncTask в доступном разреженном массиве.
источник
onPostExecute
метод все равно должен быть ждать, прежде чем окончательно будет обработан очередью сообщений основного потока.Думаю, вам понравится мой чрезвычайно подробный и рабочий пример, подробно описанный ниже.
редактировать
По просьбе Брэда Ларсона я воспроизвел большую часть связанного решения ниже. Также с тех пор, как я опубликовал это, меня указали
AsyncTaskLoader
. Я не уверен, что это полностью применимо к тем же проблемам, но вы все равно должны проверить это.Использование
AsyncTask
с диалогами выполнения и вращением устройства.Рабочее решение!
Наконец-то у меня все работает. Мой код имеет следующие особенности:
Fragment
, расположение которого меняется в зависимости от ориентации.AsyncTask
котором вы можете поработать.DialogFragment
который показывает ход выполнения задачи на индикаторе выполнения (а не только на неопределенном счетчике).Я не думаю, что такое сочетание работоспособности можно найти где-нибудь еще.
Основная идея заключается в следующем. Есть
MainActivity
класс, который содержит единственный фрагмент -MainFragment
.MainFragment
имеет разные макеты для горизонтальной и вертикальной ориентации иsetRetainInstance()
является ложным, поэтому макет может измениться. Это означает, что при изменении ориентации устройства обаMainActivity
иMainFragment
полностью уничтожаются и воссоздаются.Отдельно у нас есть
MyTask
(расширенныйAsyncTask
), который выполняет всю работу. Мы не можем сохранить его,MainFragment
потому что он будет уничтожен, а Google не рекомендует использовать что-либо подобноеsetRetainNonInstanceConfiguration()
. В любом случае это не всегда доступно, и в лучшем случае это уродливый прием. Вместо этого мы будем хранитьMyTask
в другом фрагменте,DialogFragment
вызываемомTaskFragment
. Этот фрагмент будет ужеsetRetainInstance()
установлен верно, так как устройство вращается этот фрагмент не разрушается и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? } }
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); } }
Моя задача
// 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 настаивал на добавлении библиотеки поддержки, прежде чем это позволило мне создать проект. Я уверен, ты сможешь это удалить.
источник
DialogFragment
, потому что в нем есть элементы пользовательского интерфейса, содержащие ссылки на старый контекст. Вместо этого я бы сохранилAsyncTask
в другом пустом фрагменте и установилDialogFragment
его в качестве цели.onCreateView()
повторном вызове? СтарыйmProgressBar
будет как минимум перезаписан новым.mProgressBar = null;
в ,onDestroyView()
если вы хотите быть очень уверен. Метод Singularity может быть хорошей идеей, но он еще больше увеличит сложность кода!mTask.execute()
наMainFragment.onClick()
. В качестве альтернативы вы можете разрешить передачу параметровsetTask()
или даже сохранить их вMyTask
себе. Я не совсем понимаю, что означает ваш первый вопрос, но, может быть, вы хотите использоватьTaskFragment.getTargetFragment()
? Я почти уверен, что он будет работать с расширениемViewPager
. НоViewPagers
не очень хорошо изучены или задокументированы, так что удачи! Помните, что ваш фрагмент не создается, пока он не станет видимым в первый раз.Недавно я опубликовал статью, в которой описывается, как обрабатывать изменения конфигурации с помощью сохраненных
Fragment
s. ЭтоAsyncTask
хорошо решает проблему сохранения изменения поворота.TL; DR является использование хоста вашей
AsyncTask
ВнутриFragment
, вызовsetRetainInstance(true)
наFragment
, и сообщаетAsyncTask
«s прогресс / результаты вернуться к нему вActivity
(или его целиFragment
, если вы решили использовать подход , описанный @Timmmm) через стопорноеFragment
.источник
onAttach
иonDetach
, будет ли лучше внутриTaskFragment
, мы просто вызываемgetActivity
каждый раз, когда нам нужно запустить обратный вызов. (Проверено instaceofTaskCallbacks
)onAttach()
иonDetach()
поэтому я мог избежать постоянного приведения активностиTaskCallbacks
каждый раз, когда я хотел ее использовать.Мое первое предложение - избегать внутренних AsyncTasks , вы можете прочитать вопрос, который я задал по этому поводу, и ответы: Android: рекомендации AsyncTask: частный класс или открытый класс?
После этого я начал использовать невнутренние и ... теперь вижу МНОГО преимуществ.
Во-вторых, сохраните ссылку на вашу запущенную AsyncTask в
Application
классе - http://developer.android.com/reference/android/app/Application.htmlКаждый раз, когда вы запускаете AsyncTask, устанавливайте его в приложении, а когда он завершает, установите для него значение null.
Когда начинается фрагмент / действие, вы можете проверить, запущена ли какая-либо AsyncTask (проверив, является ли он нулевым или нет в приложении), а затем установить ссылку внутри на все, что вы хотите (активность, фрагмент и т.д., чтобы вы могли выполнять обратные вызовы).
Это решит вашу проблему: если у вас работает только 1 AsyncTask в любое определенное время, вы можете добавить простую ссылку:
AsyncTask<?,?,?> asyncTask = null;
В противном случае имейте в приложении HashMap со ссылками на них.
Диалог прогресса может следовать тому же принципу.
источник
Я придумал для этого метод использования 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 } }
Кажется, это нормально обрабатывает изменения ориентации, и ваша фоновая задача будет продолжаться во время поворота.
Несколько замечаний:
У меня есть более подробная информация об использовании этого для HTTP-вызовов здесь
источник
getSupportLoaderManager().getLoader(0);
это не вернет null (потому что загрузчик с этим идентификатором 0 еще не существует)?Я создал очень крошечную библиотеку фоновых задач с открытым исходным кодом, которая в значительной степени основана на Marshmallow,
AsyncTask
но с дополнительными функциями, такими как:Внутри библиотеки используется
Fragment
без какого-либо пользовательского интерфейса, который сохраняется при изменении конфигурации (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(); } }
источник
Мой подход заключается в использовании шаблона проектирования делегирования, в общем, мы можем изолировать фактическую бизнес-логику (чтение данных из Интернета или базы данных или что-то еще) от AsyncTask (делегат) до BusinessDAO (делегат) в вашем методе AysncTask.doInBackground () , делегируйте фактическую задачу BusinessDAO, а затем реализуйте механизм одноэлементного процесса в BusinessDAO, чтобы многократный вызов BusinessDAO.doSomething () просто запускал одну фактическую задачу, выполняемую каждый раз и ожидающую результата задачи. Идея состоит в том, чтобы сохранить делегата (например, BusinessDAO) во время изменения конфигурации вместо делегатора (например, AsyncTask).
Создайте / внедрите наше собственное приложение, цель состоит в том, чтобы создать / инициализировать 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 как внутренний класс и задействуем AsyncTask.execute () из Activity / Fragement, разница теперь в том, что 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. } } ... ... }
Внутри 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% уверен, что это сработает, более того, фрагмент кода примера следует рассматривать как псевдокод. Я просто пытаюсь дать вам подсказку на уровне дизайна. Любые отзывы или предложения приветствуются и ценятся.
источник
Вы можете сделать AsyncTask статическим полем. Если вам нужен контекст, вы должны отправить контекст своего приложения. Это позволит избежать утечек памяти, иначе вы сохраните ссылку на всю свою деятельность.
источник
Если кто-то найдет свой путь к этому потоку, я обнаружил, что чистый подход состоял в том, чтобы запустить задачу Async из
app.Service
(запущенную с START_STICKY), а затем при воссоздании перебрать запущенные службы, чтобы узнать, остается ли служба (и, следовательно, задача async) Бег;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
методом и просмотром страниц. Кроме того, не сохраняя экземпляр, вы можете воссоздать свою деятельность, используя другой набор ресурсов (т. Е. Другой макет представления для новой ориентации).источник
Я пишу код 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 запускается из контекста приложения. Когда задача завершена, переменная устанавливается в ноль и удаляется из памяти.
Для меня этого достаточно.
источник
Взгляните на приведенный ниже пример, как использовать сохраненный фрагмент для сохранения фоновой задачи:
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; } } } }
источник
Взгляните сюда .
Есть решение, основанное на решении Timmmm .
Но я его улучшил:
Теперь решение расширяемое - вам нужно только расширить
FragmentAbleToStartTask
Вы можете выполнять несколько задач одновременно.
И на мой взгляд это так же просто, как startActivityForResult и получить результат
Вы также можете остановить запущенную задачу и проверить, запущена ли конкретная задача.
Извините за мой английский
источник