Ich arbeite an einer Smartphone / Tablet-App, verwende nur eine APK und lade je nach Bildschirmgröße die erforderlichen Ressourcen. Die beste Wahl für das Design schien die Verwendung von Fragmenten über die ACL zu sein.
Diese App hat bisher einwandfrei funktioniert und ist nur aktivitätsbasiert. Dies ist eine Scheinklasse, wie ich mit AsyncTasks und ProgressDialogs in den Aktivitäten umgehe, damit sie auch dann funktionieren, wenn der Bildschirm gedreht wird oder während der Kommunikation eine Konfigurationsänderung auftritt.
Ich werde das Manifest nicht ändern, um eine Neuerstellung der Aktivität zu vermeiden. Es gibt viele Gründe, warum ich es nicht tun möchte, aber hauptsächlich, weil die offiziellen Dokumente sagen, dass es nicht empfohlen wird und ich es bis jetzt ohne es geschafft habe, also empfehlen Sie es bitte nicht 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
}
}
}
}
Dieser Code funktioniert einwandfrei, ich habe ungefähr 10.000 Benutzer ohne Beanstandung, daher schien es logisch, diese Logik einfach in das neue fragmentbasierte Design zu kopieren, aber natürlich funktioniert sie nicht.
Hier ist das 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
}
}
}
}
}
Ich kann es nicht verwenden, onRetainNonConfigurationInstance()
da es von der Aktivität und nicht vom Fragment aufgerufen werden muss, das gleiche gilt für getLastNonConfigurationInstance()
. Ich habe hier einige ähnliche Fragen ohne Antwort gelesen.
Ich verstehe, dass es einige Umarbeitungen erfordern könnte, um dieses Zeug richtig in Fragmenten zu organisieren. Abgesehen davon möchte ich die gleiche grundlegende Designlogik beibehalten.
Was wäre der richtige Weg, um die AsyncTask während einer Konfigurationsänderung beizubehalten und wenn sie noch ausgeführt wird, einen progressDialog anzuzeigen, wobei zu berücksichtigen ist, dass die AsyncTask eine innere Klasse des Fragments ist und das Fragment selbst die AsyncTask.execute aufruft ()?
Fragment
sAntworten:
Fragmente können dies tatsächlich viel einfacher machen. Verwenden Sie einfach die Methode Fragment.setRetainInstance (boolean) , um Ihre Fragmentinstanz über Konfigurationsänderungen hinweg beizubehalten. Beachten Sie, dass dies der empfohlene Ersatz für Activity.onRetainnonConfigurationInstance () in den Dokumenten ist.
Wenn Sie aus irgendeinem Grund wirklich kein beibehaltenes Fragment verwenden möchten, können Sie andere Ansätze wählen. Beachten Sie, dass jedes Fragment eine eindeutige Kennung hat, die von Fragment.getId () zurückgegeben wird . Über Fragment.getActivity (). IsChangingConfigurations () können Sie auch herausfinden, ob ein Fragment für eine Konfigurationsänderung abgerissen wird . An dem Punkt, an dem Sie Ihre AsyncTask stoppen möchten (höchstwahrscheinlich in onStop () oder onDestroy ()), können Sie beispielsweise überprüfen, ob sich die Konfiguration ändert, und sie in einem statischen SparseArray unter der Kennung des Fragments speichern. Überprüfen Sie dann in onCreate () oder onStart (), ob im spärlichen Array eine AsyncTask verfügbar ist.
quelle
onPostExecute
Methode dies dennoch tun Warten Sie, bevor Sie schließlich von der Nachrichtenwarteschlange des Hauptthreads verarbeitet werden.Ich denke, Sie werden mein äußerst umfassendes und funktionierendes Beispiel genießen, das unten aufgeführt ist.
Bearbeiten
Wie von Brad Larson angefordert, habe ich den größten Teil der unten aufgeführten verknüpften Lösung reproduziert. Auch seit ich es gepostet habe, wurde ich darauf hingewiesen
AsyncTaskLoader
. Ich bin mir nicht sicher, ob es für dieselben Probleme vollständig anwendbar ist, aber Sie sollten es trotzdem überprüfen.Verwenden
AsyncTask
mit Fortschrittsdialogen und Gerätedrehung.Eine funktionierende Lösung!
Ich habe endlich alles zum Arbeiten. Mein Code hat folgende Funktionen:
Fragment
dessen Layout sich mit der Ausrichtung ändert.AsyncTask
in dem Sie etwas arbeiten können.DialogFragment
das den Fortschritt der Aufgabe in einem Fortschrittsbalken anzeigt (nicht nur ein unbestimmter Spinner).Ich glaube nicht, dass eine Kombination aus Arbeitsfähigkeit irgendwo anders zu finden ist.
Die Grundidee ist wie folgt. Es gibt eine
MainActivity
Klasse, die ein einzelnes Fragment enthält -MainFragment
.MainFragment
hat unterschiedliche Layouts für die horizontale und vertikale Ausrichtung undsetRetainInstance()
ist falsch, damit sich das Layout ändern kann. Dies bedeutet , dass , wenn die Vorrichtung Orientierung geändert wird, die beideMainActivity
undMainFragment
werden vollständig zerstört und neu erstellt.Separat haben wir
MyTask
(erweitert vonAsyncTask
) die die ganze Arbeit erledigt. Wir können es nicht speichern,MainFragment
da dies zerstört wird und Google mit so etwas wie veraltet istsetRetainNonInstanceConfiguration()
. Das ist sowieso nicht immer verfügbar und bestenfalls ein hässlicher Hack. Stattdessen werden wirMyTask
in einem anderen Fragment speichern , einemDialogFragment
aufgerufenenTaskFragment
. Dieses Fragment wird hatsetRetainInstance()
auf true gesetzt, so dass das Gerät dieses Fragment dreht nicht zerstört wird, undMyTask
wird beibehalten.Schließlich müssen
TaskFragment
wir demjenigen mitteilen , der informiert werden soll, wenn es fertig ist, und das tun wir,setTargetFragment(<the MainFragment>)
wenn wir es erstellen. Wenn das Gerät gedreht undMainFragment
zerstört wird und eine neue Instanz erstellt wird, verwenden wir dasFragmentManager
, um den Dialog (basierend auf seinem Tag) zu finden und zu tunsetTargetFragment(<the new MainFragment>)
. Das wars so ziemlich.Ich musste noch zwei weitere Dinge tun: erstens die Aufgabe abbrechen, wenn der Dialog geschlossen wird, und zweitens die Entlassungsnachricht auf null setzen, andernfalls wird der Dialog seltsamerweise geschlossen, wenn das Gerät gedreht wird.
Der Code
Ich werde die Layouts nicht auflisten, sie sind ziemlich offensichtlich und Sie finden sie im Projekt-Download unten.
Hauptaktivität
Das ist ziemlich einfach. Ich habe dieser Aktivität einen Rückruf hinzugefügt, damit sie weiß, wann die Aufgabe abgeschlossen ist, aber das brauchen Sie möglicherweise nicht. Hauptsächlich wollte ich nur den Rückrufmechanismus für Fragmentaktivitäten zeigen, weil er ziemlich ordentlich ist und Sie ihn vielleicht vorher noch nicht gesehen haben.
MainFragment
Es ist lang, aber es lohnt sich!
TaskFragment
Meine Aufgabe
Laden Sie das Beispielprojekt herunter
Hier ist der Quellcode und die APK . Entschuldigung, das ADT bestand darauf, die Support-Bibliothek hinzuzufügen, bevor ich ein Projekt erstellen konnte. Ich bin sicher, Sie können es entfernen.
quelle
DialogFragment
, da er UI-Elemente enthält, die Verweise auf den alten Kontext enthalten. Stattdessen würde ichAsyncTask
in einem anderen leeren Fragment speichern undDialogFragment
als Ziel festlegen .onCreateView()
erneut aufgerufen wird? Das altemProgressBar
wird mindestens mit einem neuen überschrieben.mProgressBar = null;
in ,onDestroyView()
wenn Sie zusätzliche sicher sein wollen. Die Methode von Singularity mag eine gute Idee sein, aber sie erhöht die Codekomplexität noch mehr!mTask.execute()
zuMainFragment.onClick()
. Alternativ können Sie die Übergabe von Parametern zulassensetTask()
oder diese sogar inMyTask
sich selbst speichern . Ich bin mir nicht ganz sicher, was Ihre erste Frage bedeutet, aber vielleicht möchten Sie sie verwendenTaskFragment.getTargetFragment()
? Ich bin mir ziemlich sicher, dass es mit a funktionieren wirdViewPager
. AberViewPagers
nicht sehr gut verstanden oder dokumentiert, also viel Glück! Denken Sie daran, dass Ihr Fragment erst erstellt wird, wenn es beim ersten Mal sichtbar ist.Ich habe kürzlich einen Artikel veröffentlicht, in dem beschrieben wird, wie Konfigurationsänderungen mit beibehaltenen
Fragment
s behandelt werden. Es löst das Problem,AsyncTask
eine Änderung über eine Rotation hinweg gut beizubehalten.Der TL; DR ist die Verwendung Host Ihr
AsyncTask
innen einFragment
, AnrufsetRetainInstance(true)
auf dasFragment
, und berichtet über dieAsyncTask
‚Fortschritte / Ergebnisse geht es zurückActivity
(oder das ZielFragment
, wenn Sie den Ansatz von @Timmmm beschrieben verwenden) durch die beibehaltenFragment
.quelle
onAttach
undonDetach
, ist es besser, wennTaskFragment
wir drinnen sind , rufen wir einfach an,getActivity
wann immer wir einen Rückruf auslösen müssen. (Durch Überprüfung instaceofTaskCallbacks
)onAttach()
undonDetach()
so konnte ich vermeiden, die AktivitätTaskCallbacks
jedes Mal, wenn ich sie verwenden wollte, ständig zu übertragen.Mein erster Vorschlag ist, innere AsyncTasks zu vermeiden . Sie können eine Frage lesen, die ich dazu gestellt habe, und die Antworten: Android: AsyncTask-Empfehlungen: Privatklasse oder öffentliche Klasse?
Danach fing ich an, nicht-inner zu verwenden und ... jetzt sehe ich VIELE Vorteile.
Die zweite Möglichkeit besteht darin, eine Referenz Ihrer ausgeführten AsyncTask in der
Application
Klasse zu speichern - http://developer.android.com/reference/android/app/Application.htmlWenn Sie eine AsyncTask starten, setzen Sie sie in der Anwendung und setzen Sie sie nach Abschluss auf null.
Wenn ein Fragment / eine Aktivität gestartet wird, können Sie überprüfen, ob eine AsyncTask ausgeführt wird (indem Sie überprüfen, ob sie in der Anwendung null ist oder nicht), und dann die Referenz im Inneren auf das festlegen, was Sie möchten (Aktivität, Fragment usw., damit Sie Rückrufe durchführen können).
Dies löst Ihr Problem: Wenn zu einem bestimmten Zeitpunkt nur 1 AsyncTask ausgeführt wird, können Sie eine einfache Referenz hinzufügen:
Andernfalls haben Sie in der Anwendung eine HashMap mit Verweisen darauf.
Der Fortschrittsdialog kann genau dem gleichen Prinzip folgen.
quelle
Ich habe mir eine Methode ausgedacht, um AsyncTaskLoaders dafür zu verwenden. Es ist ziemlich einfach zu bedienen und erfordert weniger Overhead IMO ..
Grundsätzlich erstellen Sie einen AsyncTaskLoader wie folgt:
Dann in Ihrer Aktivität, die den obigen AsyncTaskLoader verwendet, wenn auf eine Schaltfläche geklickt wird:
Dies scheint Orientierungsänderungen gut zu handhaben und Ihre Hintergrundaufgabe wird während der Rotation fortgesetzt.
Ein paar Dinge zu beachten:
Ich habe weitere Details zur Verwendung für http-Anrufe hier
quelle
getSupportLoaderManager().getLoader(0);
nicht null zurückgibt (weil der Loader mit dieser ID 0 noch nicht existiert)?Ich habe eine sehr kleine Open-Source-Hintergrundaufgabenbibliothek erstellt, die stark auf dem Marshmallow basiert,
AsyncTask
aber zusätzliche Funktionen wie:Die Bibliothek verwendet intern eine
Fragment
Benutzeroberfläche ohne Benutzeroberfläche, die bei Konfigurationsänderungen beibehalten wird (setRetainInstance(true)
).Sie finden es auf GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks
Das einfachste Beispiel (Version 0.2.0):
In diesem Beispiel wird die Aufgabe mit einer sehr begrenzten Menge an Code vollständig beibehalten.
Aufgabe:
Aktivität:
quelle
Mein Ansatz ist die Verwendung des Delegationsentwurfsmusters. Im Allgemeinen können wir die tatsächliche Geschäftslogik (Daten aus dem Internet oder der Datenbank oder was auch immer lesen) von AsyncTask (dem Delegator) bis BusinessDAO (dem Delegaten) in Ihrer AysncTask.doInBackground () -Methode isolieren Delegieren Sie die eigentliche Aufgabe an BusinessDAO und implementieren Sie dann einen Singleton-Prozessmechanismus in BusinessDAO, sodass bei mehreren Aufrufen von BusinessDAO.doSomething () nur eine tatsächliche Aufgabe ausgelöst wird, die jedes Mal ausgeführt wird und auf das Ergebnis der Aufgabe wartet. Die Idee ist, den Delegaten (dh BusinessDAO) während der Konfigurationsänderung anstelle des Delegators (dh AsyncTask) beizubehalten.
Erstellen / Implementieren unserer eigenen Anwendung. Der Zweck besteht darin, BusinessDAO hier zu erstellen / zu initialisieren, sodass der Lebenszyklus unseres BusinessDAO anwendungsbezogen und nicht aktivitätsbezogen ist. Beachten Sie, dass Sie AndroidManifest.xml ändern müssen, um MyApplication verwenden zu können:
Unsere vorhandenen Aktivitäten / Fragments sind größtenteils unverändert. Sie implementieren AsyncTask weiterhin als innere Klasse und beziehen AsyncTask.execute () aus Activity / Fragement ein. Der Unterschied besteht nun darin, dass AsyncTask die eigentliche Aufgabe an BusinessDAO delegiert. Während der Konfigurationsänderung wird also eine zweite AsyncTask ausgeführt wird initialisiert und ausgeführt und ruft BusinessDAO.doSomething () zum zweiten Mal auf. Der zweite Aufruf von BusinessDAO.doSomething () löst jedoch keine neue laufende Aufgabe aus, sondern wartet auf den Abschluss der aktuell ausgeführten Aufgabe:
Implementieren Sie in BusinessDAO einen Singleton-Prozessmechanismus, zum Beispiel:
Ich bin nicht 100% sicher, ob dies funktionieren wird. Außerdem sollte das Beispielcode-Snippet als Pseudocode betrachtet werden. Ich versuche nur, Ihnen einen Hinweis von der Designebene zu geben. Feedback oder Vorschläge sind willkommen und willkommen.
quelle
Sie können die AsyncTask zu einem statischen Feld machen. Wenn Sie einen Kontext benötigen, sollten Sie Ihren Anwendungskontext versenden. Dadurch werden Speicherverluste vermieden, da Sie sonst einen Verweis auf Ihre gesamte Aktivität behalten.
quelle
Wenn jemand den Weg zu diesem Thread findet, habe ich festgestellt, dass ein sauberer Ansatz darin besteht, die Async-Task von einem
app.Service
(mit START_STICKY gestartet) aus auszuführen und dann beim erneuten Erstellen über die ausgeführten Services zu iterieren, um herauszufinden, ob der Service (und damit die Async-Task) noch vorhanden ist Laufen;Wenn dies der Fall ist, fügen Sie das
DialogFragment
(oder was auch immer) erneut hinzu, und wenn nicht sichergestellt ist, dass der Dialog geschlossen wurde.Dies ist besonders relevant, wenn Sie die
v4.support.*
Bibliotheken verwenden, da sie (zum Zeitpunkt des Schreibens) Probleme mit dersetRetainInstance
Methode und dem Anzeigen von Paging haben. Wenn Sie die Instanz nicht beibehalten, können Sie Ihre Aktivität mit anderen Ressourcen neu erstellen (dh mit einem anderen Ansichtslayout für die neue Ausrichtung).quelle
Ich schreibe samepl Code, um dieses Problem zu lösen
Der erste Schritt besteht darin, die Anwendungsklasse zu erstellen:
In AndroidManifest.xml
Code in Aktivität:
Wenn sich die Aktivitätsorientierung ändert, wird die Variable mTask aus dem App-Kontext gestartet. Wenn die Aufgabe beendet ist, wird die Variable auf null gesetzt und aus dem Speicher entfernt.
Für mich ist es genug.
quelle
Schauen Sie sich das folgende Beispiel an, wie Sie das beibehaltene Fragment verwenden, um die Hintergrundaufgabe beizubehalten:
quelle
Schauen Sie hier .
Es gibt eine Lösung, die auf der Lösung von Timmmm basiert .
Aber ich habe es verbessert:
Jetzt ist die Lösung erweiterbar - Sie müssen nur noch erweitern
FragmentAbleToStartTask
Sie können mehrere Aufgaben gleichzeitig ausführen.
Und meiner Meinung nach ist es so einfach wie startActivityForResult und Ergebnis zu erhalten
Sie können auch eine laufende Aufgabe stoppen und prüfen, ob eine bestimmte Aufgabe ausgeführt wird
Entschuldigung für mein Englisch
quelle