Fragments D'Androïde. Maintien D'un AsyncTask lors d'une rotation de l'écran ou d'un changement de configuration
je travaille sur une application Smartphone / Tablette, en utilisant un seul APK, et en chargeant les ressources nécessaires en fonction de la taille de l'écran, le meilleur choix de conception semblait utiliser des Fragments via L'ACL.
cette application a fonctionné très bien jusqu'à présent étant seulement basée sur l'activité. Il s'agit d'une classe simulée de la façon dont je gère les AsyncTasks et ProgressDialogs dans les activités afin de les faire fonctionner même lorsque l'écran est tourné ou qu'un changement de configuration se produit au milieu de la communication.
Je ne changerai pas le Manifeste pour éviter la récréation de l'activité, il y a beaucoup de raisons pour lesquelles je ne veux pas le faire, mais principalement parce que les documents officiels disent qu'il n'est pas recommandé et j'ai réussi sans elle jusqu'à présent, alors s'il vous plaît ne pas RECOM mander cette route.
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
}
}
}
}
ce code fonctionne bien, j'ai environ 10.000 utilisateurs sans plainte, il a donc semblé logique de simplement copier cette logique dans le nouveau design basé sur Fragment, mais, bien sûr, il n'est pas travailler.
voici le 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
}
}
}
}
}
Je ne peux pas utiliser onRetainNonConfigurationInstance()
puisqu'il doit être appelé de L'activité et pas le Fragment, il va de même avec getLastNonConfigurationInstance()
. J'ai lu quelques questions similaires ici sans réponse.
je comprends qu'il pourrait être nécessaire de travailler autour pour obtenir cette substance organisée correctement en fragments, cela étant dit, je voudrais maintenir la même logique de conception de base.
quelle serait la bonne façon de conserver L'AsyncTask lors d'un changement de configuration, et si son fonctionnement est toujours en cours, montrer un progressDialog, en tenant compte du fait que L'AsyncTask est une classe interne du Fragment et que c'est le Fragment lui-même qui invoque l'AsyncTask.execute ()?
12 réponses
Fragments peuvent en fait rendre cela beaucoup plus facile. Il suffit d'utiliser la méthode Fragment.setRetainInstance (booléen) pour conserver votre instance de fragment à travers les changements de configuration. Notez qu'il s'agit du remplacement recommandé pour L'activité .onRetainnonConfigurationInstance() dans les docs.
si pour une raison quelconque vous ne voulez vraiment pas utiliser un fragment retenu, il y a d'autres approches que vous pouvez prendre. Notez que chaque fragment a un identifiant unique retourné par Fragment.getId () . Vous pouvez également savoir si un fragment est en train d'être démonté pour un changement de configuration via le Fragment .getActivity ().isChangingConfigurations () . Ainsi, au point où vous décideriez d'arrêter votre AsyncTask (dans onStop () ou onDestroy () très probablement), vous pourriez par exemple vérifier si la configuration est en train de changer et si c'est le cas, le coller dans un champ de balayage statique sous l'identifiant du fragment, et puis dans votre onCreate() ou onStart() regardez pour voir si vous avez un AsyncTask dans le tableau clairsemé disponible.
je pense que vous apprécierez mon très complet et l'exemple de travail détaillé ci-dessous.
- la Rotation fonctionne, et le dialogue survit.
- vous pouvez annuler la tâche et le dialogue en appuyant sur le bouton arrière (si vous voulez ce comportement).
- il utilise des fragments.
- la disposition du fragment sous l'activité change correctement lorsque le dispositif tourne.
- il y a un téléchargement complet de code source et un APK précompilé donc vous pouvez voir si le comportement est ce que vous voulez.
Modifier
comme demandé par Brad Larson j'ai reproduit la plupart de la solution liée ci-dessous. Aussi depuis que je l'ai posté j'ai été pointé à AsyncTaskLoader
. Je ne suis pas sûr qu'il soit totalement applicable aux mêmes problèmes, mais vous devriez le vérifier de toute façon.
Utilisation de AsyncTask
avec dialogue de progression et rotation du dispositif.
une solution efficace!
j'ai enfin tout pour fonctionner. Mon code a les caractéristiques suivantes:
- a
Fragment
dont la disposition change avec l'orientation. - An
AsyncTask
dans lequel vous pouvez travailler. - A
DialogFragment
qui montre le progrès de la tâche dans une barre de progrès (pas seulement un spinner indéterminé). - Rotation fonctionne sans interrompre la tâche ou rejeter le dialogue.
- le bouton arrière écarte la boîte de dialogue et annule la tâche (vous pouvez modifier ce comportement assez facilement cependant).
Je ne pense pas que la combinaison de la volonté de travailler puisse être trouvée ailleurs.
l'idée de base est la suivante. Il existe une classe MainActivity
qui contient une le fragment MainFragment
. MainFragment
a des dispositions différentes pour l'orientation horizontale et verticale, et setRetainInstance()
est faux de sorte que la disposition peut changer. Cela signifie que lorsque l'orientation du dispositif est modifiée, les deux MainActivity
et MainFragment
sont complètement détruits et recréés.
séparément nous avons MyTask
(étendu de AsyncTask
) qui fait tout le travail. Nous ne pouvons pas le stocker dans MainFragment
parce que cela sera détruit, et Google a déprécié en utilisant quelque chose comme setRetainNonInstanceConfiguration()
. Ce n'est pas toujours disponible de toute façon et c'est un vilain piratage au mieux. Nous allons plutôt stocker MyTask
dans un autre fragment, un DialogFragment
appelé TaskFragment
. Ce fragment sera ont setRetainInstance()
définie à true pour que l'appareil tourne ce fragment n'est pas détruite, et MyTask
est conservé.
enfin, nous devons dire au TaskFragment
qui informer quand il est terminé, et nous le faisons en utilisant setTargetFragment(<the MainFragment>)
quand nous le créons. Lorsque le périphérique est mis en rotation et que le MainFragment
est détruit et qu'une nouvelle instance est créée, nous utilisons le FragmentManager
pour trouver le dialogue (basé sur sa balise) et faire setTargetFragment(<the new MainFragment>)
. C'est assez bien.
il y avait deux autres choses que j'avais besoin de faire: premièrement, annuler la tâche lorsque la boîte de dialogue est rejetée, et deuxièmement, mettre le message de rejet à null, sinon la boîte de dialogue est étrangement rejetée lorsque l'appareil est en rotation.
le code
Je ne vais pas énumérer les layouts, ils sont assez évidents et vous pouvez les trouver dans le téléchargement de projet ci-dessous.
activité principale
C'est assez simple. J'ai ajouté un rappel à cette activité pour qu'elle sache quand la tâche est terminée, mais vous n'en aurez peut-être pas besoin. Principalement je voulais juste montrer le mécanisme de rappel d'activité de fragment parce que c'est assez soigné et vous pourriez pas l'avoir vu avant.
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
c'est long mais ça en vaut la peine!
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, 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();
}
}
}
télécharger le projet exemple
voici le code source et L'APK . Désolé, L'ADT a insisté pour ajouter la bibliothèque de soutien avant de me laisser faire un projet. Je suis sûr que vous pouvez le supprimer.
j'ai récemment a publié un article décrivant comment gérer les modifications de configuration à l'aide de retenue Fragment
. Il résout le problème de la conservation d'un AsyncTask
au sein d'une modification de rotation bien.
Le TL;DR est d'utiliser de l'hôte de votre AsyncTask
à l'intérieur d'un Fragment
, appel setRetainInstance(true)
sur le Fragment
, et le rapport de la AsyncTask
progrès/résultats Activity
(ou à sa cible Fragment
, si vous choisissez d'utiliser l'approche décrite par @Timmmm) par le Fragment
conservé .
ma première suggestion est de éviter les AsyncTasks intérieurs , vous pouvez lire une question que j'ai posée à ce sujet et les réponses: Android: AsyncTask recommandations: cours privé ou cours public?
après ça j'ai commencé à utiliser non-inner and... maintenant, je vois BEAUCOUP d'avantages.
la seconde est, Gardez une référence de votre AsyncTask en cours d'exécution dans la classe Application
- http://developer.android.com/reference/android/app/Application.html
chaque fois que vous démarrez un AsyncTask, définissez-le sur l'Application et quand il est terminé, définissez-le à null.
quand un fragment/activité commence, vous pouvez vérifier si un AsyncTask est en cours d'exécution (en vérifiant s'il est nul ou non sur l'Application) et ensuite définir la référence à l'intérieur de ce que vous voulez (activité, fragment, etc afin que vous puissiez faire des callbacks).
This résoudra votre problème: Si vous avez seulement 1 AsyncTask en cours d'exécution à un moment déterminé, vous pouvez ajouter une référence simple:
AsyncTask<?,?,?> asyncTask = null;
autrement, avoir dans l'application un HashMap avec des références à eux.
le dialogue de progression peut suivre exactement le même principe.
j'ai trouvé une méthode pour utiliser AsyncTaskLoaders pour cela. Il est assez facile à utiliser et nécessite moins DE IMO..
fondamentalement, vous créez un AsyncTaskLoader comme ceci:
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;
}
}
puis dans votre activité qui utilise L'AsyncTaskLoader ci-dessus quand un bouton est cliqué:
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
}
}
cela semble gérer les changements d'orientation et votre tâche de fond se poursuivra pendant la rotation.
quelques points à noter:
- si dans onCreate vous reattachez à l'asynctaskloader vous serez rappelé dans onLoadFinished() avec le résultat précédent (même si vous aviez déjà été informé que la demande était complète). C'est en fait le bon comportement, la plupart du temps, mais parfois il peut être difficile à gérer. J'imagine qu'il y a plein de façons de gérer ça.ce que j'ai fait, c'est appeler loader.abandonner() dans onLoadFinished. Puis j'ai ajouté check in onCreate à seulement rattacher à la chargeuse si elle n'était pas déjà abandonnée. Si vous avez encore besoin des données résultantes, vous ne voudrez pas faire cela. Dans la plupart des cas, vous voulez les données.
j'ai plus de détails sur l'utilisation de ceci pour les appels http ici
j'ai créé une très petite bibliothèque de tâches open-source qui est fortement basée sur le Marshmallow AsyncTask
mais avec des fonctionnalités supplémentaires telles que:
- maintien automatique des tâches en fonction des changements de configuration;
- l'INTERFACE utilisateur de rappel (les auditeurs);
- ne redémarre pas ou n'annule pas la tâche lorsque l'appareil tourne (comme le feraient les chargeurs);
la bibliothèque utilise en interne Fragment
sans aucune interface utilisateur, qui est conservée lors des modifications de configuration ( setRetainInstance(true)
).
vous pouvez le trouver sur GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks
exemple le plus basique (version 0.2.0):
cet exemple conserve entièrement la tâche, en utilisant une quantité très limitée de code.
tâche:
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";
}
}
activité:
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();
}
}
mon approche est d'utiliser le modèle de conception de délégation, en général, nous pouvons isoler la logique d'affaires réelle (lire des données de l'internet ou de la base de données ou quoi que ce soit) D'AsyncTask (le délégateur) à BusinessDAO (le délégué), dans votre AysncTask.doInBackground () méthode, déléguer la tâche réelle à BusinessDAO, puis mettre en œuvre un mécanisme de processus unique dans BusinessDAO, de sorte que l'appel multiple à BusinessDAO.doSomething () va juste déclencher une tâche réelle en cours d'exécution à chaque fois et en attente de la tâche de résultat. L'idée est de conserver le délégué (C'est-à-dire le BusinessDAO) pendant le changement de configuration, plutôt que le délégateur (C'est-à-dire AsyncTask).
-
" créer/implémenter notre propre Application, le but est de créer/initialiser BusinessDAO ici, de sorte que le cycle de vie de notre BusinessDAO est l'application scoped, pas l'activité scoped, notez que vous devez changer AndroidManifest.xml pour utiliser 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; } }
-
notre activité/fragment existant est pour la plupart inchangé, encore mettre en œuvre AsyncTask comme une classe interne et impliquer AsyncTask.execute () from Activity/Fragement, la différence est maintenant AsyncTask déléguera la tâche réelle à BusinessDAO, donc pendant le changement de configuration, un second AsyncTask sera initialisé et exécuté, et appellera BusinessDAO.doSomething() deuxième temps, cependant, le deuxième appel à BusinessDAO.doSomething () ne déclenchera pas une nouvelle tâche en cours d'exécution, en attendant tâche en cours d'exécution pour terminer:
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. } } ... ... }
-
à l'Intérieur de BusinessDAO, de mettre en œuvre singleton processus de mécanisme, par exemple:
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; } } }
Je ne suis pas sûr à 100% si cela fonctionne, de plus, l'extrait de code d'échantillon devrait être considéré comme un pseudocode. J'essaie juste de te donner un indice du niveau du design. Toute rétroaction ou suggestion est la bienvenue et appréciée.
si quelqu'un trouve son chemin à ce fil, alors j'ai trouvé une approche propre était d'exécuter la tâche Async à partir d'un app.Service
(commencé avec START_STICKY) et puis sur recreate iterate sur les services en cours d'exécution pour savoir si le service (et donc la tâche async) est toujours en cours d'exécution;
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;
}
si c'est le cas, ajouter à nouveau le DialogFragment
(ou autre) et s'il ne s'agit pas de s'assurer que le dialogue a été rejeté.
pertinent si vous utilisez les bibliothèques v4.support.*
depuis (au moment de la rédaction) ils ont des problèmes avec la méthode setRetainInstance
et voir la pagination. de plus, en ne retenant pas l'instance, vous pouvez recréer votre activité en utilisant un ensemble différent de ressources (c.-à-d. une disposition de vue différente pour la nouvelle orientation)
vous pourriez faire de L'AsyncTask un champ statique. Si vous avez besoin d'un contexte, vous devez expédier votre contexte de l'application. Cela évitera les fuites de mémoire, sinon vous garderiez une référence à toute votre activité.
j'écris du code samepl pour résoudre ce problème
la première étape est de faire la classe d'Application:
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);
}
}
Dans 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 en activité:
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;
}
}
}
Lorsque l'activité les changements d'orientation de la variable mTask est inited à partir de l'app contexte. Lorsque la tâche est terminée, la variable est définie à null et à remove from memory.
ça me suffit.
Regardez ci-dessous l'exemple , comment utiliser le fragment retenu pour conserver la tâche de fond:
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;
}
}
}
}
Ont un look ici .
il existe une solution basée sur la solution Timmmm's .
mais je l'ai amélioré:
-
maintenant la solution est extensible - vous avez seulement besoin de prolonger
FragmentAbleToStartTask
-
vous pouvez continuer à exécuter plusieurs tâches en même temps.
Et à mon avis, c'est comme facile comme activité de démarrage pour le résultat et de recevoir le résultat
-
vous pouvez également arrêter une tâche en cours d'exécution et vérifier si une tâche particulière est en cours d'exécution
Désolé pour mon anglais