Oto kolejny przykład AsyncTask, który używa a Fragment
do obsługi zmian konfiguracji środowiska uruchomieniowego (na przykład, gdy użytkownik obraca ekran) z setRetainInstance(true)
. Pokazany jest również określony (regularnie aktualizowany) pasek postępu.
Przykład jest częściowo oparty na oficjalnej dokumentacji Zachowanie obiektu podczas zmiany konfiguracji .
W tym przykładzie praca wymagająca wątku w tle polega na samym załadowaniu obrazu z Internetu do interfejsu użytkownika.
Wydaje się, że Alex Lockwood ma rację, że jeśli chodzi o obsługę zmian konfiguracji środowiska wykonawczego za pomocą AsyncTasks, najlepszym rozwiązaniem jest użycie „Zachowanego fragmentu”. onRetainNonConfigurationInstance()
zostaje uznany za przestarzały w Lint w Android Studio. Oficjalna dokumentacja ostrzega nas przed używaniem android:configChanges
, od samodzielnej obsługi zmiany konfiguracji , ...
Samodzielna obsługa zmiany konfiguracji może znacznie utrudnić korzystanie z alternatywnych zasobów, ponieważ system nie zastosuje ich automatycznie. Technikę tę należy traktować jako ostateczność, gdy należy unikać ponownego uruchamiania z powodu zmiany konfiguracji i nie jest zalecana dla większości aplikacji.
Następnie pojawia się kwestia, czy w ogóle należy używać AsyncTask dla wątku w tle.
Oficjalny wniosek o AsyncTask ostrzega ...
AsyncTasks powinno być idealnie używane do krótkich operacji (najwyżej kilka sekund). Jeśli chcesz, aby wątki działały przez długi czas, zdecydowanie zaleca się użycie różnych interfejsów API dostarczonych przez pakiet java.util.concurrent, takich jak Executor, ThreadPoolExecutor i FutureTask.
Alternatywnie można użyć usługi, programu ładującego (przy użyciu CursorLoader lub AsyncTaskLoader) lub dostawcy treści do wykonywania operacji asynchronicznych.
Resztę postu dzielę na:
- Procedura; i
- Cały kod powyższej procedury.
Procedura
Zacznij od podstawowego AsyncTask jako wewnętrznej klasy działania (nie musi to być klasa wewnętrzna, ale prawdopodobnie będzie to wygodne). Na tym etapie AsyncTask nie obsługuje zmian konfiguracji środowiska wykonawczego.
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");
}
}
Dodaj zagnieżdżoną klasę RetainedFragment, która rozszerza klasę Fragement i nie ma własnego interfejsu użytkownika. Dodaj setRetainInstance (true) do zdarzenia onCreate tego fragmentu. Zapewnij procedury ustawiania i pobierania danych.
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> {
....
W najbardziej zewnętrznej klasie Activity, onCreate () obsługuje RetainedFragment: Odwołaj się do niego, jeśli już istnieje (w przypadku ponownego uruchamiania działania); utwórz i dodaj, jeśli nie istnieje; Następnie, jeśli już istniał, pobierz dane z RetainedFragment i ustaw interfejs użytkownika przy użyciu tych danych.
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());
}
}
Zainicjuj AsyncTask z interfejsu użytkownika
public void getPicture(View view) {
new LoadImageFromNetworkAsyncTask().execute(
"http://i.imgur.com/SikTbWe.jpg");
}
Dodaj i zakoduj określony pasek postępu:
- Dodaj pasek postępu do układu interfejsu użytkownika;
- Uzyskaj do niego odniesienie w działaniu oncreate ();
- Spraw, aby był widoczny i niewidoczny na początku i na końcu procesu;
- Zdefiniuj postęp raportowania do interfejsu użytkownika w onProgressUpdate.
- Zmień parametr AsyncTask 2nd Generic z Void na typ, który może obsługiwać aktualizacje postępu (np. Integer).
- publikujProgress w regularnych punktach w doInBackground ().
Cały kod powyższej procedury
Układ zajęć.
<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>
Działanie z: podklasą klasy wewnętrznej AsyncTask; podklasa klasy wewnętrznej RetainedFragment, która obsługuje zmiany konfiguracji środowiska wykonawczego (np. gdy użytkownik obraca ekran); i określony pasek postępu aktualizowany w regularnych odstępach czasu. ...
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);
}
}
W tym przykładzie funkcja biblioteki (wymieniona powyżej z wyraźnym prefiksem pakietu com.example.standardapplibrary.android.Network), która naprawdę działa ...
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;
}
Dodaj wszelkie uprawnienia wymagane przez zadanie w tle do pliku AndroidManifest.xml ...
<manifest>
...
<uses-permission android:name="android.permission.INTERNET" />
Dodaj swoją aktywność do 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>