Frammenti Android.Conservazione di un AsyncTask durante la rotazione dello schermo o la modifica della configurazione
Domanda
Sto lavorando su un'app per smartphone/tablet, utilizzando un solo APK e caricando le risorse necessarie in base alle dimensioni dello schermo, la scelta di progettazione migliore sembrava utilizzare Fragments tramite ACL.
Questa app ha funzionato bene fino ad ora essendo solo basata sull'attività.Questa è una classe simulata di come gestisco AsyncTasks e ProgressDialogs nelle Attività in modo che funzionino anche quando lo schermo viene ruotato o si verifica una modifica della configurazione durante la comunicazione.
Non modificherò il manifest per evitare la ricreazione dell'attività, ci sono molte ragioni per cui non voglio farlo, ma principalmente perché i documenti ufficiali dicono che non è consigliato e finora ne ho fatto a meno, quindi per favore non consigliarlo itinerario.
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
}
}
}
}
Questo codice funziona bene, ho circa 10.000 utenti senza lamentarsi, quindi sembrava logico copiare semplicemente questa logica nel nuovo Fragment Based Design, ma, ovviamente, non funziona.
Ecco il 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
}
}
}
}
}
Non posso usare onRetainNonConfigurationInstance()
poiché deve essere chiamato dall'attività e non dal frammento, lo stesso vale getLastNonConfigurationInstance()
.Ho letto alcune domande simili qui senza risposta.
Capisco che potrebbe essere necessario un po' di lavoro per organizzare correttamente queste cose in frammenti, detto questo, vorrei mantenere la stessa logica di progettazione di base.
Quale sarebbe il modo corretto per mantenere AsyncTask durante una modifica della configurazione e, se è ancora in esecuzione, mostrare una progressDialog, tenendo in considerazione che AsyncTask è una classe interna al Fragment ed è il Fragment stesso che invoca AsyncTask.execute ()?
Soluzione
I frammenti possono effettivamente renderlo molto più facile. Basta usare il metodo frammento.setraininstance (booleano) per avere il tuo frammento istanza conservata attraverso le modifiche alla configurazione. Si noti che questo è la sostituzione raccomandata per Attività.onretainnonconfigurationinstance () <) < i documenti.
Se per qualche motivo non vuoi davvero usare un frammento mantenuto, ci sono altri approcci che puoi prendere. Si noti che ogni frammento ha un identificatore univoco restituito da frammento.getid () . Puoi anche scoprire se un frammento viene abbattuto per un cambiamento di configurazione attraverso frammento .getattività (). ischangingconfigurations () . Quindi, nel punto in cui decideresti di fermare il tuo asynctask (in Onstop () o OnDestroy () Molto probabilmente), potresti per esempio, controllare se la configurazione sta cambiando e se così lo attacca in un STATICO STATICO SOTTO L'IDENTIFICATORE E poi nel tuo ONCreate () o OnStart () Guarda per vedere se hai un asynctSak nell'array sparso disponibile.
Altri suggerimenti
Penso che ti piacerà il mio esempio estremamente completo e di funzionamento dettagliato di seguito.
- .
- funziona la rotazione e la finestra di dialogo sopravvive.
- È possibile annullare l'attività e la finestra di dialogo premendo il pulsante Indietro (se si desidera questo comportamento).
- utilizza frammenti.
- Il layout del frammento sotto l'attività cambia correttamente quando il dispositivo ruota.
- Esiste un codice sorgente completo download e un APK precompilato in modo da poter vedere se il comportamento è quello che vuoi.
Modifica
Come richiesto da Brad Larson ho riprodotto la maggior parte della soluzione collegata qui sotto. Inoltre, da quando l'ho pubblicato sono stato indicato da
AsyncTaskLoader
. Non sono sicuro che sia totalmente applicabile agli stessi problemi, ma dovresti controllarlo comunque.Utilizzo di
AsyncTask
con finestre di dialogo di avanzamento e rotazione del dispositivo.Una soluzione di lavoro!
Ho finalmente avuto tutto per lavorare. Il mio codice ha le seguenti caratteristiche:
- .
- un
Fragment
il cui layout cambia con l'orientamento. - un
AsyncTask
in cui puoi fare un po 'di lavoro. - un
DialogFragment
che mostra il progresso dell'attività in una barra di avanzamento (non solo uno spinner indeterminato). - La rotazione funziona senza interrompere l'attività o il licenziare la finestra di dialogo.
- Il pulsante posteriore licenzi la finestra di dialogo e cancella l'attività (è possibile modificare questo comportamento abbastanza facilmente però).
Non penso che quella combinazione di lavorabilità possa essere trovata in qualsiasi altro posto.
L'idea di base è la seguente. C'è una classe
MainActivity
che contiene un singolo frammento -MainFragment
.MainFragment
ha diversi layout per orientamento orizzontale e verticale esetRetainInstance()
è falso in modo che il layout possa cambiare. Ciò significa che quando l'orientamento del dispositivo viene modificato, siaMainActivity
cheMainFragment
sono completamente distrutti e ricreati.Separatamente abbiamo
MyTask
(esteso daAsyncTask
) che fa tutto il lavoro. Non possiamo memorizzarlo inMainFragment
perché verrà distrutto e Google ha deprecato usando qualcosa comesetRetainNonInstanceConfiguration()
. Non è sempre disponibile comunque ed è un brutto hack al meglio. Invece memorizzeremoMyTask
in un altro frammento, unDialogFragment
chiamatoTaskFragment
. questo frammento ha il generatore disetRetainInstance()
impostato su true, in quanto il dispositivo ruota di questo frammento non viene distrutto eMyTask
viene mantenuto.Infine abbiamo bisogno di dire al
TaskFragment
che informare quando è finito, e lo facciamo utilizzandosetTargetFragment(<the MainFragment>)
quando lo creiamo. Quando il dispositivo viene ruotato e ilMainFragment
viene distrutto e viene creata una nuova istanza, utilizziamo ilFragmentManager
per trovare la finestra di dialogo (in base al tag) e faresetTargetFragment(<the new MainFragment>)
. Questo è praticamente.C'erano altre due cose che dovevo fare: Prima annullare l'attività quando la finestra di dialogo viene respinta e Secondo impostare il messaggio di licenzitura su NULL, altrimenti la finestra di dialogo è stranamente licenziata quando il dispositivo viene ruotato.
il codice
Non elenco i layout, sono piuttosto ovvi e puoi trovarli nel download del progetto qui sotto.
Mainalty
Questo è piuttosto semplice. Ho aggiunto un callback in questa attività in modo che sa quando il compito è finito, ma non potresti averne bisogno. Principalmente volevo solo mostrare il meccanismo di callback dell'attività frammentario perché è abbastanza pulito e potresti non averlo visto prima.
.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
È lungo ma ne vale la pena!
.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(); } } }
Scarica il progetto di esempio
Ecco il
- un
Ho recentemente Pubblicato un articolo Descrivendo come gestire le modifiche di configurazione utilizzando Fragment
s mantenuti.Risolve il problema del mantenimento di un AsyncTask
attraverso un cambiamento di rotazione piacevolmente.
TL; DR deve utilizzare Host Your AsyncTask
Inside a Fragment
, chiama setRetainInstance(true)
sul Fragment
e riportano i progressi / Risultati del AsyncTask
Torna al suo Activity
(o è Target Fragment
, se si sceglie di utilizzare l'approccio descritto da @TIMMMM) attraverso il Fragment
mantenuto.
Il mio primo suggerimento è quello di Evita gli asynctasks interni , puoi leggere una domanda che ho chiesto informazioni su questa e le risposte: Android: AsynctSask Raccomandazioni: Classe privata o Classe pubblica?
Dopo aver iniziato a usare il non-interno e ... Ora vedo molti vantaggi.
Il secondo è, conservare un riferimento del tuo asynctCask in esecuzione nella classe Application
- http://developer.android.com/reference/android/app/application.html
Ogni volta che si avvia un asynctask, impostalo sull'applicazione e quando lo termina, impostalo su NULL.
Quando si avvia un frammento / attività è possibile verificare se è in esecuzione qualsiasi asynctsk (controllando se è nullo o meno sull'applicazione) e quindi impostare il riferimento all'interno di qualsiasi cosa tu voglia (attività, frammento ecc. Quindi puoi effettuare callback) .
Questo risolverà il tuo problema:
Se hai solo 1 asynctask in esecuzione in qualsiasi momento determinato è possibile aggiungere un riferimento semplice:
.
AsyncTask<?,?,?> asyncTask = null;
altro, avere nell'applicazione un hashmap con i riferimenti a loro.
La finestra di dialogo Progresso può seguire lo stesso identico principio.
Ho trovato un metodo per usare Asynctaskloader per questo. È piuttosto facile da usare e richiede meno in testa IMO ..
Fondamentalmente crei un asynctchloader come questo:
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;
}
}
.
Quindi nella tua attività che utilizza il suddetto asynctchaskloader quando si fa clic su un pulsante:
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
}
}
.
Questo sembra gestire l'orientamento cambia fine e il compito di sfondo continuerà durante la rotazione.
Alcune cose da notare:
- .
- Se in ONCREA hai riattaccato al Asynctaskloader che verrai richiamato in onloadFinited () con il risultato precedente (anche se ti è stato già detto che la richiesta era completa). Questo è in realtà un buon comportamento la maggior parte del tempo, ma a volte può essere complicato da gestire. Mentre immagino ci sono molti modi per gestire questo ciò che ho fatto è stato chiamato loader.abandon () in onloadfinished. Poi ho aggiunto il check-in OnCreate a Reatach solo al caricatore se non era già stato abbandonato. Se hai bisogno di nuovo i dati risultanti, non vorrai farlo. Nella maggior parte dei casi vuoi i dati.
Ho maggiori dettagli sull'utilizzo di questo per le chiamate http qui
Ho creato una minuscola libreria di attività di sfondo open source open source che è pesantemente basata sul AsyncTask
marshmallow ma con funzionalità aggiuntive come:
- .
- mantenendo automaticamente le attività attraverso le modifiche alla configurazione;
- Callback UI (ascoltatori);
- non si riavvia o annulla l'attività quando il dispositivo ruota (come fare i caricatori);
La biblioteca utilizza internamente un
Fragment
senza alcuna interfaccia utente, che viene mantenuta cambiamenti di configurazione Accross (setRetainInstance(true)
).Puoi trovarlo su github: https://github.com/neotech-software/ ASSUNZIONI ANDROID-ASSICURABILI
Most Esempio di base (versione 0.2.0):
Questo esempio conserva completamente l'attività, utilizzando una quantità di codice molto limitata.
attività:
.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"; } }
Attività:
.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(); } }
Il mio approccio consiste nell'utilizzare il modello di progettazione della delega, in generale, possiamo isolare la logica aziendale effettiva (leggere dati da Internet o database o altro) da AsyncTask (il delegante) a BusinessDAO (il delegato), nel metodo AysncTask.doInBackground() , delegare l'attività effettiva a BusinessDAO, quindi implementare un meccanismo di processo singleton in BusinessDAO, in modo che più chiamate a BusinessDAO.doSomething() attivino semplicemente un'attività effettiva in esecuzione ogni volta e in attesa del risultato dell'attività.L'idea è mantenere il delegato (cioèBusinessDAO) durante la modifica della configurazione, al posto del delegante (es.Attività asincrona).
Crea/implementa la nostra applicazione, lo scopo è creare/inizializzare BusinessDAO qui, in modo che il ciclo di vita del nostro BusinessDAO abbia come ambito l'applicazione e non l'attività. Tieni presente che è necessario modificare AndroidManifest.xml per utilizzare 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; } }
Le nostre attività/fragement esistenti sono per lo più invariate, implementano ancora AsyncTask come classe interna e coinvolgono AsyncTask.execute() da Activity/Fragement, la differenza ora è che AsyncTask delegherà l'attività effettiva a BusinessDAO, quindi durante la modifica della configurazione, un secondo AsyncTask verrà inizializzato ed eseguito e chiamerà BusinessDAO.doSomething() una seconda volta, tuttavia, la seconda chiamata a BusinessDAO.doSomething() non attiverà una nuova attività in esecuzione, ma attenderà invece il completamento dell'attività in esecuzione corrente:
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. } } ... ... }
All'interno di BusinessDAO, implementare il meccanismo del processo singleton, ad esempio:
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; } } }
Non sono sicuro al 100% che funzionerà, inoltre, lo snippet di codice di esempio dovrebbe essere considerato come pseudocodice.Sto solo cercando di darti qualche indizio dal livello di progettazione.Qualsiasi feedback o suggerimento è benvenuto e apprezzato.
Potresti rendere l'asynctask un campo statico.Se hai bisogno di un contesto, è necessario spedire il contesto dell'applicazione.Ciò eviterà perdite di memoria, altrimenti manterresti un riferimento alla tua intera attività.
Se qualcuno si trova a questo thread, ho trovato un approccio pulito è stato quello di eseguire il compito di async da un app.Service
(avviato con Start_sticky) e quindi su ricreare iterato sui servizi di esecuzione per scoprire se il servizio (e quindi asynccompito) è ancora in esecuzione;
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;
}
.
Se lo è, riaggiungere il DialogFragment
(o qualsiasi altra cosa) e se non è assicurato che la finestra di dialogo sia stata respinta.
Questo è particolarmente pertinente se si utilizzano le librerie v4.support.*
poiché (al momento della scrittura) hanno conosciuti problemi con il metodo setRetainInstance
e visualizzare il paging. Inoltre, non mantenendo l'istanza che puoiRicreare la tua attività utilizzando un insieme diverso di risorse (cioè un disposizione di vista diversa per il nuovo orientamento)
scrivo il codice con lo stesso codice per risolvere questo problema
Primo passo è la classe di applicazione:
public class TheApp extends Application {
private static TheApp sTheApp;
private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>();
@Override
public void onCreate() {
super.onCreate();
sTheApp = this;
}
public static TheApp get() {
return sTheApp;
}
public void registerTask(String tag, AsyncTask<?,?,?> task) {
tasks.put(tag, task);
}
public void unregisterTask(String tag) {
tasks.remove(tag);
}
public AsyncTask<?,?,?> getTask(String tag) {
return tasks.get(tag);
}
}
.
in androidmanifest.xml
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:name="com.example.tasktest.TheApp">
.
Codice in Attività:
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;
}
}
}
.
Quando l'orientamento delle attività cambia variabile MTASK è inutilizzata dal contesto di app.Quando l'attività è finita la variabile è impostata su null e rimuovere dalla memoria.
per me è abbastanza.
Dai un'occhiata al seguente esempio, come utilizzare il frammento mantenuto per conservare attività di sfondo:
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;
}
}
}
}
. Dai un'occhiata
C'è una soluzione basata su TIMMMM soluzione.
Ma lo ho migliorato:
- .
-
Ora la soluzione è estensibile - devi solo estendere
FragmentAbleToStartTask
-
Puoi continuare a eseguire diverse attività allo stesso tempo.
E a mio parere è facile come StartactivityForresult e ricevi risultati
-
Puoi anche interrompere un'attività in esecuzione e verificare se un'attività particolare è in esecuzione
scusa per il mio inglese