Ecco un altro esempio di un AsyncTask che utilizza un Fragment
per gestire le modifiche alla configurazione di runtime (come quando l'utente ruota lo schermo) con setRetainInstance(true)
. Viene inoltre dimostrata una determinata barra di avanzamento (regolarmente aggiornata).
L'esempio si basa in parte sui documenti ufficiali, Conservare un oggetto durante una modifica della configurazione .
In questo esempio il lavoro che richiede un thread in background è il semplice caricamento di un'immagine da Internet nell'interfaccia utente.
Alex Lockwood sembra aver ragione quando si tratta di gestire le modifiche alla configurazione di runtime con AsyncTasks usando un "frammento mantenuto" è la migliore pratica. onRetainNonConfigurationInstance()
viene deprecato in Lint, in Android Studio. I documenti ufficiali ci avvertono di non usare android:configChanges
, dalla Gestione della modifica della configurazione , ...
Gestire da soli la modifica della configurazione può rendere molto più difficile l'utilizzo di risorse alternative, poiché il sistema non le applica automaticamente per te. Questa tecnica deve essere considerata l'ultima risorsa quando è necessario evitare il riavvio a causa di una modifica della configurazione e non è consigliata per la maggior parte delle applicazioni.
Quindi c'è il problema se si debba usare un AsyncTask per il thread in background.
Il riferimento ufficiale per AsyncTask avverte ...
AsyncTasks dovrebbe essere idealmente usato per operazioni brevi (al massimo pochi secondi). Se è necessario mantenere i thread in esecuzione per lunghi periodi di tempo, si consiglia vivamente di utilizzare le varie API fornite dal pacakge java.util.concurrent come Executor, ThreadPoolExecutor e FutureTask.
In alternativa, è possibile utilizzare un servizio, un caricatore (utilizzando un CursorLoader o AsyncTaskLoader) o un provider di contenuti per eseguire operazioni asincrone.
Divido il resto del post in:
- La procedura; e
- Tutto il codice per la procedura sopra descritta.
La procedura
Inizia con un AsyncTask di base come classe interna di un'attività (non è necessario che sia una classe interna ma probabilmente sarà conveniente esserlo). In questa fase, AsyncTask non gestisce le modifiche alla configurazione di runtime.
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");
}
}
Aggiungi una classe nidificata RetainedFragment che estende la classe Fragement e non ha la sua interfaccia utente. Aggiungi setRetainInstance (true) all'evento onCreate di questo frammento. Fornire procedure per impostare e ottenere i tuoi dati.
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> {
....
Nella classe esterna Activity onCreate () gestisce RetainedFragment: fai riferimento se esiste già (nel caso in cui l'attività si riavvii); crearlo e aggiungerlo se non esiste; Quindi, se esiste già, ottieni i dati da RetainedFragment e imposta l'interfaccia utente con tali dati.
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());
}
}
Avviare AsyncTask dall'interfaccia utente
public void getPicture(View view) {
new LoadImageFromNetworkAsyncTask().execute(
"http://i.imgur.com/SikTbWe.jpg");
}
Aggiungi e codifica una determinata barra di avanzamento:
- Aggiungi una barra di avanzamento al layout dell'interfaccia utente;
- Ottieni un riferimento ad esso in Activity oncreate ();
- Renderlo visibile e invisibile all'inizio e alla fine del processo;
- Definire l'avanzamento da segnalare all'interfaccia utente in onProgressUpdate.
- Modificare il secondo parametro generico AsyncTask da Void a un tipo in grado di gestire gli aggiornamenti di avanzamento (ad es. Intero).
- publishingProgress in punti regolari in doInBackground ().
Tutto il codice per la procedura sopra descritta
Layout delle attività.
<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'attività con: classe interna AsyncTask sottoclassata; classe interna RetainedFragment sottoclasse che gestisce le modifiche alla configurazione di runtime (ad es. quando l'utente ruota lo schermo); e una determinata barra di avanzamento che si aggiorna a intervalli regolari. ...
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 questo esempio la funzione di libreria (di cui sopra con il prefisso esplicito del pacchetto com.example.standardapplibrary.android.Network) che funziona davvero ...
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;
}
Aggiungi tutte le autorizzazioni necessarie per l'attività in background su AndroidManifest.xml ...
<manifest>
...
<uses-permission android:name="android.permission.INTERNET" />
Aggiungi la tua attività su 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>