How to Use Loaders in Android - Sử dụng loader trong lập trình android
I. Mở đầ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. http://developer.android.com/guide/components/loaders.html Vậy ...
I. Mở đầ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.
http://developer.android.com/guide/components/loaders.html
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é
II. Chi tiết
1. Giới thiệu
Trước hết mình cũng xin giới thiệu một số trang liên quan đến lập trình android mà mình thường tham khảo
- http://www.grokkingandroid.com/
- https://android-arsenal.com/
Và bài viết mình sử dụng trong viblo lần này cũng được dịch từ trang grokkingandroid của tác giả Wolfram Rittmeyer, một người rất có tâm huyết với lập trình mobile nói chung và lập trình android nói riêng, đồng thời cũng là một lập trình viên có nhiều bài viết tâm đắc về các kỹ thuật trong lập trình.
2. 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.
3. 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à:
- initLoader()
- 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:
- 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)
- 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)
- 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);
4. 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()
The methods you have to implement are:
onCreateLoader(),
onLoadFinished() and
onLoadReset()
- 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é.
- 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); } }
- 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.
5. 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
6. 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); }
7. Sử dụng Loader trong các version android thấp hơn Honeycomb
Để hỗ trợ các chức năng mới trên các thiết bị Android phiên bản cũ Google cung cấp thư viện Support Library. Và chúng ta chú ý 2 component chính trong gói thư viện này là Fragments and Loaders.
Bạn có sử dụng thư viện này trong các dự án hỗ trợ các phiên bản android thấp. Bạn có thể sử dụng thư viện support như Loader_API trên các thiết bị Android phiên bản cao với đầy đủ chức năng.
Nhưng bạn cần chú những gói class nhúng vào, mọi class nhúng vào từ thư viện support sẽ bắt đầu bằng android.support.v4
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader;
Đừng lẫn lộn giữa các class thư viện support với các class thường nhé nếu không compiler sẽ báo lỗi hoặc android Lint sẽ thông báo bằng cách đánh dấu vàng những dòng code không hợp lệ. Để sử dụng Loader trong các phiên bản Android thấp bạn cần sử dụng activity và fragment từ thư viện Support Library. Và bạn sẽ không có hàm getLoaderManager() thay vào đó là getSupportLoaderManager().
8. 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
9. 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ứ.
(source: http://www.grokkingandroid.com/using-loaders-in-android/#how_to_deal_with_cursoradapters)
III. Tổng kết
Phù cuối cùng cũng xong. Bài dịch mình đã update thêm một số cải tiến trong android hiện tại. Hi vọng bạn sẽ có thêm kiến thức trong lập trình Android.
Cảm ơn vì bạn đã theo đến tận đây.
Hẹn gặp bạn trong bài viết tiếp theo. Có thể là tùy biến Loader trong việc lấy dữ liệu online như một HttpConnection.