Hier ist ein weiteres Beispiel für eine AsyncTask, mit der a Fragment
zur Änderung der Laufzeitkonfiguration (z. B. wenn der Benutzer den Bildschirm dreht) verwendet wird setRetainInstance(true)
. Ein bestimmter (regelmäßig aktualisierter) Fortschrittsbalken wird ebenfalls gezeigt.
Das Beispiel basiert teilweise auf den offiziellen Dokumenten Beibehalten eines Objekts während einer Konfigurationsänderung .
In diesem Beispiel ist die Arbeit, die einen Hintergrund-Thread erfordert, das bloße Laden eines Bildes aus dem Internet in die Benutzeroberfläche.
Alex Lockwood scheint recht zu haben, dass es eine bewährte Methode ist, Änderungen der Laufzeitkonfiguration mit AsyncTasks mithilfe eines "beibehaltenen Fragments" zu verarbeiten. onRetainNonConfigurationInstance()
wird in Lint in Android Studio veraltet. Die offiziellen Dokumente warnen uns davor android:configChanges
, von der Handhabung der Konfigurationsänderung selbst ...
Wenn Sie die Konfigurationsänderung selbst vornehmen, kann es viel schwieriger werden, alternative Ressourcen zu verwenden, da das System sie nicht automatisch für Sie anwendet. Diese Technik sollte als letzter Ausweg betrachtet werden, wenn Sie Neustarts aufgrund einer Konfigurationsänderung vermeiden müssen, und wird für die meisten Anwendungen nicht empfohlen.
Dann gibt es die Frage, ob man überhaupt eine AsyncTask für den Hintergrund-Thread verwenden soll.
Die offizielle Referenz für AsyncTask warnt ...
AsyncTasks sollten idealerweise für kurze Vorgänge (höchstens einige Sekunden) verwendet werden. Wenn Sie Threads über einen längeren Zeitraum laufen lassen müssen, wird dringend empfohlen, die verschiedenen APIs zu verwenden, die vom Paket java.util.concurrent bereitgestellt werden, z Executor, ThreadPoolExecutor und FutureTask.
Alternativ kann ein Dienst, ein Loader (mit einem CursorLoader oder AsyncTaskLoader) oder ein Inhaltsanbieter verwendet werden, um asynchrone Vorgänge auszuführen.
Ich teile den Rest des Beitrags in:
- Der Ablauf; und
- Der gesamte Code für das obige Verfahren.
Der Ablauf
Beginnen Sie mit einer grundlegenden AsyncTask als innere Klasse einer Aktivität (es muss keine innere Klasse sein, aber es wird wahrscheinlich bequem sein). Zu diesem Zeitpunkt verarbeitet die AsyncTask keine Änderungen der Laufzeitkonfiguration.
public class ThreadsActivity extends ActionBarActivity {
private ImageView mPictureImageView;
private class LoadImageFromNetworkAsyncTask
extends AsyncTask<String, Void, Bitmap> {
@Override
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
mPictureImageView.setImageBitmap(bitmap);
}
}
/**
* Requires in AndroidManifext.xml
* <uses-permission android:name="android.permission.INTERNET" />
*/
private Bitmap loadImageFromNetwork(String url) {
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream((InputStream)
new URL(url).getContent());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threads);
mPictureImageView =
(ImageView) findViewById(R.id.imageView_picture);
}
public void getPicture(View view) {
new LoadImageFromNetworkAsyncTask()
.execute("http://i.imgur.com/SikTbWe.jpg");
}
}
Fügen Sie eine verschachtelte Klasse RetainedFragment hinzu, die die Fragement-Klasse erweitert und keine eigene Benutzeroberfläche hat. Fügen Sie dem onCreate-Ereignis dieses Fragments setRetainInstance (true) hinzu. Stellen Sie Verfahren zum Einstellen und Abrufen Ihrer Daten bereit.
public class ThreadsActivity extends Activity {
private ImageView mPictureImageView;
private RetainedFragment mRetainedFragment = null;
...
public static class RetainedFragment extends Fragment {
private Bitmap mBitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The key to making data survive
// runtime configuration changes.
setRetainInstance(true);
}
public Bitmap getData() {
return this.mBitmap;
}
public void setData(Bitmap bitmapToRetain) {
this.mBitmap = bitmapToRetain;
}
}
private class LoadImageFromNetworkAsyncTask
extends AsyncTask<String, Integer,Bitmap> {
....
In onCreate () der äußersten Aktivitätsklasse wird das RetainedFragment behandelt: Verweisen Sie darauf, wenn es bereits vorhanden ist (falls die Aktivität neu gestartet wird). erstellen und hinzufügen, wenn es nicht existiert; Wenn es bereits vorhanden war, holen Sie sich Daten aus dem RetainedFragment und stellen Sie Ihre Benutzeroberfläche mit diesen Daten ein.
public class ThreadsActivity extends Activity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threads);
final String retainedFragmentTag = "RetainedFragmentTag";
mPictureImageView =
(ImageView) findViewById(R.id.imageView_picture);
mLoadingProgressBar =
(ProgressBar) findViewById(R.id.progressBar_loading);
// Find the RetainedFragment on Activity restarts
FragmentManager fm = getFragmentManager();
// The RetainedFragment has no UI so we must
// reference it with a tag.
mRetainedFragment =
(RetainedFragment) fm.findFragmentByTag(retainedFragmentTag);
// if Retained Fragment doesn't exist create and add it.
if (mRetainedFragment == null) {
// Add the fragment
mRetainedFragment = new RetainedFragment();
fm.beginTransaction()
.add(mRetainedFragment, retainedFragmentTag).commit();
// The Retained Fragment exists
} else {
mPictureImageView
.setImageBitmap(mRetainedFragment.getData());
}
}
Initiieren Sie die AsyncTask über die Benutzeroberfläche
public void getPicture(View view) {
new LoadImageFromNetworkAsyncTask().execute(
"http://i.imgur.com/SikTbWe.jpg");
}
Fügen Sie einen bestimmten Fortschrittsbalken hinzu und codieren Sie ihn:
- Fügen Sie dem UI-Layout einen Fortschrittsbalken hinzu.
- Holen Sie sich einen Verweis darauf in der Aktivität oncreate ();
- Machen Sie es zu Beginn und am Ende des Prozesses sichtbar und unsichtbar.
- Definieren Sie den Fortschritt, der in onProgressUpdate an die Benutzeroberfläche gemeldet werden soll.
- Ändern Sie den Parameter AsyncTask 2nd Generic von Void in einen Typ, der Fortschrittsaktualisierungen verarbeiten kann (z. B. Integer).
- PublishProgress an regelmäßigen Stellen in doInBackground ().
Der gesamte Code für das obige Verfahren
Aktivitätslayout.
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.mysecondapp.ThreadsActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<ImageView
android:id="@+id/imageView_picture"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@android:color/black" />
<Button
android:id="@+id/button_get_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@id/imageView_picture"
android:onClick="getPicture"
android:text="Get Picture" />
<Button
android:id="@+id/button_clear_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/button_get_picture"
android:layout_toEndOf="@id/button_get_picture"
android:layout_toRightOf="@id/button_get_picture"
android:onClick="clearPicture"
android:text="Clear Picture" />
<ProgressBar
android:id="@+id/progressBar_loading"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/button_get_picture"
android:progress="0"
android:indeterminateOnly="false"
android:visibility="invisible" />
</RelativeLayout>
</ScrollView>
Die Aktivität mit: untergeordneter AsyncTask-Innenklasse; Unterklasse RetainedFragment innere Klasse, die Änderungen der Laufzeitkonfiguration verarbeitet (z. B. wenn der Benutzer den Bildschirm dreht); und einen bestimmten Fortschrittsbalken, der in regelmäßigen Abständen aktualisiert wird. ...
public class ThreadsActivity extends Activity {
private ImageView mPictureImageView;
private RetainedFragment mRetainedFragment = null;
private ProgressBar mLoadingProgressBar;
public static class RetainedFragment extends Fragment {
private Bitmap mBitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The key to making data survive runtime configuration changes.
setRetainInstance(true);
}
public Bitmap getData() {
return this.mBitmap;
}
public void setData(Bitmap bitmapToRetain) {
this.mBitmap = bitmapToRetain;
}
}
private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,
Integer, Bitmap> {
@Override
protected Bitmap doInBackground(String... urls) {
// Simulate a burdensome load.
int sleepSeconds = 4;
for (int i = 1; i <= sleepSeconds; i++) {
SystemClock.sleep(1000); // milliseconds
publishProgress(i * 20); // Adjust for a scale to 100
}
return com.example.standardapplibrary.android.Network
.loadImageFromNetwork(
urls[0]);
}
@Override
protected void onProgressUpdate(Integer... progress) {
mLoadingProgressBar.setProgress(progress[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
publishProgress(100);
mRetainedFragment.setData(bitmap);
mPictureImageView.setImageBitmap(bitmap);
mLoadingProgressBar.setVisibility(View.INVISIBLE);
publishProgress(0);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threads);
final String retainedFragmentTag = "RetainedFragmentTag";
mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);
mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);
// Find the RetainedFragment on Activity restarts
FragmentManager fm = getFragmentManager();
// The RetainedFragment has no UI so we must reference it with a tag.
mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(
retainedFragmentTag);
// if Retained Fragment doesn't exist create and add it.
if (mRetainedFragment == null) {
// Add the fragment
mRetainedFragment = new RetainedFragment();
fm.beginTransaction().add(mRetainedFragment,
retainedFragmentTag).commit();
// The Retained Fragment exists
} else {
mPictureImageView.setImageBitmap(mRetainedFragment.getData());
}
}
public void getPicture(View view) {
mLoadingProgressBar.setVisibility(View.VISIBLE);
new LoadImageFromNetworkAsyncTask().execute(
"http://i.imgur.com/SikTbWe.jpg");
}
public void clearPicture(View view) {
mRetainedFragment.setData(null);
mPictureImageView.setImageBitmap(null);
}
}
In diesem Beispiel die Bibliotheksfunktion (auf die oben mit dem expliziten Paketpräfix com.example.standardapplibrary.android.Network verwiesen wird), die echte Arbeit leistet ...
public static Bitmap loadImageFromNetwork(String url) {
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
.getContent());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
Fügen Sie der AndroidManifest.xml alle Berechtigungen hinzu, die für Ihre Hintergrundaufgabe erforderlich sind ...
<manifest>
...
<uses-permission android:name="android.permission.INTERNET" />
Fügen Sie Ihre Aktivität zu AndroidManifest.xml hinzu ...
<manifest>
...
<application>
<activity
android:name=".ThreadsActivity"
android:label="@string/title_activity_threads"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.mysecondapp.MainActivity" />
</activity>