Sử dụng CursorLoader mà không có ContentProvider


107

Tài liệu SDK Android cho biết startManagingCursor()phương pháp đó đã bị loại bỏ:

Phương pháp này không được dùng nữa. Sử dụng lớp CursorLoader mới với LoaderManager để thay thế; điều này cũng có sẵn trên các nền tảng cũ hơn thông qua gói tương thích Android. Phương pháp này cho phép hoạt động quản lý vòng đời của Con trỏ đã cho cho bạn dựa trên vòng đời của hoạt động. Có nghĩa là, khi hoạt động bị dừng, nó sẽ tự động gọi hủy kích hoạt () trên Con trỏ đã cho và khi khởi động lại sau đó, nó sẽ gọi yêu cầu () cho bạn. Khi hoạt động bị hủy, tất cả các Con trỏ được quản lý sẽ tự động bị đóng. Nếu bạn đang nhắm mục tiêu HONEYCOMB trở lên, hãy cân nhắc sử dụng LoaderManager để thay thế, có sẵn qua getLoaderManager ()

Vì vậy, tôi muốn sử dụng CursorLoader. Nhưng làm thế nào tôi có thể sử dụng nó với tùy chỉnh CursorAdaptervà không ContentProvider, khi tôi cần URI trong hàm tạo của CursorLoader?


@Alex Lockwood tại sao chúng tôi đang sử dụng CursorAdapter mà không có ContentProvider, vui lòng gợi ý cho tôi stackoverflow.com/questions/20419278/…

tại sao chúng ta đang sử dụng mà không cần CursorAdapter ContentProvider tôi xin đề nghị stackoverflow.com/questions/20419278/...

Câu trả lời:


155

Tôi đã viết một CursorLoader đơn giản không cần trình cung cấp nội dung:

import android.content.Context;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;

/**
 * Used to write apps that run on platforms prior to Android 3.0. When running
 * on Android 3.0 or above, this implementation is still used; it does not try
 * to switch to the framework's implementation. See the framework SDK
 * documentation for a class overview.
 *
 * This was based on the CursorLoader class
 */
public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
    private Cursor mCursor;

    public SimpleCursorLoader(Context context) {
        super(context);
    }

    /* Runs on a worker thread */
    @Override
    public abstract Cursor loadInBackground();

    /* Runs on the UI thread */
    @Override
    public void deliverResult(Cursor cursor) {
        if (isReset()) {
            // An async query came in while the loader is stopped
            if (cursor != null) {
                cursor.close();
            }
            return;
        }
        Cursor oldCursor = mCursor;
        mCursor = cursor;

        if (isStarted()) {
            super.deliverResult(cursor);
        }

        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
            oldCursor.close();
        }
    }

    /**
     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
     * will be called on the UI thread. If a previous load has been completed and is still valid
     * the result may be passed to the callbacks immediately.
     * <p/>
     * Must be called from the UI thread
     */
    @Override
    protected void onStartLoading() {
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }

    /**
     * Must be called from the UI thread
     */
    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor cursor) {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        if (mCursor != null && !mCursor.isClosed()) {
            mCursor.close();
        }
        mCursor = null;
    }
}

Nó chỉ cần AsyncTaskLoader lớp. Một trong những Android 3.0 trở lên hoặc một trong những đi kèm với gói tương thích.

Tôi cũng đã viết mộtListLoader cái tương thích với LoadManagervà được sử dụng để lấy một java.util.Listbộ sưu tập chung .


13
Đã tìm thấy một ví dụ mã hay sử dụng điều này - bitbucket.org/ssutee/418496_mobileapp/src/fc5ee705a2fd/demo/… - thấy nó rất hữu ích!
Shushu

@Cristian Cảm ơn vì ví dụ. Giấy phép liên kết với lớp học của bạn là gì. Làm thế nào nó có thể được sử dụng lại?
codinguser

2
Giấy phép là Apache 2.0; bạn có thể sử dụng lại nó ở bất kỳ đâu / khi nào bạn muốn. Hãy cho tôi biết nếu bạn có bất kỳ cải tiến nào.
Cristian

14
Công cụ tuyệt vời! Người dùng cần phải nhận thức một giới hạn, đó là nó không có cơ chế để làm mới về thay đổi dữ liệu (như bốc hàng có nghĩa vụ phải làm)
emmby

1
@Jadeye ở đây bạn có người đàn ông: ListLoaderSupportListLoader
Cristian

23

Viết trình tải của riêng bạn sử dụng lớp cơ sở dữ liệu của bạn thay vì trình cung cấp nội dung. Cách dễ nhất là chỉ lấy nguồn của CursorLoaderlớp từ thư viện tương thích và thay thế các truy vấn của trình cung cấp bằng các truy vấn đến lớp trợ giúp db của riêng bạn.


1
Đây là cách dễ nhất theo ý kiến ​​của tôi. Trong ứng dụng của tôi, tôi đã tạo ra một CursorLoaderdescendat để quản lý một con trỏ SQLite, Appart từ các nhà xây dựng Tôi chỉ cần thiết để ghi đè lên các loadInBackgroundphương pháp để thay thế các truy vấn cung cấp dịch vụ với truy vấn con trỏ của tôi
Jose_GD

14

SimpleCursorLoader là một giải pháp đơn giản, tuy nhiên nó không hỗ trợ cập nhật trình tải khi dữ liệu thay đổi. CommonsWare có thư viện loaderex bổ sung SQLiteCursorLoader và hỗ trợ truy vấn lại các thay đổi dữ liệu.

https://github.com/commonsguy/cwac-loaderex


2
Tuy nhiên, để sử dụng truy vấn lại tự động, bạn cần sử dụng cùng một bộ tải cho giao diện người dùng cũng như cho các bản cập nhật, hạn chế khả năng sử dụng của nó cho các dịch vụ nền.
ge0rg

12

Tùy chọn thứ ba sẽ chỉ là ghi đè loadInBackground:

public class CustomCursorLoader extends CursorLoader {
    private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();

    @Override
    public Cursor loadInBackground() {
        Cursor cursor = ... // get your cursor from wherever you like

        if (cursor != null) {
            // Ensure the cursor window is filled
            cursor.getCount();
            cursor.registerContentObserver(mObserver);
        }

        return cursor;
    }
};

Điều này cũng sẽ xử lý việc truy vấn lại con trỏ của bạn khi cơ sở dữ liệu thay đổi.

Chỉ báo trước: Bạn sẽ phải xác định một người quan sát khác, vì Google với sự khôn ngoan vô hạn đã quyết định đặt gói hàng của họ ở chế độ riêng tư. Nếu bạn đặt lớp vào cùng một gói với lớp ban đầu (hoặc lớp compat), bạn thực sự có thể sử dụng trình quan sát ban đầu. Người quan sát là một vật thể rất nhẹ và không được sử dụng ở bất kỳ nơi nào khác, vì vậy điều này không tạo ra nhiều khác biệt.


Quan sát của tôi trong quá trình thử nghiệm nhanh là registerContentObserver sẽ chỉ được gọi đối với con trỏ nếu con trỏ được nhắm mục tiêu đến Nhà cung cấp nội dung. Bạn có thể xác nhận / phủ nhận điều này không?
Nick Campion

1
Nó không nhất thiết phải là một ContentProvider. Nhưng con trỏ cần phải được đăng ký với một tiểu thông báo (setNotificationUri) và sau đó nó cần được ai đó thông báo (thường là ContentProvider, nhưng có thể là bất kỳ thứ gì) bằng cách gọi ContentResolver.notifyChange.
Timo Ohr 20/12/12

4
Vâng. trên CustomLoader của bạn loadInBackground() , trước khi trả lại con trỏ, hãy nói rằng cursor.setNotificationUri(getContext().getContentResolver(), uri);tiểu có thể chỉ từ Chuỗi ngẫu nhiên như Uri.parse("content://query_slot1"). Có vẻ như nó không quan tâm đến bồn tiểu có thực sự tồn tại hay không. Và khi tôi thực hiện thao tác trên DB. Nói getContentResolver().notifyChange(uri, null);sẽ làm thủ thuật. Sau đó, tôi có thể tạo một vài "rãnh tiểu truy vấn" trong một tệp liên tục cho ứng dụng có số lượng truy vấn nhỏ. Tôi thử nghiệm chèn bản ghi DB trong thời gian chạy và nó có vẻ hoạt động nhưng tôi vẫn nghi ngờ đó là hoạt động tốt. Bất kì lời đề nghị nào?
Yeung

Tôi đang sử dụng phương pháp này với gợi ý của @Yeung và mọi thứ đều hoạt động, bao gồm cả việc tự động tải lại con trỏ khi cập nhật cơ sở dữ liệu.
DavidH

không cần nó bất kỳ unregisterContentObserver?
GPack

2

Phương án thứ ba do Timo Ohr đề xuất, cùng với ý kiến ​​của Yeung, đưa ra câu trả lời đơn giản nhất (dao cạo của Occam). Dưới đây là một ví dụ về một lớp hoàn chỉnh phù hợp với tôi. Có hai quy tắc để sử dụng lớp này.

  1. Mở rộng lớp trừu tượng này và triển khai các phương thức getCursor () và getContentUri ().
  2. Bất kỳ lúc nào cơ sở dữ liệu bên dưới thay đổi (ví dụ: sau khi chèn hoặc xóa), hãy nhớ gọi

    getContentResolver().notifyChange(myUri, null);

    trong đó myUri là cùng một được trả về từ việc triển khai phương thức getContentUri () của bạn.

Đây là mã cho lớp mà tôi đã sử dụng:

package com.example.project;

import android.content.Context;
import android.database.Cursor;
import android.content.CursorLoader;
import android.content.Loader;

public abstract class AbstractCustomCursorLoader extends CursorLoader
  {
    private final Loader.ForceLoadContentObserver mObserver = new Loader.ForceLoadContentObserver();

    public AbstractCustomCursorLoader(Context context)
      {
        super(context);
      }

    @Override
    public Cursor loadInBackground()
      {
        Cursor cursor = getCursor();

        if (cursor != null)
          {
            // Ensure the cursor window is filled
            cursor.getCount();
            cursor.registerContentObserver(mObserver);
          }

        cursor.setNotificationUri(getContext().getContentResolver(), getContentUri());
        return cursor;
      }

    protected abstract Cursor getCursor();
    protected abstract Uri getContentUri();
  }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.