Voici un autre exemple d'AsyncTask qui utilise un Fragment
pour gérer les changements de configuration d'exécution (comme lorsque l'utilisateur fait pivoter l'écran) avec setRetainInstance(true)
. Une barre de progression déterminée (régulièrement mise à jour) est également présentée.
L'exemple est en partie basé sur les documents officiels, Conservation d'un objet lors d'un changement de configuration .
Dans cet exemple, le travail nécessitant un fil d'arrière-plan est le simple chargement d'une image depuis Internet dans l'interface utilisateur.
Alex Lockwood semble avoir raison de dire que lorsqu'il s'agit de gérer les changements de configuration d'exécution avec AsyncTasks, l'utilisation d'un «fragment conservé» est la meilleure pratique. onRetainNonConfigurationInstance()
devient obsolète dans Lint, dans Android Studio. La documentation officielle nous avertit de ne pas utiliser android:configChanges
, de Gérer vous-même le changement de configuration , ...
La gestion du changement de configuration vous-même peut rendre l'utilisation de ressources alternatives beaucoup plus difficile, car le système ne les applique pas automatiquement pour vous. Cette technique doit être considérée comme un dernier recours lorsque vous devez éviter les redémarrages dus à un changement de configuration et n'est pas recommandée pour la plupart des applications.
Ensuite, il y a la question de savoir si l'on doit utiliser une AsyncTask pour le thread d'arrière-plan.
La référence officielle d'AsyncTask prévient ...
AsyncTasks devrait idéalement être utilisé pour des opérations courtes (quelques secondes au maximum). Executor, ThreadPoolExecutor et FutureTask.
Vous pouvez également utiliser un service, un chargeur (en utilisant un CursorLoader ou AsyncTaskLoader) ou un fournisseur de contenu pour effectuer des opérations asynchrones.
Je divise le reste du message en:
- La procédure; et
- Tout le code pour la procédure ci-dessus.
La procédure
Commencez avec une AsyncTask de base comme classe interne d'une activité (il n'est pas nécessaire que ce soit une classe interne, mais ce sera probablement pratique). À ce stade, AsyncTask ne gère pas les modifications de configuration d'exécution.
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");
}
}
Ajoutez une classe imbriquée RetainedFragment qui étend la classe Fragement et n'a pas sa propre interface utilisateur. Ajoutez setRetainInstance (true) à l'événement onCreate de ce fragment. Fournissez des procédures pour définir et obtenir vos données.
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> {
....
Dans la classe d'activité la plus externe, onCreate () gère le RetainedFragment: référencez-le s'il existe déjà (au cas où l'activité redémarre); créez-le et ajoutez-le s'il n'existe pas; Ensuite, s'il existait déjà, récupérez les données de RetainedFragment et définissez votre interface utilisateur avec ces données.
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());
}
}
Lancer l'AsyncTask depuis l'interface utilisateur
public void getPicture(View view) {
new LoadImageFromNetworkAsyncTask().execute(
"http://i.imgur.com/SikTbWe.jpg");
}
Ajoutez et codez une barre de progression déterminée:
- Ajoutez une barre de progression à la disposition de l'interface utilisateur;
- Obtenez une référence à celui-ci dans l'activité oncreate ();
- Rendez-le visible et invisible au début et à la fin du processus;
- Définissez la progression à signaler à l'interface utilisateur dans onProgressUpdate.
- Modifiez le paramètre AsyncTask 2nd Generic de Void en un type qui peut gérer les mises à jour de progression (par exemple Integer).
- publishProgress à des points réguliers dans doInBackground ().
Tout le code pour la procédure ci-dessus
Disposition des activités.
<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>
L'activité avec: classe interne AsyncTask sous-classée; classe interne sous-classée RetainedFragment qui gère les changements de configuration d'exécution (par exemple, lorsque l'utilisateur fait pivoter l'écran); et une mise à jour de la barre de progression déterminée à intervalles réguliers. ...
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);
}
}
Dans cet exemple, la fonction de bibliothèque (référencée ci-dessus avec le préfixe de package explicite com.example.standardapplibrary.android.Network) qui fonctionne vraiment ...
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;
}
Ajoutez toutes les autorisations requises par votre tâche d'arrière-plan à AndroidManifest.xml ...
<manifest>
...
<uses-permission android:name="android.permission.INTERNET" />
Ajoutez votre activité à AndroidManifest.xml ...
<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>