AsyncTask
est une bonne chose d'exécuter des tâches complexes dans un autre thread.
Mais quand il y a un changement d'orientation ou un autre changement de configuration alors que AsyncTask
est toujours en cours d'exécution, le Activity
actuel est détruit et redémarré. Et comme l'instance de AsyncTask
est connectée à cette activité, elle échoue et provoque l'affichage d'une fenêtre de message "forcer la fermeture".
Je cherche donc une sorte de "meilleure pratique" pour éviter ces erreurs et empêcher l'échec d'AsyncTask.
Ce que j'ai vu jusqu'à présent c'est:
onRetainNonConfigurationInstance
Activity
est détruit et de le redémarrer lorsque le Activity
est créé à nouveau.Quelques exemples de code:
Android AsyncTasks lors d'une rotation d'écran, partie I et partie II
Pouvez-vous m'aider à trouver la meilleure approche qui résout le mieux le problème et qui soit facile à mettre en œuvre? Le code lui-même est également important car je ne sais pas comment le résoudre correctement.
Ne [~ # ~] pas [~ # ~] utilisez Android:configChanges
Pour résoudre ce problème. C'est une très mauvaise pratique.
Ne [~ # ~] pas [~ # ~] utilisez Activity#onRetainNonConfigurationInstance()
non plus. Ceci est moins modulaire et ne convient pas aux applications basées sur Fragment
.
Vous pouvez lire mon article décrivant comment gérer les modifications de configuration avec retenu Fragment
s. Cela résout le problème de conserver un AsyncTask
pendant un changement de rotation. Vous devez fondamentalement héberger votre AsyncTask
à l'intérieur d'un Fragment
, appeler setRetainInstance(true)
sur le Fragment
et signaler le progrès/les résultats de AsyncTask
Retour à c'est Activity
à travers le Fragment
retenu.
Je résous généralement ce problème en faisant en sorte que mes tâches asynchrones déclenchent des intentions de diffusion dans le rappel .onPostExecute (), de sorte qu'elles ne modifient pas l'activité qui les a démarrées directement. Les Activités écoutent ces émissions avec les BroadcastReceivers dynamiques et agissent en conséquence.
De cette façon, les tâches asynchrones n'ont pas à se soucier de l'instance d'activité spécifique qui gère leur résultat. Ils ne font que "crier" quand ils ont terminé et si une activité à ce moment-là (active et ciblée/est dans son état de reprise) s'intéresse aux résultats de la tâche, elle sera gérée.
Cela implique un peu plus de temps système, car le runtime doit gérer la diffusion, mais cela ne me dérange généralement pas. Je pense que l'utilisation de LocalBroadcastManager au lieu de celle par défaut du système large accélère un peu les choses.
Voici un autre exemple d'une AsyncTask qui utilise un Fragment
pour gérer les modifications de configuration à l'exécution (comme lorsque l'utilisateur fait pivoter l'écran) avec setRetainInstance(true)
. Une barre de progression déterminée (mise à jour régulièrement) est également présentée.
L'exemple est en partie basé sur la documentation officielle, Conserver un objet lors d'un changement de configuration .
Dans cet exemple, le travail nécessitant un fil d’arrière-plan est le simple chargement d’une image d’Internet dans l’UI.
Alex Lockwood semble avoir raison de dire que lorsqu'il s'agit de gérer les modifications de configuration d'exécution avec AsyncTasks à l'aide d'un "fragment conservé", il est recommandé. onRetainNonConfigurationInstance()
devient obsolète dans Lint, dans Android Studio. Les documents officiels nous en avertissent en utilisant Android:configChanges
, à partir de Gestion de la modification de configuration vous-même , ...
Le fait de gérer vous-même les changements de configuration peut rendre l'utilisation de ressources alternatives plus difficile, car le système ne les applique pas automatiquement pour vous. Cette technique doit être considérée comme un dernier recours lorsque vous devez éviter les redémarrages en raison d'un changement de configuration et n'est pas recommandée pour la plupart des applications.
Ensuite, il y a la question de savoir s'il faut utiliser une AsyncTask pour le thread en arrière-plan.
Le référence officielle pour AsyncTask avertit ...
AsyncTasks devrait idéalement être utilisé pour des opérations courtes (quelques secondes au maximum). Si vous devez maintenir les threads en cours d'exécution pendant de longues périodes, il est vivement recommandé d'utiliser les différentes API fournies par le paquet Java.util.concurrent, telles que Executor, ThreadPoolExecutor et FutureTask.
Vous pouvez également utiliser un service, un chargeur (à l'aide d'un CursorLoader ou AsyncTaskLoader) ou un fournisseur de contenu pour effectuer des opérations asynchrones.
Je décompose le reste du post en:
Commencez avec une AsyncTask de base en tant que classe interne d'une activité (il n'est pas nécessaire que ce soit une classe interne mais ce sera probablement pratique). À ce stade, la tâche AsyncTask ne gère pas les modifications de la configuration d'exécution.
public class ThreadsActivity extends ActionBarActivity {
private ImageView mPictureImageView;
private class LoadImageFromNetworkAsyncTask
extends AsyncTask<String, Void, Bitmap> {
@Override
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
mPictureImageView.setImageBitmap(bitmap);
}
}
/**
* Requires in AndroidManifext.xml
* <uses-permission Android:name="Android.permission.INTERNET" />
*/
private Bitmap loadImageFromNetwork(String url) {
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream((InputStream)
new URL(url).getContent());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threads);
mPictureImageView =
(ImageView) findViewById(R.id.imageView_picture);
}
public void getPicture(View view) {
new LoadImageFromNetworkAsyncTask()
.execute("http://i.imgur.com/SikTbWe.jpg");
}
}
Ajoutez une classe imbriquée RetainedFragment qui étend la classe Fragement et ne possède pas sa propre interface utilisateur. Ajoutez setRetainInstance (true) à l'événement onCreate de ce fragment. Fournir des procédures pour définir et obtenir vos données.
public class ThreadsActivity extends Activity {
private ImageView mPictureImageView;
private RetainedFragment mRetainedFragment = null;
...
public static class RetainedFragment extends Fragment {
private Bitmap mBitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The key to making data survive
// runtime configuration changes.
setRetainInstance(true);
}
public Bitmap getData() {
return this.mBitmap;
}
public void setData(Bitmap bitmapToRetain) {
this.mBitmap = bitmapToRetain;
}
}
private class LoadImageFromNetworkAsyncTask
extends AsyncTask<String, Integer,Bitmap> {
....
Dans onCreate () de la classe d'activité la plus externe, gérez le RetainedFragment: Référencez-le s'il existe déjà (au cas où l'activité redémarre); créez-le et ajoutez-le s'il n'existe pas; Ensuite, s'il existait déjà, récupérez les données de RetainedFragment et définissez votre interface utilisateur avec ces données.
public class ThreadsActivity extends Activity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threads);
final String retainedFragmentTag = "RetainedFragmentTag";
mPictureImageView =
(ImageView) findViewById(R.id.imageView_picture);
mLoadingProgressBar =
(ProgressBar) findViewById(R.id.progressBar_loading);
// Find the RetainedFragment on Activity restarts
FragmentManager fm = getFragmentManager();
// The RetainedFragment has no UI so we must
// reference it with a tag.
mRetainedFragment =
(RetainedFragment) fm.findFragmentByTag(retainedFragmentTag);
// if Retained Fragment doesn't exist create and add it.
if (mRetainedFragment == null) {
// Add the fragment
mRetainedFragment = new RetainedFragment();
fm.beginTransaction()
.add(mRetainedFragment, retainedFragmentTag).commit();
// The Retained Fragment exists
} else {
mPictureImageView
.setImageBitmap(mRetainedFragment.getData());
}
}
Initier la AsyncTask à partir de l'interface utilisateur
public void getPicture(View view) {
new LoadImageFromNetworkAsyncTask().execute(
"http://i.imgur.com/SikTbWe.jpg");
}
Ajoutez et codez une barre de progression déterminée:
Mise en page de l'activité.
<ScrollView xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
tools:context="com.example.mysecondapp.ThreadsActivity">
<RelativeLayout
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:orientation="vertical"
Android:paddingBottom="@dimen/activity_vertical_margin"
Android:paddingLeft="@dimen/activity_horizontal_margin"
Android:paddingRight="@dimen/activity_horizontal_margin"
Android:paddingTop="@dimen/activity_vertical_margin">
<ImageView
Android:id="@+id/imageView_picture"
Android:layout_width="300dp"
Android:layout_height="300dp"
Android:background="@Android:color/black" />
<Button
Android:id="@+id/button_get_picture"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:layout_alignParentLeft="true"
Android:layout_alignParentStart="true"
Android:layout_below="@id/imageView_picture"
Android:onClick="getPicture"
Android:text="Get Picture" />
<Button
Android:id="@+id/button_clear_picture"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:layout_alignBottom="@id/button_get_picture"
Android:layout_toEndOf="@id/button_get_picture"
Android:layout_toRightOf="@id/button_get_picture"
Android:onClick="clearPicture"
Android:text="Clear Picture" />
<ProgressBar
Android:id="@+id/progressBar_loading"
style="?android:attr/progressBarStyleHorizontal"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:layout_below="@id/button_get_picture"
Android:progress="0"
Android:indeterminateOnly="false"
Android:visibility="invisible" />
</RelativeLayout>
</ScrollView>
L'activité avec: classe interne AsyncTask sous-classée; classe interne RetainedFragment sous-classée qui gère les modifications de configuration d'exécution (par exemple, lorsque l'utilisateur fait pivoter l'écran); et une barre de progression déterminée mise à jour à intervalles réguliers. ...
public class ThreadsActivity extends Activity {
private ImageView mPictureImageView;
private RetainedFragment mRetainedFragment = null;
private ProgressBar mLoadingProgressBar;
public static class RetainedFragment extends Fragment {
private Bitmap mBitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The key to making data survive runtime configuration changes.
setRetainInstance(true);
}
public Bitmap getData() {
return this.mBitmap;
}
public void setData(Bitmap bitmapToRetain) {
this.mBitmap = bitmapToRetain;
}
}
private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,
Integer, Bitmap> {
@Override
protected Bitmap doInBackground(String... urls) {
// Simulate a burdensome load.
int sleepSeconds = 4;
for (int i = 1; i <= sleepSeconds; i++) {
SystemClock.sleep(1000); // milliseconds
publishProgress(i * 20); // Adjust for a scale to 100
}
return com.example.standardapplibrary.Android.Network
.loadImageFromNetwork(
urls[0]);
}
@Override
protected void onProgressUpdate(Integer... progress) {
mLoadingProgressBar.setProgress(progress[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
publishProgress(100);
mRetainedFragment.setData(bitmap);
mPictureImageView.setImageBitmap(bitmap);
mLoadingProgressBar.setVisibility(View.INVISIBLE);
publishProgress(0);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threads);
final String retainedFragmentTag = "RetainedFragmentTag";
mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);
mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);
// Find the RetainedFragment on Activity restarts
FragmentManager fm = getFragmentManager();
// The RetainedFragment has no UI so we must reference it with a tag.
mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(
retainedFragmentTag);
// if Retained Fragment doesn't exist create and add it.
if (mRetainedFragment == null) {
// Add the fragment
mRetainedFragment = new RetainedFragment();
fm.beginTransaction().add(mRetainedFragment,
retainedFragmentTag).commit();
// The Retained Fragment exists
} else {
mPictureImageView.setImageBitmap(mRetainedFragment.getData());
}
}
public void getPicture(View view) {
mLoadingProgressBar.setVisibility(View.VISIBLE);
new LoadImageFromNetworkAsyncTask().execute(
"http://i.imgur.com/SikTbWe.jpg");
}
public void clearPicture(View view) {
mRetainedFragment.setData(null);
mPictureImageView.setImageBitmap(null);
}
}
Dans cet exemple, la fonction de bibliothèque (référencée ci-dessus avec le préfixe de package explicite com.example.standardapplibrary.Android.Network) qui fonctionne réellement ...
public static Bitmap loadImageFromNetwork(String url) {
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
.getContent());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
Ajoutez les autorisations requises par votre tâche en arrière-plan au fichier AndroidManifest.xml ...
<manifest>
...
<uses-permission Android:name="Android.permission.INTERNET" />
Ajoutez votre activité à AndroidManifest.xml ...
<manifest>
...
<application>
<activity
Android:name=".ThreadsActivity"
Android:label="@string/title_activity_threads"
Android:parentActivityName=".MainActivity">
<meta-data
Android:name="Android.support.PARENT_ACTIVITY"
Android:value="com.example.mysecondapp.MainActivity" />
</activity>
Récemment, j'ai trouvé une bonne solution ici . Il est basé sur l'enregistrement d'un objet de tâche via RetainConfiguration. Pour moi, la solution est très élégante et quant à moi, je commence à l’utiliser. Vous devez simplement imbriquer votre asynctask de la tâche de base et c'est tout.
Vous pouvez utiliser des chargeurs pour cela. Vérifiez Doc ici
Basé sur la réponse de @Alex Lockwood et sur les réponses de @William & @quickdraw mcgraw sur ce message: Comment gérer les messages du gestionnaire lorsque l'activité/le fragment est suspend , j'ai écrit une solution générique.
De cette façon, la rotation est gérée et si l'activité passe en arrière-plan pendant l'exécution de la tâche asynchrone, elle recevra les rappels (onPreExecute, onProgressUpdate, onPostExecute & onCancelled) une fois reprise, de sorte qu'aucune exception IllegalStateException ne sera levée (voir Comment gérer les messages du gestionnaire lorsque l’activité/le fragment est en pause ).
Ce serait bien d'avoir la même chose mais avec des types d'arguments génériques, comme un AsyncTask (par exemple: AsyncTaskFragment <Params, Progress, Result>), mais je n'ai pas réussi à le faire rapidement et à ne pas avoir de temps pour le moment. Si quelqu'un veut faire de l'amélioration, n'hésitez pas!
Le code:
import Android.app.Activity;
import Android.os.AsyncTask;
import Android.os.Bundle;
import Android.os.Message;
import Android.support.v4.app.Fragment;
import Android.support.v4.app.FragmentManager;
import Android.support.v7.app.AppCompatActivity;
public class AsyncTaskFragment extends Fragment {
/* ------------------------------------------------------------------------------------------ */
// region Classes & Interfaces
public static abstract class Task extends AsyncTask<Object, Object, Object> {
private AsyncTaskFragment _fragment;
private void setFragment(AsyncTaskFragment fragment) {
_fragment = fragment;
}
@Override
protected final void onPreExecute() {
// Save the state :
_fragment.setRunning(true);
// Send a message :
sendMessage(ON_PRE_EXECUTE_MESSAGE, null);
}
@Override
protected final void onPostExecute(Object result) {
// Save the state :
_fragment.setRunning(false);
// Send a message :
sendMessage(ON_POST_EXECUTE_MESSAGE, result);
}
@Override
protected final void onProgressUpdate(Object... values) {
// Send a message :
sendMessage(ON_PROGRESS_UPDATE_MESSAGE, values);
}
@Override
protected final void onCancelled() {
// Save the state :
_fragment.setRunning(false);
// Send a message :
sendMessage(ON_CANCELLED_MESSAGE, null);
}
private void sendMessage(int what, Object obj) {
Message message = new Message();
message.what = what;
message.obj = obj;
Bundle data = new Bundle(1);
data.putString(EXTRA_FRAGMENT_TAG, _fragment.getTag());
message.setData(data);
_fragment.handler.sendMessage(message);
}
}
public interface AsyncTaskFragmentListener {
void onPreExecute(String fragmentTag);
void onProgressUpdate(String fragmentTag, Object... progress);
void onCancelled(String fragmentTag);
void onPostExecute(String fragmentTag, Object result);
}
private static class AsyncTaskFragmentPauseHandler extends PauseHandler {
@Override
final protected void processMessage(Activity activity, Message message) {
switch (message.what) {
case ON_PRE_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPreExecute(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
case ON_POST_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPostExecute(message.getData().getString(EXTRA_FRAGMENT_TAG), message.obj); break; }
case ON_PROGRESS_UPDATE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onProgressUpdate(message.getData().getString(EXTRA_FRAGMENT_TAG), ((Object[])message.obj)); break; }
case ON_CANCELLED_MESSAGE : { ((AsyncTaskFragmentListener)activity).onCancelled(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
}
}
}
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Attributes
private Task _task;
private AsyncTaskFragmentListener _listener;
private boolean _running = false;
private static final String EXTRA_FRAGMENT_TAG = "EXTRA_FRAGMENT_TAG";
private static final int ON_PRE_EXECUTE_MESSAGE = 0;
private static final int ON_POST_EXECUTE_MESSAGE = 1;
private static final int ON_PROGRESS_UPDATE_MESSAGE = 2;
private static final int ON_CANCELLED_MESSAGE = 3;
private AsyncTaskFragmentPauseHandler handler = new AsyncTaskFragmentPauseHandler();
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Getters
public AsyncTaskFragmentListener getListener() { return _listener; }
public boolean isRunning() { return _running; }
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Setters
public void setTask(Task task) {
_task = task;
_task.setFragment(this);
}
public void setListener(AsyncTaskFragmentListener listener) { _listener = listener; }
private void setRunning(boolean running) { _running = running; }
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Fragment lifecycle
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onResume() {
super.onResume();
handler.resume(getActivity());
}
@Override
public void onPause() {
super.onPause();
handler.pause();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
_listener = (AsyncTaskFragmentListener) activity;
}
@Override
public void onDetach() {
super.onDetach();
_listener = null;
}
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Utils
public void execute(Object... params) {
_task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
}
public void cancel(boolean mayInterruptIfRunning) {
_task.cancel(mayInterruptIfRunning);
}
public static AsyncTaskFragment getRetainedOrNewFragment(AppCompatActivity activity, String fragmentTag) {
FragmentManager fm = activity.getSupportFragmentManager();
AsyncTaskFragment fragment = (AsyncTaskFragment) fm.findFragmentByTag(fragmentTag);
if (fragment == null) {
fragment = new AsyncTaskFragment();
fragment.setListener( (AsyncTaskFragmentListener) activity);
fm.beginTransaction().add(fragment, fragmentTag).commit();
}
return fragment;
}
// endregion
/* ------------------------------------------------------------------------------------------ */
}
Vous aurez besoin du PauseHandler:
import Android.app.Activity;
import Android.os.Handler;
import Android.os.Message;
import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.List;
/**
* Message Handler class that supports buffering up of messages when the activity is paused i.e. in the background.
*
* https://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused
*/
public abstract class PauseHandler extends Handler {
/**
* Message Queue Buffer
*/
private final List<Message> messageQueueBuffer = Collections.synchronizedList(new ArrayList<Message>());
/**
* Flag indicating the pause state
*/
private Activity activity;
/**
* Resume the handler.
*/
public final synchronized void resume(Activity activity) {
this.activity = activity;
while (messageQueueBuffer.size() > 0) {
final Message msg = messageQueueBuffer.get(0);
messageQueueBuffer.remove(0);
sendMessage(msg);
}
}
/**
* Pause the handler.
*/
public final synchronized void pause() {
activity = null;
}
/**
* Store the message if we have been paused, otherwise handle it now.
*
* @param msg Message to handle.
*/
@Override
public final synchronized void handleMessage(Message msg) {
if (activity == null) {
final Message msgCopy = new Message();
msgCopy.copyFrom(msg);
messageQueueBuffer.add(msgCopy);
} else {
processMessage(activity, msg);
}
}
/**
* Notification message to be processed. This will either be directly from
* handleMessage or played back from a saved message when the activity was
* paused.
*
* @param activity Activity owning this Handler that isn't currently paused.
* @param message Message to be handled
*/
protected abstract void processMessage(Activity activity, Message message);
}
Exemple d'utilisation:
public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {
private final static String ASYNC_TASK_FRAGMENT_A = "ASYNC_TASK_FRAGMENT_A";
private final static String ASYNC_TASK_FRAGMENT_B = "ASYNC_TASK_FRAGMENT_B";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button testButton = (Button) findViewById(R.id.test_button);
final AsyncTaskFragment fragment = AsyncTaskFragment.getRetainedOrNewFragment(TestActivity.this, ASYNC_TASK_FRAGMENT_A);
testButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(!fragment.isRunning()) {
fragment.setTask(new Task() {
@Override
protected Object doInBackground(Object... objects) {
// Do your async stuff
return null;
}
});
fragment.execute();
}
}
});
}
@Override
public void onPreExecute(String fragmentTag) {}
@Override
public void onProgressUpdate(String fragmentTag, Float percent) {}
@Override
public void onCancelled(String fragmentTag) {}
@Override
public void onPostExecute(String fragmentTag, Object result) {
switch (fragmentTag) {
case ASYNC_TASK_FRAGMENT_A: {
// Handle ASYNC_TASK_FRAGMENT_A
break;
}
case ASYNC_TASK_FRAGMENT_B: {
// Handle ASYNC_TASK_FRAGMENT_B
break;
}
}
}
}
Pour ceux qui souhaitent éviter les fragments, vous pouvez conserver la tâche Async qui s'exécute sur les modifications d'orientation à l'aide de onRetainCustomNonConfigurationInstance () et de certains câblages.
(Notez que cette méthode est l’alternative à la méthode déconseillée onRetainNonConfigurationInstance () ).
On dirait que cette solution n'est pas fréquemment mentionnée cependant. J'ai écrit un exemple courant simple pour illustrer.
À votre santé!
public class MainActivity extends AppCompatActivity {
private TextView result;
private Button run;
private AsyncTaskHolder asyncTaskHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
result = (TextView) findViewById(R.id.textView_result);
run = (Button) findViewById(R.id.button_run);
asyncTaskHolder = getAsyncTaskHolder();
run.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
asyncTaskHolder.execute();
}
});
}
private AsyncTaskHolder getAsyncTaskHolder() {
if (this.asyncTaskHolder != null) {
return asyncTaskHolder;
}
//Not deprecated. Get the same instance back.
Object instance = getLastCustomNonConfigurationInstance();
if (instance == null) {
instance = new AsyncTaskHolder();
}
if (!(instance instanceof ActivityDependant)) {
Log.e("", instance.getClass().getName() + " must implement ActivityDependant");
}
return (AsyncTaskHolder) instance;
}
@Override
//Not deprecated. Save the object containing the running task.
public Object onRetainCustomNonConfigurationInstance() {
return asyncTaskHolder;
}
@Override
protected void onStart() {
super.onStart();
if (asyncTaskHolder != null) {
asyncTaskHolder.attach(this);
}
}
@Override
protected void onStop() {
super.onStop();
if (asyncTaskHolder != null) {
asyncTaskHolder.detach();
}
}
void updateUI(String value) {
this.result.setText(value);
}
interface ActivityDependant {
void attach(Activity activity);
void detach();
}
class AsyncTaskHolder implements ActivityDependant {
private Activity parentActivity;
private boolean isRunning;
private boolean isUpdateOnAttach;
@Override
public synchronized void attach(Activity activity) {
this.parentActivity = activity;
if (isUpdateOnAttach) {
((MainActivity) parentActivity).updateUI("done");
isUpdateOnAttach = false;
}
}
@Override
public synchronized void detach() {
this.parentActivity = null;
}
public synchronized void execute() {
if (isRunning) {
Toast.makeText(parentActivity, "Already running", Toast.LENGTH_SHORT).show();
return;
}
isRunning = true;
new AsyncTask<Void, Integer, Void>() {
@Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < 100; i += 10) {
try {
Thread.sleep(500);
publishProgress(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
if (parentActivity != null) {
((MainActivity) parentActivity).updateUI(String.valueOf(values[0]));
}
}
@Override
protected synchronized void onPostExecute(Void aVoid) {
if (parentActivity != null) {
((MainActivity) parentActivity).updateUI("done");
} else {
isUpdateOnAttach = true;
}
isRunning = false;
}
}.execute();
}
}
J'ai implémenté bibliothèque qui peut résoudre les problèmes de pause d'activité et de recréation pendant l'exécution de votre tâche.
Vous devez implémenter AsmykPleaseWaitTask
et AsmykBasicPleaseWaitActivity
. Votre activité et votre tâche en arrière-plan fonctionneront bien même si vous pivotez l'écran et passez d'une application à l'autre.