Hướng dẫn sử dụng Android AsyncTaskLoader

1- Android AsyncTaskLoader

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:
AsyncTaskAsyncTaskLoader 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:

2- Ví dụ AsyncTaskLoader

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", "[email protected]", "Tom"));
        list.add(new UserAccount("jerry", "[email protected]", "Jerry"));
        list.add(new UserAccount("donald", "[email protected]", "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;
    }

}

Xem thêm các chuyên mục: