Android Fragments. Le maintien d'une AsyncTask au cours de la rotation de l'écran ou de la modification de la configuration
Je suis en train de travailler sur un Smartphone /Tablette application, en utilisant un seul APK, et le chargement des ressources nécessaires en fonction de la taille de l'écran, le meilleur choix de conception qui semblait être à l'aide de Fragments via les ACL.
Cette application a été fonctionne bien jusqu'à maintenant uniquement basée sur l'activité. C'est une maquette de la classe de la façon dont je gère AsyncTasks et ProgressDialogs dans les Activités afin de les faire travailler, même lorsque l'écran est mis en rotation ou un changement de configuration se produit en milieu de communication.
Je ne vais pas changer le manifeste pour éviter de loisirs de l'Activité, il existe de nombreuses raisons pour lesquelles je ne veux pas le faire, mais surtout parce que les docs officielles dire qu'il n'est pas recommandé, et j'ai réussi sans cette mesure, de sorte s'il vous plaît ne recommandons que la 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 très bien, j'ai autour de 10.000 utilisateurs, sans plainte, il m'a donc semblé logique de simplement copier cette logique dans le nouveau Fragment de Base de Conception, mais, bien sûr, il ne fonctionne pas.
Ici est la 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'elle doit être appelée à partir de l'Activité et non le Fragment, en va de même avec getLastNonConfigurationInstance()
. J'ai lu quelques questions similaires ici sans réponse.
Je comprends qu'il pourrait exiger de travailler autour pour obtenir ce genre de choses bien organisé dans les fragments, cela étant dit, je tiens à maintenir la même conception de base de la logique.
Ce serait le bon moyen de conserver les AsyncTask lors d'un changement de configuration, et s'il est toujours en cours d'exécution, montrent une progressDialog, en prenant en considération le fait que l'AsyncTask est un intérieur de classe pour le Fragment et c'est le Fragment lui-même qui invoque l'AsyncTask.execute()?
- Peut-être le fil sur comment gérer le changement de configuration avec AsyncTask peut aider
- associer AsyncTask avec une application sur le cycle de vie..donc peut reprendre lorsque l'activité recrée
- Découvrez mon post sur ce sujet: Gestion des Changements de Configuration avec
Fragment
s
Vous devez vous connecter pour publier un commentaire.
Fragments pouvez effectivement faire beaucoup plus facile. Suffit d'utiliser la méthode Le Fragment.setRetainInstance(boolean) avoir votre fragment exemple conservé à travers les modifications de la configuration. Notez que cela est recommandé de remplacement pour De l'activité.onRetainnonConfigurationInstance() dans les docs.
Si pour quelque raison vous ne voulez vraiment pas à utiliser un fragment conservé, il existe d'autres approches que vous pouvez prendre. Notez que chaque fragment est un identificateur unique qui est renvoyé par Le Fragment.getId(). Vous pouvez également savoir si un fragment est en train d'être démoli pour une config de changement à travers Le Fragment.getActivity().isChangingConfigurations(). Donc, au point où vous décidez d'arrêter votre AsyncTask (en onStop() ou onDestroy() le plus probable), vous pouvez par exemple vérifier si la configuration est en train de changer et le coller dans un statique SparseArray sous le fragment de l'identifiant, puis dans votre onCreate() ou onStart() regardez pour voir si vous avez une AsyncTask dans le tableau fragmenté disponibles.
onPostExecute
méthode devra encore attendre avant d'être finalement traitées par le thread principal message de la file d'attente.Je pense que vous apprécierez mon très complet et de travail exemple détaillé ci-dessous.
Modifier
Comme demandé par Brad Larson j'ai reproduit la plupart de la solution ci-dessous. Aussi depuis que j'ai posté, j'ai été rappelé à
AsyncTaskLoader
. Je ne suis pas sûr que il est totalement applicable aux mêmes problèmes, mais vous devriez vérifier de toute façon.À l'aide de
AsyncTask
avec les progrès et les boîtes de dialogue de l'appareil de rotation.Une solution de travail!
J'ai finalement tout s'est mis au travail. Mon code a les caractéristiques suivantes:
Fragment
dont les modifications de mise en page à l'orientation.AsyncTask
dans laquelle vous pouvez faire un peu de travail.DialogFragment
qui montre la progression de la tâche dans une barre de progression (et pas seulement pour une période indéterminée spinner).Je ne pense pas que la combinaison de workingness peuvent être trouvés n'importe où ailleurs.
L'idée de base est comme suit. Il y a un
MainActivity
classe qui contient un fragment unique -MainFragment
.MainFragment
a différentes mises en page horizontale et verticale de l'orientation, etsetRetainInstance()
est faux de sorte que la mise en page peut changer. Cela signifie que lorsque le dispositif d'orientation est modifiée, à la foisMainActivity
etMainFragment
sont complètement détruits et recréés.Séparément, nous avons
MyTask
(étendue deAsyncTask
) qui fait tout le travail. Nous ne pouvons pas stocker dansMainFragment
parce que ce sera détruit, et Google a retiré de l'aide de quelque chose commesetRetainNonInstanceConfiguration()
. Ce n'est pas toujours à la disposition de toute façon et est un vilain hack au mieux. Au lieu de cela, nousMyTask
dans un autre fragment, unDialogFragment
appeléTaskFragment
. Ce fragment sera ontsetRetainInstance()
définie à true pour que l'appareil tourne ce fragment n'est pas détruite, etMyTask
est conservé.Enfin, nous devons dire au
TaskFragment
qui informer quand il est terminé, et nous le faisons à l'aide desetTargetFragment(<the MainFragment>)
lorsque nous de la créer. Lors de la rotation de l'appareil et leMainFragment
est détruite et une nouvelle instance est créée, nous utilisons laFragmentManager
pour trouver la boîte de dialogue (basé sur son étiquette) et nesetTargetFragment(<the new MainFragment>)
. C'est assez bien.Il y a deux autres choses que je devais faire: d'abord annuler la tâche lorsque la boîte de dialogue est fermée, et le deuxième ensemble de la rejeter message à null, sinon le dialogue est bizarrement rejeté lors de la rotation de l'appareil.
Le code
Je ne vais pas lister les mises en page, ils sont assez évidentes, et vous pouvez les trouver dans le projet de téléchargement ci-dessous.
MainActivity
C'est assez simple. J'ai ajouté un rappel dans cette activité, de sorte qu'il sait quand la tâche est terminée, mais vous ne pourriez pas besoin de cela. Surtout, je voulais juste montrer le fragment d'activité de mécanisme de rappel parce que c'est très soignée et vous pourriez ne pas avoir vu avant.
MainFragment
C'est long, mais ça valait le coup!
TaskFragment
MyTask
Télécharger un exemple de projet
Ici est la le code source et l'APK. Désolé, l'ADT a insisté sur l'ajout de la bibliothèque de prise en charge avant de me laisser faire un projet. Je suis sûr que vous pouvez le supprimer.
DialogFragment
, parce qu'il a des éléments d'INTERFACE utilisateur qui contiennent des références à l'ancien contexte. Au lieu de cela, j'avais storeAsyncTask
dans un autre vide fragment, et de définirDialogFragment
comme sa cible.onCreateView()
est appelée de nouveau? L'ancienmProgressBar
sera remplacée par une nouvelle au moins.mProgressBar = null;
dansonDestroyView()
si vous voulez être certain. La singularité de la méthode peut être une bonne idée, mais il va augmenter la complexité du code, même plus!mTask.execute()
àMainFragment.onClick()
. Vous pouvez également autoriser les paramètres à passer danssetTask()
ou même de les stocker dansMyTask
lui-même. Je ne suis pas exactement sûr de ce que votre première question, mais peut-être que vous souhaitez utiliserTaskFragment.getTargetFragment()
? Je suis assez sûr qu'elle fonctionne à l'aide d'unViewPager
. MaisViewPagers
ne sont pas très bien compris ou documentés, alors bonne chance! Souvenez-vous de votre fragment n'est pas créé jusqu'à ce qu'il soit visible pour la première fois.IllegalStateException
en utilisantcommitAllowingStateLoss()
probablement (voir la ligne où j'ai deux alternatives -commit()
oushow()
). Vous pouvez poser vos questions en tant que nouvelle question - alors vous pouvez poster votre code.mFM = getActivity().getSupportFragmentManager()
J'ai récemment publié un article décrivant comment gérer les modifications de configuration à l'aide de retenue
Fragment
s. Il résout le problème de la conservation d'unAsyncTask
à travers une modification de rotation bien.Le TL;DR est d'utiliser de l'hôte de votre
AsyncTask
à l'intérieur d'unFragment
, appelsetRetainInstance(true)
sur leFragment
, et le rapport de laAsyncTask
's progrès/résultats c'estActivity
(ou à sa cibleFragment
, si vous choisissez d'utiliser l'approche décrite par @Timmmm) par le biais de la retenueFragment
.onAttach
etonDetach
, qu'il sera mieux si à l'intérieur deTaskFragment
, il suffit d'appelergetActivity
chaque fois que nous devons tirer un rappel. (En cochant instaceofTaskCallbacks
)getActivity
? Comme quand je parle de Google officiel exemple developer.android.com/guide/components/fragments.html , ils ont également été en utilisant le vôtre chemin.getActivity
. Alors, j'ai pensé que je pourrais être en train de faire une mauvaise choses, après avoir regardé votre code...onAttach()
etonDetach()
si je pouvais éviter de devoir constamment le casting de l'activité deTaskCallbacks
à chaque fois que je voulais l'utiliser.Ma première suggestion est de éviter intérieure AsyncTasks, vous pouvez lire une question que j'ai demandé à ce sujet et les réponses: Android: AsyncTask recommandations: cours privé ou public de la classe?
Après que j'ai commencé à l'aide de non-intérieure et... maintenant, je vois BEAUCOUP d'avantages.
La seconde est, de conserver une référence à l'exécution des AsyncTask dans le
Application
Classe - http://developer.android.com/reference/android/app/Application.htmlChaque fois que vous démarrez une AsyncTask, réglez-le sur l'Application et quand il a finit il mis à null.
Lorsqu'un fragment/le début de l'activité, vous pouvez vérifier si tout AsyncTask est en cours d'exécution (en vérifiant si elle est null ou non sur l'Application), puis définissez la référence à l'intérieur de tout ce que vous voulez (activité, fragment etc, donc vous pouvez faire les rappels).
Cela permettra de résoudre votre problème:
Si vous avez seulement 1 AsyncTask cours d'exécution à tout moment déterminé à l'avance, vous pouvez ajouter une simple référence:
Les autres, dans l'Application d'une table de hachage avec des références à eux.
La boîte de dialogue de progression peut suivre exactement le même principe.
IntentService
Je suis venu avec une méthode d'utilisation de AsyncTaskLoaders pour cela. Il est assez facile à utiliser et nécessite moins de frais généraux de l'OMI..
Fondamentalement, vous créez un AsyncTaskLoader comme ceci:
Puis dans votre activité qui utilise le ci-dessus AsyncTaskLoader quand on clique sur un bouton:
Cela semble pour gérer les changements d'orientation de l'amende et votre tâche en arrière-plan se poursuivra au cours de la rotation.
Quelques choses à noter:
J'ai plus de détails sur l'utilisation de ce pour http appels ici
getSupportLoaderManager().getLoader(0);
ne retourne pas null (parce que le chargeur avec cette id, 0, n'existe pas encore)?J'ai créé un très petit open-source en tâche de fond de la bibliothèque qui est fortement basée sur la Guimauve
AsyncTask
mais avec des fonctionnalités supplémentaires telles que:La bibliothèque utilise en interne une
Fragment
sans interface utilisateur, qui est conservé à travers les modifications de la configuration (setRetainInstance(true)
).Vous pouvez le trouver sur GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks
La plupart des exemple de base (version 0.2.0):
Cet exemple entièrement conserve la tâche, à l'aide d'une quantité très limitée de code.
Tâche:
Activité:
Mon approche consiste à utiliser la délégation modèle de conception, en général, on peut séparer la logique métier (lire les données à partir d'internet ou de la base de données ou que ce soit) à partir de AsyncTask (le délégataire) à BusinessDAO (le délégué), dans votre AysncTask.doInBackground() la méthode, déléguer la tâche à BusinessDAO, puis de mettre en œuvre un singleton processus de mécanisme dans BusinessDAO, de sorte que plusieurs appels à BusinessDAO.doSomething() sera juste déclencher un réel de l'exécution de chaque tâche et de temps d'attente pour le résultat de la tâche. L'idée est de retenir la délégué (c'est à dire BusinessDAO) lors de la modification de la configuration, au lieu de le délégataire (c'est à dire AsyncTask).
Créer/mettre en Œuvre notre propre Application, le but est de créer/initialiser BusinessDAO ici, de sorte que notre BusinessDAO du cycle de vie de la demande est portée, pas d'activité étendue note que vous avez besoin de changer AndroidManifest.xml pour utiliser MyApplication:
Notre Activité existante/Fragement sont essentiellement le même, toujours mettre en œuvre des AsyncTask intérieur de la classe et de les impliquer AsyncTask.execute() de l'Activité/Fragement, la différence maintenant est AsyncTask de déléguer la tâche à BusinessDAO, tant lors de la modification de la configuration, un deuxième AsyncTask sera initialisé et exécuté, et l'appel BusinessDAO.doSomething() deuxième temps, cependant, le deuxième appel à BusinessDAO.doSomething() ne va pas déclencher une nouvelle tâche en cours d'exécution, au lieu de cela, en attente pour le courant de l'exécution de la tâche pour terminer:
À l'intérieur de BusinessDAO, de mettre en œuvre singleton processus de mécanisme, par exemple:
Je ne suis pas sûr à 100% si cela fonctionne, en outre, l'échantillon extrait de code doivent être considérées comme des pseudo-code. Je suis juste essayer de vous donner une indication de niveau de la conception. Tous les commentaires ou suggestions sont les bienvenus et appréciés.
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 permettra d'éviter les fuites de mémoire, sinon, on aurait de conserver une référence à l'ensemble de votre activité.
Si quelqu'un trouve ce thread, j'ai trouvé un propre approche a été pour exécuter la tâche Asynchrone à partir d'un
app.Service
(commencé avec START_STICKY) et puis recréer itérer sur les services en cours d'exécution pour savoir si le service (et donc async task) est toujours en cours d'exécution;Si elle l'est, rajouter les
DialogFragment
(ou autre) et si elle n'est pas d'assurer le dialogue a été rejeté., C'est particulièrement pertinent si vous utilisez le
v4.support.*
bibliothèques depuis (au moment de la rédaction), ils connaissent des problèmes avec lasetRetainInstance
méthode et d'afficher la pagination. En outre, par le fait de ne pas conserver l'instance, vous pouvez recréer votre activité à l'aide d'un autre ensemble de ressources (c'est à dire d'un point de vue différent de mise en page pour la nouvelle orientation)J'écris samepl code pour résoudre ce problème
Première étape est de faire de la classe d'Application:
Dans AndroidManifest.xml
Code de l'activité:
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 variable est définie sur null et les supprimer de la mémoire.
Pour moi suffisant.
Ont un coup d'oeil à l'exemple ci-dessous , comment utiliser le fragment conservé de conserver en tâche de fond:
Ont un look ici.
Il y a une solution basée sur Timmmm de l' solution.
Mais j'ai amélioré:
Maintenant la solution est extensible - vous avez seulement besoin d'étendre
FragmentAbleToStartTask
- Vous en mesure de continuer d'exécuter plusieurs tâches en même temps.
Et à mon avis, c'est aussi simple que startActivityForResult et recevoir le résultat
Vous pouvez également interrompre une tâche en cours d'exécution et de vérifier si la tâche est en cours d'exécution
Désolé pour mon anglais