Bài 26: CursorLoader trong Android - Lập trình Android cơ bản

Đăng bởi: Admin | Lượt xem: 2824 | Chuyên mục: Android


1. Giới thiệu

Loader là một kỹ thuật không phải mới trong lập trình ứng dụng android hiện tại, khái niệm Loader hay Loader Manager được giới thiệu từ khi Google giới thiệu phiên bản android Honeycomb cùng với sự ra đời của Fragment.
Vậy bạn có cho rằng bài viết này đã cũ và nó không còn hữu ích nữa? Nhưng không, trong thực tế thì mình thấy những bạn đã và đang là lập trình viên android cũng ít sử dụng kỹ thuật Loader này. Nếu bạn đã biết và nắm được kỹ thuật này rồi thì bạn có thể dừng việc đọc tại đây, còn nếu chưa thì hãy cùng mình theo dõi để biết xem Loader là gì? sử dụng nó ra sao? và tại sao mình lại khuyên bạn nên dùng nó nhé.

2. Chi tiết

2.1 Loaders ra đời khi nào

Honeycomb được google ra mắt trong sự kiện Google IO vào tháng 7/2011 như một sự đột phá của hệ điều hành android cả về mặt UI, UX lẫn cơ chế hoạt động. Nó bao gồm rất nhiều cải tiến về kỹ thuật với các công nghệ mới đáp ứng xu hướng phát triển của xã hội cần một thiết bị lớn hơn, đa nhiệm hơn, dễ tương tác hơn do đó action bar, fragment... ra đời.
Với sự giới thiệu của Honeycomb, Loaders trở thành 1 định hướng để truy xuất tới database (CSDL) hoặc content providers. Chúng được dùng để load dữ liệu khi cần và thông báo tới người dùng khi thực hiện kết quả xong. (Cái này đại khái cũng tương tự như ajax trong javascript).
Google không chỉ giới thiệu Loaders mà còn thông báo deprecated phương thức truy cập tới Cusor trong activity trước đó. Bạn được khuyên rằng không nên sử dụng các method cũ là startManagingCursor() hay managedQuery() trong dự án của mình nữa.
Và trong bài viết này tôi sẽ giới thiệu với bạn các class từ Loader API và cách sử dụng chúng ra sao.
Các class và interface trong Loader API.
|_. Class |_. Usage |
| LoaderManager | Class làm nhiệm vụ quản lý các Loader của bạn. Giữ vai trò tương tác dữ liệu giữa Loader với Activity, Fragment.|
| LoaderManager.LoaderCallbacks | Là interface để lắng nghe sự kiện từ các Loader. Activity hay Fragment nào lắng nghe sự kiện của Loader thì phải implement class này. |
| Loader | Là class khởi tạo của mọi Loader. Nếu bạn muốn custom một class Loader của chính bạn thì bạn phải kế thừa từ class này. |
| AsyncTaskLoader | Một class kế thừa từ Loader và có vai trò như một AsyncTask |
| CursorLoader | Một class loader con kế thừa từ AsyncTaskLoader làm nhiệm vụ truy cập dữ liệu từ các ContentProvider |
Trong các phần dưới đây tôi sẽ mô tả chi các class trên để bạn biết về chúng và làm sao để sử dụng một cách hiệu quả - chúng ta sẽ bắt đầu với LoaderManager.

2.2 LoaderManager

Class này đóng vai trò là người quản lý các Loader trong vòng đời của activity hay các fragment. Nếu Hệ thống android hủy (kết thúc) các fragment/activity thì LoaderManager sẽ thông báo với các loader mà nó đang quản lý giải phóng dữ liệu.
LoaderManager cũng chịu trách nhiệm lưu trữ dữ liệu của bạn trong các trường hợp thay đổi điều kiện hệ thống như xoay màn hình và gọi các callback tương ứng trong trường hợp có dữ liệu thay đổi.
Tóm lại, LoaderManager cung cấp phương thức quản lý data hiệu quản hơn, mạnh mẽ hơn so với các phương thức cũ startManagingCursor() hay managedQuery() làm.
Bạn không thể khởi tạo LoaderManager. Thay vào đó bạn chỉ cần gọi hàm getLoaderManager() từ activity hay fragment để lấy ra dùng thôi.
Thường thì bạn chỉ quan tâm đến 2 hàm cơ bản của LoaderManager là:
  1. initLoader()
  2. restartLoader()
initLoader() là hàm dùng để thêm một Loader vào LoaderManager:
getLoaderManager().initLoader(LOADER_ID, Bundle, LoaderCallback);
Hàm này có 3 biến:
  1. LOADER_ID: là id duy nhất của loader này (bạn không thể thêm 2 loader có cùng id vào một LoaderManager)
  2. Bundle: là lựa chọn tùy biến cho Loader/LoaderCallbacks của bạn. Bạn có thể dùng ID của Loader để gọi ở những hàm khác sau này. Vì vậy dùng khai báo final statck cho ID làm có code của bạn tường mình hơn. Tuy nhiên Bundle sẽ giúp bạn mang thêm những điều kiện hoặc dữ liệu khác cho việc sử dụng loader (cái này thì không dùng được với CursorLoader)
  3. LoaderCallback: Chính là interface để lắng nghe sự kiện của loader cho việc xử lý dữ liệu của bạn.
Hàm initLoader() sẽ tạo ra một loader mới nếu ID của loader này không được khởi tạo trước đó. Bạn phải nhớ rằng Android sẽ quản lý việc thay đổi cấu hình của hệ thống thay cho bạn (configuration) do đó chỉ với một thay đổi đơn giản như việc xoay màn hình cũng dẫn đến việc gọi lại hàm initLoader(). Nhưng trong trường hợp này LoaderManger sẽ trả về một Loader đã tồn tại trước đó truy vấn của bạn sẽ không bị lặp lại nữa.
restartLoader() Bởi vì Android không thực hiện truy vấn lại cho nên khi bạn muốn truy vấn lại bạn cần một phương thức khác để làm điều này. Và chúng ta cũng dễ nhận thấy điều này trong các truy vấn tìm kiếm khi chúng ta thay đổi điều kiện tìm kiếm.
Bạn tiến hành thiết lập lại truy vấn bằng cách gọi hàm restartLoader(). Phương thức này cũng cần các biến như hàm initLoader(). Và tất nhiên bạn sẽ phải sử dụng ID mà bạn đã tạo ở initLoader() để tiến hành truy vấn lại. initLoader() là hàm dùng để thêm một Loader vào LoaderManager:
getLoaderManager().restartLoader(LOADER_ID, Bundle, LoaderCallback);

2.3 LoaderManager.LoaderCallbacks

Là một interface định nghĩa sẵn các hàm mà bạn phải kế thừa để tạo Loader, xử lý và giải phóng dữ liệu.
Được sử dụng như một biến và bạn cần phải định nghĩa kiểu dữ liệu cho nó. Thường thì dạng dữ liệu cơ bản là Cursor:
public class YourFragment extends Fragment
      implements LoaderCallbacks<Cursor> {
   //…
}
Các hàm bắt buộc phải kế thừa: onCreateLoader(), onLoadFinished() và onLoadReset()
  1. onCreateLoader() LoaderManager sẽ gọi hàm này khi bạn gọi hàm initLoader(). Và như đã đề cập trước đó trình quản lý LoaderManager sẽ chỉ gọi tới hàm này khi ID loader chưa được khởi tạo trước đó.
Hàm này cần 1 biến kiểu số nguyên int và một Bundle. Đó chính là các biến trong initLoader(). Ví dụ chúng ta sẽ khởi tạo một CursorLoader dư lày:
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
   CursorLoader loader = new CursorLoader(
         Context,
         SOME_CONTENT_URI,
         projection,
         selection,
         selectionArgs,
         sortOrder);
   return loader;
}
Bạn sẽ nhìn thấy biến Context, đây là một đối tượng được truyền thêm vào Loader để thực hiện việc truy vấn ContentResolver. Nếu bạn vẫn chưa rõ về những biến này bạn có thể tham khảo thêm bài viết về việc truy cập đến content providers ở đây http://www.grokkingandroid.com/android-tutorial-using-content-providers/
Nếu bạn muốn thực hiện nhiều truy vấn hãy tạo các Loader với các ID khác nhau nhé.
  1. onLoadFinished() Hàm này khá là thú vị. Bạn sẽ tiến hành cập nhật UI dựa vào kết quả mà bạn nhận được qua Loader ở đây nhé.
Với ListAdapters bạn chỉ cần đơn giản thay Cusor của adapter bằng Cusor bạn nhận được ở đây. Bạn có thể xem thêm trong mục “Cập nhật CursorAdapters“ bên dưới nhé.
Ví dụ :
public void onLoadFinished(
      Loader<Cursor> loader,
      Cursor cursor) {
   if (cursor != null && cursor.getCount() > 0) {
      cursor.moveToFirst();
      int idIndex =
            cursor.getColumnIndex(LentItems._ID);
      int nameIndex =
            cursor.getColumnIndex(LentItems.NAME);
      int borrowerIndex =
            cursor.getColumnIndex(LentItems.BORROWER);
      this.itemId = cursor.getLong(idIndex);
      String name = cursor.getString(nameIndex);
      String borrower = cursor.getString(borrowerIndex);
      ((EditText)findViewById(R.id.name)).
            setText(name);
      ((EditText)findViewById(R.id.person)).
            setText(borrower);
   }
}
  1. onLoadReset() Hàm này cho phép bạn giải phóng mọi dữ liệu mà Loader đang giữ. Bạn có thể set các đối tượng cusor (từ kết quả Loader) mà bạn dùng bằng null. Nhưng nhớ rằng đứng đóng cusor nhé, Loader sẽ làm điều này cho bạn.

2.4 Loader, AsyncTaskLoader and CursorLoader

Interface Loader và các hàm của nó sẽ không thú vị chút nào khi bạn không biết cách biến chúng thành code của mình. Và tất nhiên bạn phải tạo 1 Loader. Không đơn giản là kế thừa constructor của CursorLoader và bạn không thể tự tương tác với các đối tượng mà nó định nghĩa sẵn. Với Loader của bạn thì khác, bạn có thể tùy biến dữ liệu theo ý muốn của bạn một cách thoải mái.
Nếu bạn sử dụng nhiều Loader bạn cần truy xuất tới ID của Loader trong hàm callback. Để lấy ID thì bạn gọi hàm getId() của Loader trong callback. Nếu bạn muốn tùy chỉnh một Loader theo ý của mình thì bạn có thể tham khảo bài viết của tác giả Alex Lockwood "Tutorial on implementing loaders" tại http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html

2.5 Dùng Loader để cập nhật CursorAdapters

Một ứng dụng quan trọng trong việc dùng cusors trong android là dùng CursorAdapter với vai trò hiển thị dữ liệu cho ListView, AutoCompleteTextViews...
Và với Loader thì bạn sẽ thực hiện việc này một cách đơn giản.
Trước hết, Bạn sẽ không có đối tượng Cursor trước khi hàm onLoadFinished() của Callback được gọi. Hay nói cách khác cursor sẽ chưa sẵn sàng khi bạn khởi tạo adapter. Vì vậy bạn sẽ khởi tạo adapter với cusor null:
SimpleCursorAdapter adapter =
      new SimpleCursorAdapter(
            getApplicationContext(),
            android.R.layout.simple_list_item_1,
            null,
            columns,
            layoutIds,
            0);
Khi cusor sẵn sàng để sử dụng thì bạn chỉ cần gọi hàm swapCursor() để add đối tượng cusor vào adapter:
public void onLoadFinished(
      Loader<Cursor> loader,
      Cursor cursor) {
   ((SimpleCursorAdapter)this.getListAdapter()).
         swapCursor(cursor);
}
Và để giải phóng hết dữ liệu trong hàm onLoadReset() bạn cũng cần gọi hàm swapCursor() nhưng truyền vào đối tượng cusor null.
public void onLoaderReset(Loader<Cursor> loader) {
   ((SimpleCursorAdapter)this.getListAdapter()).
         swapCursor(null);
}

2.6 Sử dụng Loader để truy xuất tới đối tượng SQLiteDatabase

Android CursorLoader chỉ được sử dụng cho những đối tượng Cursors từ content providers và để dùng được thì chúng ta cần một loại Loader khác nếu muốn trực tiếp sử dụng đối tượng SQLite.
Chúng ta cần phải cảm ơn Mark Murphy người đã viết ra thư viện để hỗ trợ việc dùng Loader, cái có chứa cả SQLiteCursorLoader. Để dùng được SQLiteCursorLoader của Murphy bạn phải tạo một câu lệnh truy vấn SQL để truyền vào constructor của Loader. Và Loader này cần 1 đối tượng SQLiteOpenHelper. Ví dụ:
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
   String rawQuery = "SELECT …";
   String[] queryParams = // to substitute placeholders
   SQLiteCursorLoader loader =
   new SQLiteCursorLoader(
         getActivity().getApplicationContext(),
         yourSqliteOpenHelper,
         rawQuery,
         queryParams);
   return loader;
}
  Để biết thêm chi tiết bạn có thể tham khảo nguồn bài viết từ GitHub https://github.com/commonsguy/cwac-loaderex

2.7 Khi nào thì không dùng Loader

Trên trang reddit, thì lập trình viên cokacokacoh có chú ý rằng bài viết của tôi thiếu một mục khi nào thì không nên dùng Loader. Và tất nhiên là anh ấy đúng. Vì vậy nên tôi thêm một phần vào cuối bài viết.
Tác giả cokacokacoh đã chỉ ra rằng bạn không nên sử dụng Loaders nếu bạn cần những nhiệm vụ chạy ngầm hoàn thành. Android sẽ kết thúc các Loaders cùng với các Activity/Fragment chứa nó. Nếu bạn cần làm một số hành động thì bạn phải chờ đến khi kết thúc vậy đừng dùng Loader nhé, mà bạn nên dùng service cho những việc như này.
Nhớ rằng Loader là những component đặt biệt giúp bạn tạo ra các kết nối bất đồng bộ với các component UI. Đó là lý do tại Loaders hạn cgees việc khởi tạo các component. Đừng quá lạm dụng Loader cho mọi thứ.

3. Tổng kết:

Bài viết là sự tổng hợp của nhiều nguồn khác nhau từ các trang web khác nhau , hi vọng bài viết sẽ giúp được các bạn ít nhiều trong công việc.
Bài tiếp theo: Databinding trong Android >>
vncoder logo

Theo dõi VnCoder trên Facebook, để cập nhật những bài viết, tin tức và khoá học mới nhất!