Hướng dẫn sử dụng Android AsyncTaskLoader
Xem thêm các chuyên mục:
AsyncTaskLoader được sử dụng để thực hiện một nhiệm vụ không đồng bộ (asynchronous task) bên dưới nền của ứng dụng, vì vậy trong quá trình mà nhiệm vụ được thực hiện người dùng vẫn có thể tương tác với ứng dụng. Sau khi nhiệm vụ hoàn thành kết quả sẽ được cập nhập lên giao diện.
AsyncTaskLoader thực hiện các chức năng tương tự như AsyncTask, nhưng AsyncTaskLoader tốt hơn, vì các lý do sau đây:
AsyncTask và AsyncTaskLoader có cách cư xử khác nhau khi cấu hình của thiết bị thay đổi, chẳng hạn, người dùng xoay màn hình thiết bị, Activity có thể bị phá hủy (destroy) và được tạo lại (re-create). Và khi đó:
- AsyncTask sẽ được thực thi lại (re-executed) và một Thread mới được tạo ra, Thread cũ trở thành bơ vơ và không còn được kiểm soát.
- AsyncTaskLoader sẽ được sử dụng lại (re-used) dựa trên Loader ID đã được đăng ký với LoaderManager trước đó. Như vậy nó ngăn chặn sự trùng lặp của các nhiệm vụ nền, và tránh việc tạo ra các nhiệm vụ rác.
Android AsyncTaskLoader Javadocs:Xem thêm:
Trong ví dụ này chúng ta sẽ sử dụng AsyncTaskLoader để tải danh sách các UserAccount và hiển thị nó trên giao diện. Các dữ liệu này có thể được lấy từ một URL hoặc từ cơ sở dữ liệu. Nhiệm vụ này được thực thi ở bên dưới nền của ứng dụng, vì vậy trong quá trình nhiệm vụ được thực thi người dùng vẫn có thể tương tác với ứng dụng.

Trên Android Studio tạo mới project:
- File > New > New Project > Empty Activity
- Name: AsyncTaskLoaderExample
- Package name: org.o7planning.asynctaskloaderexample
- Language: Java
Giao diện của ứng dụng:

main_activity.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button_load" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:text="Load Data" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button_cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:text="Cancel" app:layout_constraintStart_toEndOf="@+id/button_load" app:layout_constraintTop_toTopOf="parent" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" app:layout_constraintStart_toEndOf="@+id/button_cancel" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="16dp" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:layout_marginEnd="16dp" android:layout_marginRight="16dp" android:layout_marginBottom="16dp" android:background="#F3FAED" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/button_load" /> </androidx.constraintlayout.widget.ConstraintLayout>
UserAccountTaskLoader.java
package org.o7planning.asynctaskloaderexample; import android.content.Context; import android.os.SystemClock; import androidx.loader.content.AsyncTaskLoader; import java.util.ArrayList; import java.util.List; public class UserAccountTaskLoader extends AsyncTaskLoader<List<UserAccount>> { private String param1; private String param2; public UserAccountTaskLoader(Context context, String param1, String param2) { super(context); this.param1 = param1; this.param2 = param2; } @Override public List<UserAccount> loadInBackground() { // Do something, for example: // - Download data from URL and parse it info Java object. // - Query data from Database into Java object. List<UserAccount> list = new ArrayList<UserAccount>(); list.add(new UserAccount("tom", "tom@example.com", "Tom")); list.add(new UserAccount("jerry", "jerry@example.com", "Jerry")); list.add(new UserAccount("donald", "donald@example.com", "Donald")); SystemClock.sleep(2000); // 2 Seconds. return list; } }
MainActivity.java
package org.o7planning.asynctaskloaderexample; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import java.util.List; public class MainActivity extends AppCompatActivity // implements LoaderManager.LoaderCallbacks<List<UserAccount>>, Loader.OnLoadCanceledListener<List<UserAccount>> { private static final String LOG_TAG = "AndroidExample"; private static final int LOADER_ID_USERACCOUNT = 10000; private Button buttonLoad; private Button buttonCancel; private ProgressBar progressBar; private TextView textView; private static final String KEY_PARAM1 = "SomeKey1"; private static final String KEY_PARAM2 = "SomeKey2"; private LoaderManager loaderManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.buttonLoad = (Button) this.findViewById(R.id.button_load); this.buttonCancel = (Button) this.findViewById(R.id.button_cancel); this.progressBar = (ProgressBar) this.findViewById(R.id.progressBar); this.textView = (TextView) this.findViewById(R.id.textView); // Hide ProgressBar. this.progressBar.setVisibility(View.GONE); this.buttonCancel.setEnabled(false); this.buttonLoad.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { clickButtonLoad(); } }); this.buttonCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { clickButtonCancel(); } }); this.loaderManager = LoaderManager.getInstance(this); } // User click on "Load Data" button. private void clickButtonLoad() { this.textView.setText(""); Log.i(LOG_TAG, "loadUserAccount"); LoaderManager.LoaderCallbacks<List<UserAccount>> loaderCallbacks = this; // Arguments: Bundle args = new Bundle(); args.putString(KEY_PARAM1, "Some value1"); args.putString(KEY_PARAM2, "Some value2"); // You can pass a null args to a Loader Loader<List<UserAccount>> loader = this.loaderManager.initLoader(LOADER_ID_USERACCOUNT, args, loaderCallbacks); try { loader.registerOnLoadCanceledListener(this); // Loader.OnLoadCanceledListener } catch(IllegalStateException e) { // There is already a listener registered } loader.forceLoad(); // Start Loading.. } // User click on "Cancel" button. private void clickButtonCancel() { Log.i(LOG_TAG, "cancelLoadUserAccount"); Loader<List<UserAccount>> loader = this.loaderManager.getLoader(LOADER_ID_USERACCOUNT); if(loader != null) { boolean cancelled = loader.cancelLoad(); } } // Implements method of LoaderManager.LoaderCallbacks @NonNull @Override public Loader<List<UserAccount>> onCreateLoader(int id, @Nullable Bundle args) { Log.i(LOG_TAG, "onCreateLoader"); this.progressBar.setVisibility(View.VISIBLE); // To show if(id == LOADER_ID_USERACCOUNT) { this.buttonLoad.setEnabled(false); this.buttonCancel.setEnabled(true); // Parameters: String param1 = (String) args.get(KEY_PARAM1); String param2 = (String) args.get(KEY_PARAM2); // Return a Loader. return new UserAccountTaskLoader(MainActivity.this, param1, param2); } throw new RuntimeException("TODO.."); } // Implements method of LoaderManager.LoaderCallbacks @Override public void onLoadFinished(@NonNull Loader<List<UserAccount>> loader, List<UserAccount> data) { Log.i(LOG_TAG, "onLoadFinished"); if(loader.getId() == LOADER_ID_USERACCOUNT) { // Destroy a Loader by ID. this.loaderManager.destroyLoader(loader.getId()); StringBuilder sb = new StringBuilder(); for(UserAccount userAccount: data) { sb.append("Username:").append(userAccount.getUserName()).append("\t") // .append("Email:").append(userAccount.getEmail()).append("\n"); } this.textView.setText(sb.toString()); // Hide ProgressBar. this.progressBar.setVisibility(View.GONE); this.buttonLoad.setEnabled(true); this.buttonCancel.setEnabled(false); } } // Implements method of LoaderManager.LoaderCallbacks @Override public void onLoaderReset(@NonNull Loader<List<UserAccount>> loader) { Log.i(LOG_TAG, "onLoaderReset"); this.textView.setText(""); } // Implements method of Loader.OnLoadCanceledListener @Override public void onLoadCanceled(@NonNull Loader<List<UserAccount>> loader) { Log.i(LOG_TAG, "onLoadCanceled"); if(loader.getId() == LOADER_ID_USERACCOUNT) { // Destroy a Loader by ID. this.loaderManager.destroyLoader(loader.getId()); this.progressBar.setVisibility(View.GONE); // To hide this.buttonLoad.setEnabled(true); this.buttonCancel.setEnabled(false); } } }
UserAccount.java
package org.o7planning.asynctaskloaderexample; public class UserAccount { private String userName; private String email; private String fullName; public UserAccount(String userName, String email, String fullName) { this.userName = userName; this.email = email; this.fullName = fullName; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } }