lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Trình tải

Được giới thiệu trong Android 3.0, trình tải giúp việc tải dữ liệu không đồng bộ trong một hoạt động hoặc phân đoạn trở nên dễ dàng. Trình tải có những đặc điểm sau:

  • Chúng sẵn có cho mọi ActivityFragment.
  • Chúng cung cấp khả năng tải dữ liệu không đồng bộ.
  • Chúng theo dõi nguồn dữ liệu của mình và chuyển giao kết quả mới khi nội dung thay đổi.
  • Chúng tự động kết nối lại với con chạy của trình tải cuối cùng khi được tạo lại sau khi cấu hình thay đổi. Vì thế, chúng không cần truy vấn lại dữ liệu của mình.

Tổng quan về API Trình tải

Có nhiều lớp và giao diện có thể có liên quan trong khi sử dụng các trình tải trong một ứng dụng. Chúng được tóm tắt trong bảng này.

Lớp/Giao diện Mô tả
LoaderManager Một lớp tóm tắt được liên kết với Activity hoặc Fragment để quản lý một hoặc nhiều thực thể Loader. Nó giúp ứng dụng quản lý các thao tác chạy lâu hơn cùng với vòng đời Activity hoặc Fragment; công dụng phổ biến nhất của lớp này là khi dùng với CursorLoader, tuy nhiên, các ứng dụng được tự do ghi trình tải của chính mình để tải các kiểu dữ liệu khác.

Chỉ có một LoaderManager trên mỗi hoạt động hoặc phân đoạn. Nhưng một LoaderManager có thể có nhiều trình tải.
LoaderManager.LoaderCallbacks Một giao diện gọi lại để một máy khách tương tác với LoaderManager. Ví dụ, bạn sử dụng phương pháp gọi lại onCreateLoader() để tạo một trình tải mới.
Loader Một lớp tóm tắt có vai trò thực hiện việc tải dữ liệu không đồng bộ. Đây là lớp cơ bản cho một trình tải. Thông thường, bạn sẽ sử dụng CursorLoader, nhưng bạn có thể triển khai lớp con của chính mình. Trong khi các trình tải đang hoạt động, chúng sẽ theo dõi nguồn dữ liệu của mình và chuyển giao kết quả mới khi nội dung thay đổi.
AsyncTaskLoader Trình tải tóm tắt có chức năng cung cấp AsyncTask để thực hiện công việc.
CursorLoader Một lớp con của AsyncTaskLoader có chức năng truy vấn ContentResolver và trả về một Cursor. Lớp này triển khai giao thức Loader theo một cách chuẩn hóa để truy vấn các con chạy, xây dựng trên AsyncTaskLoader để thực hiện truy vấn con chạy trên một luồng nền sao cho nó không chặn UI của ứng dụng. Sử dụng trình tải này là cách tốt nhất để tải dữ liệu không đồng bộ từ một ContentProvider, thay vì phải thực hiện một truy vấn được quản lý thông qua phân đoạn hoặc các API của hoạt động.

Các lớp và giao diện trong bảng trên là những thành phần thiết yếu mà bạn sẽ sử dụng để triển khai một trình tải trong ứng dụng của mình. Bạn sẽ không cần tất cả chúng cho từng trình tải mà bạn tạo lập, nhưng bạn sẽ luôn cần một tham chiếu tới LoaderManager để khởi tạo một trình tải và triển khai một lớp Loader chẳng hạn như CursorLoader. Các phần sau đây trình bày với bạn cách sử dụng những lớp và giao diện này trong một ứng dụng.

Sử dụng các Trình tải trong một Ứng dụng

Phần này mô tả cách sử dụng các trình tải trong một ứng dụng Android. Một ứng dụng sử dụng trình tải thường bao gồm:

Khởi động một Trình tải

LoaderManager quản lý một hoặc nhiều thực thể Loader trong một Activity hoặc Fragment. Chỉ có một LoaderManager trên mỗi hoạt động hoặc phân đoạn.

Thông thường, bạn sẽ khởi tạo một Loader bên trong phương pháp onCreate() của hoạt động, hoặc trong phương pháp onActivityCreated() của phân đoạn. Bạn làm điều này như sau:

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);

Phương pháp initLoader() sẽ lấy những tham số sau:

  • Một ID duy nhất xác định trình tải. Trong ví dụ này, ID là 0.
  • Các tham đối tùy chọn để cung cấp cho trình tải khi xây dựng (null trong ví dụ này).
  • Triển khai LoaderManager.LoaderCallbacks, phương pháp mà LoaderManager gọi để báo cáo các sự kiện trình tải. Trong ví dụ này , lớp cục bộ triển khai giao diện LoaderManager.LoaderCallbacks, vì thế nó chuyển một tham chiếu tới chính nó, this.

Lệnh gọi initLoader() đảm bảo rằng một trình tải được khởi tạo và hiện hoạt. Nó có hai kết quả có thể xảy ra:

  • Nếu trình tải được quy định bởi ID đã tồn tại, trình tải được tạo lập cuối cùng sẽ được sử dụng lại.
  • Nếu trình tải được quy định bởi ID không tồn tại, initLoader() sẽ kích khởi phương pháp LoaderManager.LoaderCallbacksonCreateLoader(). Đây là nơi bạn triển khai mã để khởi tạo và trả về một trình tải mới. Để bàn thêm, hãy xem phần onCreateLoader.

Dù trong trường hợp nào, triển khai LoaderManager.LoaderCallbacks đã cho được liên kết với trình tải, và sẽ được gọi khi trạng thái của trình tải thay đổi. Nếu tại điểm thực hiện lệnh gọi này, hàm gọi đang trong trạng thái được khởi động của nó, và trình tải được yêu cầu đã tồn tại và đã khởi tạo dữ liệu của nó, khi đó hệ thống sẽ gọi onLoadFinished() ngay lập tức (trong khi initLoader()), vì thế bạn phải sẵn sàng khi điều này xảy ra. Xem onLoadFinished để thảo luận thêm về lệnh gọi lại này

Lưu ý rằng phương pháp initLoader() sẽ trả về Loader đã được tạo lập, nhưng bạn không cần bắt lại một tham chiếu tới nó. LoaderManager tự động quản lý vòng đời của trình tải. LoaderManager khởi động và dừng tải khi cần và duy trì trạng thái của trình tải và nội dung đi kèm của nó. Như hàm ý, bạn hiếm khi tương tác trực tiếp với các trình tải (thông qua một ví dụ về việc sử dụng các phương pháp trình tải để tinh chỉnh hành vi của một trình tải, hãy xem ví dụ LoaderThrottle). Bạn thường sử dụng nhất là các phương pháp LoaderManager.LoaderCallbacks để can thiệp vào tiến trình tải khi diễn ra một sự kiện đặc biệt. Để thảo luận thêm về chủ đề này, hãy xem phần Sử dụng Phương pháp Gọi lại LoaderManager.

Khởi động lại một Trình tải

Khi bạn sử dụng initLoader(), như trình bày bên trên, nó sử dụng một trình tải hiện hữu với ID được quy định nếu có. Nếu không có, nó sẽ tạo một trình tải. Nhưng đôi khi bạn muốn bỏ dữ liệu cũ của mình và bắt đầu lại.

Để bỏ dữ liệu cũ của mình, hãy sử dụng restartLoader(). Ví dụ, việc triển khai SearchView.OnQueryTextListener này sẽ khởi động lại trình tải khi truy vấn của người dùng thay đổi. Trình tải cần được khởi động lại sao cho nó có thể sử dụng bộ lọc tìm kiếm được điều chỉnh để thực hiện một truy vấn mới:

public boolean onQueryTextChanged(String newText) {
    // Called when the action bar search text has changed.  Update
    // the search filter, and restart the loader to do a new query
    // with this filter.
    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getLoaderManager().restartLoader(0, null, this);
    return true;
}

Sử dụng các Phương pháp Gọi lại LoaderManager

LoaderManager.LoaderCallbacks là một giao diện gọi lại cho phép một máy khách tương tác với LoaderManager.

Các trình tải, đặc biệt là CursorLoader, được kỳ vọng sẽ giữ lại dữ liệu của chúng sau khi bị dừng. Điều này cho phép ứng dụng giữ lại dữ liệu của chúng qua hoạt động hoặc các phương pháp onStop()onStart() của phân đoạn, sao cho khi người dùng quay lại một ứng dụng, họ không phải chờ dữ liệu tải lại. Bạn sử dụng các phương pháp LoaderManager.LoaderCallbacks khi cần biết khi nào thì nên tạo một trình tải mới, và để thông báo với ứng dụng khi nào thì đến lúc để dừng sử dụng dữ liệu của một trình tải.

LoaderManager.LoaderCallbacks bao gồm những phương pháp sau:

  • onLoadFinished() — Được gọi khi một trình tải được tạo trước đó đã hoàn tất việc tải.
  • onLoaderReset() — Được gọi khi một trình tải được tạo trước đó đang được đặt lại, vì thế mà khiến dữ liệu của nó không sẵn có.

Những phương pháp này được mô tả chi tiết hơn trong các phần sau.

onCreateLoader

Khi bạn định truy cập một trình tải (ví dụ, thông qua initLoader()), nó kiểm tra xem trình tải được quy định bởi ID có tồn tại không. Nếu không, nó sẽ kích khởi phương pháp LoaderManager.LoaderCallbacks onCreateLoader(). Đây là lúc bạn tạo một trình tải mới. Thông thường sẽ có một CursorLoader, nhưng bạn có thể triển khai lớp con Loader của chính mình.

Trong ví dụ này, phương pháp gọi lại onCreateLoader() sẽ tạo một CursorLoader. Bạn phải xây dựng CursorLoader bằng cách sử dụng phương pháp hàm dựng của nó mà yêu cầu trọn bộ thông tin cần thiết để thực hiện một truy vấn tới ContentProvider. Cụ thể, nó cần:

  • uri — URI của nội dung cần truy xuất.
  • dự thảo — Một danh sách các cột sẽ trả về. Việc chuyển null sẽ trả về tất cả cột, điều này không hiệu quả.
  • lựa chọn — Một bộ lọc khai báo các hàng nào sẽ trả về, có định dạng như một mệnh đề SQL WHERE (không gồm chính mệnh đề WHERE). Việc chuyển null sẽ trả về tất cả hàng cho URI đã cho.
  • selectionArgs — Bạn có thể thêm ?s vào lựa chọn, chúng sẽ được thay thế bằng các giá trị từ selectionArgs, theo thứ tự xuất hiện trong lựa chọn. Giá trị sẽ được gắn kết thành các Xâu.
  • sortOrder — Cách sắp xếp thứ tự các hàng, được định dạng như một mệnh đề SQL ORDER BY (không bao gồm chính mệnh đề ORDER BY). Việc chuyển null sẽ sử dụng thứ tự sắp xếp mặc định, điều này có thể dẫn đến kết quả không theo thứ tự.

Ví dụ:

 // If non-null, this is the current filter the user has provided.
String mCurFilter;
...
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    // This is called when a new Loader needs to be created.  This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    Uri baseUri;
    if (mCurFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(mCurFilter));
    } else {
        baseUri = Contacts.CONTENT_URI;
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + Contacts.DISPLAY_NAME + " != '' ))";
    return new CursorLoader(getActivity(), baseUri,
            CONTACTS_SUMMARY_PROJECTION, select, null,
            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}

onLoadFinished

Phương pháp này được gọi khi một trình tải được tạo trước đó đã hoàn thành việc tải của mình. Phương pháp này được bảo đảm sẽ được gọi trước khi giải phóng dữ liệu cuối cùng được cung cấp cho trình tải này. Tại điểm này, bạn nên loại bỏ mọi trường hợp sử dụng dữ liệu cũ (do nó sẽ được giải phóng sớm), nhưng không nên tự mình giải phóng dữ liệu do trình tải sở hữu dữ liệu và sẽ đảm nhận việc này.

Trình tải sẽ giải phóng dữ liệu sau khi nó biết ứng dụng đang không còn sử dụng nó nữa. Ví dụ, nếu dữ liệu là một con chạy từ một CursorLoader, bạn không nên tự mình gọi close() trên dữ liệu đó. Nếu con chạy đang được đặt trong một CursorAdapter, bạn nên sử dụng phương pháp swapCursor() sao cho Cursor cũ không bị đóng. Ví dụ:

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; ... public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in.  (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); }

onLoaderReset

Phương pháp này được gọi khi một trình tải được tạo trước đó đang được đặt lại, vì thế mà khiến dữ liệu của nó không sẵn có. Lệnh gọi lại này cho phép bạn tìm hiểu xem khi nào thì dữ liệu sẽ được giải phóng để bạn có thể loại bỏ tham chiếu của mình tới nó.  

Sự triển khai này gọi ra swapCursor() với một giá trị null:

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...

public void onLoaderReset(Loader<Cursor> loader) {
    // This is called when the last Cursor provided to onLoadFinished()
    // above is about to be closed.  We need to make sure we are no
    // longer using it.
    mAdapter.swapCursor(null);
}

Ví dụ

Lấy một ví dụ, sau đây là triển khai đầy đủ của Fragment có chức năng hiển thị một ListView chứa kết quả của một truy vấn đối với trình cung cấp nội dung danh bạ. Nó sử dụng một CursorLoader để quản lý truy vấn trên trình cung cấp.

Để một ứng dụng truy cập danh bạ của một người dùng, như minh họa trong ví dụ này, bản kê khai của nó phải bao gồm quyền READ_CONTACTS.

public static class CursorLoaderListFragment extends ListFragment
        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

    // This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter;

    // If non-null, this is the current filter the user has provided.
    String mCurFilter;

    @Override public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Give some text to display if there is no data.  In a real
        // application this would come from a resource.
        setEmptyText("No phone numbers");

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true);

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
        setListAdapter(mAdapter);

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    }

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Place an action bar item for searching.
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        SearchView sv = new SearchView(getActivity());
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
    }

    public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override public boolean onQueryTextSubmit(String query) {
        // Don't care about this.
        return true;
    }

    @Override public void onListItemClick(ListView l, View v, int position, long id) {
        // Insert desired behavior here.
        Log.i("FragmentComplexList", "Item clicked: " + id);
    }

    // These are the Contacts rows that we will retrieve.
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY,
    };
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created.  This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Uri baseUri;
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }
}

Thêm Ví dụ

Có một vài mẫu khác trong ApiDemos để minh họa cách sử dụng các trình tải:

  • LoaderCursor — Một phiên bản hoàn chỉnh của đoạn mã HTML trình bày ở trên.
  • LoaderThrottle — Một ví dụ về cách sử dụng điều chỉnh để giảm số truy vấn mà một trình cung cấp nội dung thực hiện khi dữ liệu của nó thay đổi.

Để biết thông tin về việc tải xuống và cài đặt các mẫu SDK, hãy xem phần Tải Mẫu.