36.Android 自定義ContentProvider

36.Android 自定義ContentProvider


ContentProvider 介紹

有一種情景:假如A應用存儲的數據,B也用到。那麼,要怎麼共享數據呢?

其實有很多種解決辦法,比如存在SD卡上、網絡等等。

其實Android上的相冊、聯繫人也都是Google給Android系統寫的App。也提供了對應的一套API給我們調用到其相應的數據,這就是ContentProvider。

ContentProvider是獲取數據和存儲都用的是統一的封裝接口,涉及到Uri的概念。

當然了,如果用到Uri了,我們可以使用Google提供的工具類 UriMatcherContentUris ,以下會講到這些。


ContentProvider 優點

  • 可以獲取系統原生一些App的數據。
  • 不同App之間可以共享數據。
  • 對數據進行封裝,存儲和獲取提供統一的API。

Android 自帶的ContentProvider

如果想獲取一些系統自帶App的數據,可以參考:
android.provider 包下的ContentProvider。


ContentProvider Uri 結構

content://com.camnter.content.provider/message/6

  • content:// :Scheme
  • com.camnter.content.provider :Authority(主機名),這個ContentProvider的唯一標識。
  • message :Table(表名)
  • 6 :Id

幾個例子:
content://com.camnter.content.provider/message:表示操作message表的所有記錄。

content://com.camnter.content.provider/message/6:表示操作message表的Id = 6的記錄。

content://com.camnter.content.provider/message/6/content:表示操作message表的Id = 6的記錄的content字段。


Google Uri 工具類

UriMatcher

  • public void addURI(String authority, String path, int code):添加Uri,Uri會被添加到一個ArrayList裏,提供match(Uri uri)去匹配。
  • public int match(Uri uri):根據Uri匹配改Uri是否被添加。

ContentUris

  • public static long parseId(Uri contentUri):解析Uri取得Id。
  • public static Uri.Builder appendId(Uri.Builder builder, long id):添加Id。
  • public static Uri withAppendedId(Uri contentUri, long id):添加Id。

UriMatcher 解析

ContentUris的源碼特別簡單,整個類就調用了Uri類的兩個方法 T T# 。

所以就看看UriMatcher源碼吧。

由於對其他類的依賴不多,我就自定義了一份UriMatcher

import android.net.Uri;

import java.util.ArrayList;
import java.util.List;

/**
Utility class to aid in matching URIs in content providers.

<p>To use this class, build up a tree of <code>UriMatcher</code> objects.
For example:
<pre>
    private static final int PEOPLE = 1;
    private static final int PEOPLE_ID = 2;
    private static final int PEOPLE_PHONES = 3;
    private static final int PEOPLE_PHONES_ID = 4;
    private static final int PEOPLE_CONTACTMETHODS = 7;
    private static final int PEOPLE_CONTACTMETHODS_ID = 8;

    private static final int DELETED_PEOPLE = 20;

    private static final int PHONES = 9;
    private static final int PHONES_ID = 10;
    private static final int PHONES_FILTER = 14;

    private static final int CONTACTMETHODS = 18;
    private static final int CONTACTMETHODS_ID = 19;

    private static final int CALLS = 11;
    private static final int CALLS_ID = 12;
    private static final int CALLS_FILTER = 15;

    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static
    {
        sURIMatcher.addURI("contacts", "people", PEOPLE);
        sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID);
        sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES);
        sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID);
        sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
        sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
        sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE);
        sURIMatcher.addURI("contacts", "phones", PHONES);
        sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER);
        sURIMatcher.addURI("contacts", "phones/#", PHONES_ID);
        sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS);
        sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID);
        sURIMatcher.addURI("call_log", "calls", CALLS);
        sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER);
        sURIMatcher.addURI("call_log", "calls/#", CALLS_ID);
    }
</pre>
<p>Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, paths can start
 with a leading slash.  For example:
<pre>
        sURIMatcher.addURI("contacts", "/people", PEOPLE);
</pre>
<p>Then when you need to match against a URI, call {@link #match}, providing
the URL that you have been given.  You can use the result to build a query,
return a type, insert or delete a row, or whatever you need, without duplicating
all of the if-else logic that you would otherwise need.  For example:
<pre>
    public String getType(Uri url)
    {
        int match = sURIMatcher.match(url);
        switch (match)
        {
            case PEOPLE:
                return "vnd.android.cursor.dir/person";
            case PEOPLE_ID:
                return "vnd.android.cursor.item/person";
... snip ...
                return "vnd.android.cursor.dir/snail-mail";
            case PEOPLE_ADDRESS_ID:
                return "vnd.android.cursor.item/snail-mail";
            default:
                return null;
        }
    }
</pre>
instead of:
<pre>
    public String getType(Uri url)
    {
        List<String> pathSegments = url.getPathSegments();
        if (pathSegments.size() >= 2) {
            if ("people".equals(pathSegments.get(1))) {
                if (pathSegments.size() == 2) {
                    return "vnd.android.cursor.dir/person";
                } else if (pathSegments.size() == 3) {
                    return "vnd.android.cursor.item/person";
... snip ...
                    return "vnd.android.cursor.dir/snail-mail";
                } else if (pathSegments.size() == 3) {
                    return "vnd.android.cursor.item/snail-mail";
                }
            }
        }
        return null;
    }
</pre>
*/
public class UriMatcher
{
    public static final int NO_MATCH = -1;
    /**
     * Creates the root node of the URI tree.
     * 創建Uri樹的根節點
     *
     * @param code the code to match for the root URI
     */
    public UriMatcher(int code)
    {
        mCode = code;
        mWhich = -1;
        mChildren = new ArrayList<UriMatcher>();
        mText = null;
    }

    private UriMatcher()
    {
        mCode = NO_MATCH;
        mWhich = -1;
        mChildren = new ArrayList<UriMatcher>();
        mText = null;
    }

    /**
     * Add a URI to match, and the code to return when this URI is
     * matched. URI nodes may be exact match string, the token "*"
     * that matches any text, or the token "#" that matches only
     * numbers.
     * 添加一個URI匹配,並且這裏添加code值會在這個URI匹配成功的時候返回。URI節點可能精確匹配字符串,
     * token爲“*”的時候會匹配爲text,token爲“#”的時候只會匹配爲數字
     * <p>
     * Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
     * this method will accept a leading slash in the path.
     *
     * @param authority the authority to match
     *                  ContentProvider的authority
     *
     * @param path the path to match. * may be used as a wild card for
     * any text, and # may be used as a wild card for numbers.
     *             匹配路徑,*可能是作爲一個通配符用於任何文本,和#可能用作通配符數字。
     *
     * @param code the code that is returned when a URI is matched
     * against the given components. Must be positive.
     *             該URI匹配成功時返回的code
    */
    public void addURI(String authority, String path, int code)
    {
        if (code < 0) {
            throw new IllegalArgumentException("code " + code + " is invalid: it must be positive");
        }

        String[] tokens = null;
        if (path != null) {
            String newPath = path;
            // Strip leading slash if present.
            if (path.length() > 0 && path.charAt(0) == '/') {
                newPath = path.substring(1);
            }
            tokens = newPath.split("/");
        }

        int numTokens = tokens != null ? tokens.length : 0;
        UriMatcher node = this;
        for (int i = -1; i < numTokens; i++) {
            String token = i < 0 ? authority : tokens[i];
            ArrayList<UriMatcher> children = node.mChildren;
            int numChildren = children.size();
            UriMatcher child;
            int j;
            for (j = 0; j < numChildren; j++) {
                child = children.get(j);
                if (token.equals(child.mText)) {
                    node = child;
                    break;
                }
            }
            if (j == numChildren) {
                // Child not found, create it
                child = new UriMatcher();
                if (token.equals("#")) {
                    child.mWhich = NUMBER;
                } else if (token.equals("*")) {
                    child.mWhich = TEXT;
                } else {
                    child.mWhich = EXACT;
                }
                child.mText = token;
                node.mChildren.add(child);
                node = child;
            }
        }
        node.mCode = code;
    }

    /**
     * Try to match against the path in a url.
     * 嘗試匹配一個url的路徑。
     *
     * @param uri       The url whose path we will match against.
     *                  將匹配的url路徑。
     *
     * @return  The code for the matched node (added using addURI),
     * or -1 if there is no matched node.
     *          匹配節點設置的code(在使用addURI時,給對應URI設置code),
     *          或者沒有匹配節點,則反悔-1。
     */
    public int match(Uri uri)
    {
        final List<String> pathSegments = uri.getPathSegments();
        final int li = pathSegments.size();

        UriMatcher node = this;

        if (li == 0 && uri.getAuthority() == null) {
            return this.mCode;
        }

        for (int i=-1; i<li; i++) {
            String u = i < 0 ? uri.getAuthority() : pathSegments.get(i);
            ArrayList<UriMatcher> list = node.mChildren;
            if (list == null) {
                break;
            }
            node = null;
            int lj = list.size();
            for (int j=0; j<lj; j++) {
                UriMatcher n = list.get(j);
          which_switch:
                switch (n.mWhich) {
                    case EXACT:
                        if (n.mText.equals(u)) {
                            node = n;
                        }
                        break;
                    case NUMBER:
                        int lk = u.length();
                        for (int k=0; k<lk; k++) {
                            char c = u.charAt(k);
                            if (c < '0' || c > '9') {
                                break which_switch;
                            }
                        }
                        node = n;
                        break;
                    case TEXT:
                        node = n;
                        break;
                }
                if (node != null) {
                    break;
                }
            }
            if (node == null) {
                return NO_MATCH;
            }
        }

        return node.mCode;
    }

    private static final int EXACT = 0;
    private static final int NUMBER = 1;
    private static final int TEXT = 2;

    private int mCode;
    private int mWhich;
    private String mText;
    private ArrayList<UriMatcher> mChildren;
}

以上註釋還有使用,Google告訴我們怎麼使用UriMatcher的範例代碼。


自定義 ContentProvider

代碼都重複看過,都寫上了註釋

BaseContentProvider

public abstract class BaseContentProvider extends ContentProvider {

    // 單一數據的MIME類型字符串應該以vnd.android.cursor.item/開頭
    protected static final String MIME_SINGLE = "vnd.android.cursor.item/";

    // 數據集的MIME類型字符串則應該以vnd.android.cursor.dir/開頭
    protected static final String MIME_MULTIPLE = "vnd.android.cursor.dir/";

    /**
     * Implement this to initialize your content provider on startup.
     * This method is called for all registered content providers on the
     * application main thread at application launch time.  It must not perform
     * lengthy operations, or application startup will be delayed.
     * <p/>
     * <p>You should defer nontrivial initialization (such as opening,
     * upgrading, and scanning databases) until the content provider is used
     * (via {@link #query}, {@link #insert}, etc).  Deferred initialization
     * keeps application startup fast, avoids unnecessary work if the provider
     * turns out not to be needed, and stops database errors (such as a full
     * disk) from halting application launch.
     * <p/>
     * <p>If you use SQLite, {@link SQLiteOpenHelper}
     * is a helpful utility class that makes it easy to manage databases,
     * and will automatically defer opening until first use.  If you do use
     * SQLiteOpenHelper, make sure to avoid calling
     * {@link SQLiteOpenHelper#getReadableDatabase} or
     * {@link SQLiteOpenHelper#getWritableDatabase}
     * from this method.  (Instead, override
     * {@link SQLiteOpenHelper#onOpen} to initialize the
     * database when it is first opened.)
     *
     * @return true if the provider was successfully loaded, false otherwise
     */
    @Override
    public abstract boolean onCreate();

    /**
     * Implement this to handle query requests from clients.
     * This method can be called from multiple threads, as described in
     * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
     * and Threads</a>.
     * <p/>
     * Example client call:<p>
     * <pre>// Request a specific record.
     * Cursor managedCursor = managedQuery(
     * ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2),
     * projection,    // Which columns to return.
     * null,          // WHERE clause.
     * null,          // WHERE clause value substitution
     * People.NAME + " ASC");   // Sort order.</pre>
     * Example implementation:<p>
     * <pre>// SQLiteQueryBuilder is a helper class that creates the
     * // proper SQL syntax for us.
     * SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
     * <p/>
     * // Set the table we're querying.
     * qBuilder.setTables(DATABASE_TABLE_NAME);
     * <p/>
     * // If the query ends in a specific record number, we're
     * // being asked for a specific record, so set the
     * // WHERE clause in our query.
     * if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){
     * qBuilder.appendWhere("_id=" + uri.getPathLeafId());
     * }
     * <p/>
     * // Make the query.
     * Cursor c = qBuilder.query(mDb,
     * projection,
     * selection,
     * selectionArgs,
     * groupBy,
     * having,
     * sortOrder);
     * c.setNotificationUri(getContext().getContentResolver(), uri);
     * return c;</pre>
     *
     * @param uri           The URI to query. This will be the full URI sent by the client;
     *                      if the client is requesting a specific record, the URI will end in a record number
     *                      that the implementation should parse and add to a WHERE or HAVING clause, specifying
     *                      that _id value.
     * @param projection    The list of columns to put into the cursor. If
     *                      {@code null} all columns are included.
     * @param selection     A selection criteria to apply when filtering rows.
     *                      If {@code null} then all rows are included.
     * @param selectionArgs You may include ?s in selection, which will be replaced by
     *                      the values from selectionArgs, in order that they appear in the selection.
     *                      The values will be bound as Strings.
     * @param sortOrder     How the rows in the cursor should be sorted.
     *                      If {@code null} then the provider is free to define the sort order.
     * @return a Cursor or {@code null}.
     */
    @Override
    public abstract Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);

    /**
     * Implement this to handle requests for the MIME type of the data at the
     * given URI.  The returned MIME type should start with
     * <code>vnd.android.cursor.item</code> for a single record,
     * or <code>vnd.android.cursor.dir/</code> for multiple items.
     * This method can be called from multiple threads, as described in
     * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
     * and Threads</a>.
     * <p/>
     * <p>Note that there are no permissions needed for an application to
     * access this information; if your content provider requires read and/or
     * write permissions, or is not exported, all applications can still call
     * this method regardless of their access permissions.  This allows them
     * to retrieve the MIME type for a URI when dispatching intents.
     *
     * @param uri the URI to query.
     * @return a MIME type string, or {@code null} if there is no type.
     */
    @Override
    public abstract String getType(Uri uri);

    /**
     * Implement this to handle requests to insert a new row.
     * As a courtesy, call {@link ContentResolver#notifyChange(Uri, ContentObserver) notifyChange()}
     * after inserting.
     * This method can be called from multiple threads, as described in
     * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
     * and Threads</a>.
     *
     * @param uri    The content:// URI of the insertion request. This must not be {@code null}.
     * @param values A set of column_name/value pairs to add to the database.
     *               This must not be {@code null}.
     * @return The URI for the newly inserted item.
     */
    @Override
    public abstract Uri insert(Uri uri, ContentValues values);

    /**
     * Implement this to handle requests to delete one or more rows.
     * The implementation should apply the selection clause when performing
     * deletion, allowing the operation to affect multiple rows in a directory.
     * As a courtesy, call {@link ContentResolver#notifyChange(Uri, ContentObserver) notifyChange()}
     * after deleting.
     * This method can be called from multiple threads, as described in
     * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
     * and Threads</a>.
     * <p/>
     * <p>The implementation is responsible for parsing out a row ID at the end
     * of the URI, if a specific row is being deleted. That is, the client would
     * pass in <code>content://contacts/people/22</code> and the implementation is
     * responsible for parsing the record number (22) when creating a SQL statement.
     *
     * @param uri           The full URI to query, including a row ID (if a specific record is requested).
     * @param selection     An optional restriction to apply to rows when deleting.
     * @param selectionArgs
     * @return The number of rows affected.
     * @throws SQLException
     */
    @Override
    public abstract int delete(Uri uri, String selection, String[] selectionArgs);

    /**
     * Implement this to handle requests to update one or more rows.
     * The implementation should update all rows matching the selection
     * to set the columns according to the provided values map.
     * As a courtesy, call {@link ContentResolver#notifyChange(Uri, ContentObserver) notifyChange()}
     * after updating.
     * This method can be called from multiple threads, as described in
     * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
     * and Threads</a>.
     *
     * @param uri           The URI to query. This can potentially have a record ID if this
     *                      is an update request for a specific record.
     * @param values        A set of column_name/value pairs to update in the database.
     *                      This must not be {@code null}.
     * @param selection     An optional filter to match rows to update.
     * @param selectionArgs
     * @return the number of rows affected.
     */
    @Override
    public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);

}

MessageSQLiteHelper

public class MessageSQLiteHelper extends SQLiteOpenHelper {

    private static final String TAG = "MessageSQLiteHelper";

    private static final String DB_NAME = "message.db";
    private static final int VERSION = 1;

    public static final String TB_MESSAGE = "tb_message";
    public static final String TB_MESSAGE_SQL = "CREATE TABLE IF NOT EXISTS " + TB_MESSAGE + "(_id  INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," +
            " content text)";

    private static MessageSQLiteHelper ourInstance;

    public MessageSQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    public static MessageSQLiteHelper getInstance(Context context) {
        if (ourInstance == null) ourInstance = new MessageSQLiteHelper(context);
        return ourInstance;
    }

    public MessageSQLiteHelper(Context context) {
        this(context, DB_NAME, null, VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(TB_MESSAGE_SQL);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.i(TAG, "onUpgrade");
    }

}

MessageContentProvider

public class MessageContentProvider extends BaseContentProvider {

    // 主機名
    private static final String AUTHORITY = "com.camnter.content.provider";

    // Message uri
    public static final Uri MESSAGE_URI = Uri.parse("content://" + AUTHORITY + "/message");

    // 數據集的MIME類型字符串則應該以vnd.android.cursor.dir/開頭
    private static final String TOPIC_SINGLE = MIME_SINGLE + "message";

    // 單一數據的MIME類型字符串應該以vnd.android.cursor.item/開頭
    private static final String TOPIC_MULTIPLE = MIME_MULTIPLE + "message";

    // 有id匹配碼
    private static final int MESSAGE = 6;
    // 有無匹配碼
    public static final int MESSAGES = 7;


    // Message SQLite helper
    private MessageSQLiteHelper messageSQLiteHelper;

    private static final UriMatcher messageUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        // content://com.camnter.content.provider/message
        messageUriMatcher.addURI(AUTHORITY, "message", MESSAGES);
        // content://com.camnter.content.provider/message/#
        messageUriMatcher.addURI(AUTHORITY, "message/#", MESSAGE);
    }

    @Override
    public boolean onCreate() {
        this.messageSQLiteHelper = MessageSQLiteHelper.getInstance(this.getContext());
        return true;
    }

    @Override
    public String getType(Uri uri) {
        int match = messageUriMatcher.match(uri);
        switch (match) {
            case MESSAGE:
                return TOPIC_SINGLE;
            case MESSAGES:
                return TOPIC_MULTIPLE;
            default:
                return null;
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = this.messageSQLiteHelper.getWritableDatabase();
        long id;
        switch (messageUriMatcher.match(uri)) {
            case MESSAGE:
                id = db.insert(MessageSQLiteHelper.TB_MESSAGE, "content", values);
                this.getContext().getContentResolver().notifyChange(uri, null);
                return ContentUris.withAppendedId(uri, id);
            case MESSAGES:
                id = db.insert(MessageSQLiteHelper.TB_MESSAGE, "content", values);
                String path = uri.toString();
                this.getContext().getContentResolver().notifyChange(uri, null);
                // 新id的Uri替換舊id的Uri
                return Uri.parse(path.substring(0, path.lastIndexOf("/")) +"/"+ id);
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = this.messageSQLiteHelper.getWritableDatabase();
        int count;
        switch (messageUriMatcher.match(uri)) {
            case MESSAGE:
                count = db.delete(MessageSQLiteHelper.TB_MESSAGE, selection, selectionArgs);
                this.getContext().getContentResolver().notifyChange(uri, null);
                break;
            case MESSAGES:
                long messageId = ContentUris.parseId(uri);
                // 指定id
                String where = "_id=" + messageId;
                // 把其它條件附加上
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";
                count = db.delete(MessageSQLiteHelper.TB_MESSAGE, where, selectionArgs);
                this.getContext().getContentResolver().notifyChange(uri, null);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = this.messageSQLiteHelper.getWritableDatabase();
        int count;
        switch (messageUriMatcher.match(uri)) {
            case MESSAGE:
                long messageId = ContentUris.parseId(uri);
                // 指定id
                String where = "_id=" + messageId;
                // 把其它條件附加上
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";
                count = db.update(MessageSQLiteHelper.TB_MESSAGE, values, where, selectionArgs);
                this.getContext().getContentResolver().notifyChange(uri, null);
                break;
            case MESSAGES:
                count = db.update(MessageSQLiteHelper.TB_MESSAGE, values, selection, selectionArgs);
                this.getContext().getContentResolver().notifyChange(uri, null);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return count;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = this.messageSQLiteHelper.getReadableDatabase();
        Cursor cursor;
        switch (messageUriMatcher.match(uri)) {
            case MESSAGE:
                long messageId = ContentUris.parseId(uri);
                // 指定id
                String where = "_id=" + messageId;
                // 把其它條件附加上
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";
                cursor = db.query(MessageSQLiteHelper.TB_MESSAGE, projection, where, selectionArgs, null, null, sortOrder);
                this.getContext().getContentResolver().notifyChange(uri, null);
                return cursor;
            case MESSAGES:
                cursor = db.query(MessageSQLiteHelper.TB_MESSAGE, projection, selection, selectionArgs, null, null, sortOrder);
                this.getContext().getContentResolver().notifyChange(uri, null);
                return cursor;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }

}

CustomContentProviderActivity

public class CustomContentProviderActivity extends AppCompatActivity {


    private RecyclerView providerRV;
    private ProviderRecyclerViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_custom_content_provider);

        this.getContentResolver().registerContentObserver(MessageContentProvider.MESSAGE_URI, true, new MessageProviderObserver(new Handler()));

        this.providerRV = (RecyclerView) this.findViewById(R.id.provider_rv);
        this.adapter = new ProviderRecyclerViewAdapter(this.getContentResolver(), MessageContentProvider.MESSAGE_URI);
        this.providerRV.setAdapter(this.adapter);
        this.initRecyclerView();
    }


    private void initRecyclerView() {
        // 實例化LinearLayoutManager
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        // 設置垂直佈局
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);

        // 設置佈局管理器
        this.providerRV.setLayoutManager(linearLayoutManager);

        this.providerRV.setItemAnimator(new DefaultItemAnimator());
        this.providerRV.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));

        // 使RecyclerView保持固定的大小,該信息被用於自身的優化
        this.providerRV.setHasFixedSize(true);

        ArrayList<SQLiteData> allData = new ArrayList<>();
        allData.add(new SQLiteData());
        this.adapter.setList(allData);
        this.adapter.notifyDataSetChanged();
    }

    private class MessageProviderObserver extends ContentObserver {

        /**
         * Creates a content observer.
         *
         * @param handler The handler to run {@link #onChange} on, or null if none.
         */
        public MessageProviderObserver(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            super.onChange(selfChange, uri);
        }
    }


    /**
     * Provider RecyclerView Adapter
     */
    private class ProviderRecyclerViewAdapter extends EasyRecyclerViewAdapter implements View.OnClickListener {

        private static final int ITEM_PROVIDER_OPERATION = 0;
        private static final int ITEM_PROVIDER_DATA = 1;

        private ContentResolver resolver;
        private Uri uri;

        public ProviderRecyclerViewAdapter(ContentResolver resolver, Uri uri) {
            this.resolver = resolver;
            this.uri = uri;
        }

        @Override
        public int[] getItemLayouts() {
            return new int[]{R.layout.item_content_provider_operation, R.layout.item_content_provider_data};
        }

        @Override
        public void onBindRecycleViewHolder(EasyRecyclerViewHolder easyRecyclerViewHolder, int position) {
            int itemType = this.getRecycleViewItemType(position);
            switch (itemType) {
                case ITEM_PROVIDER_OPERATION:
                    easyRecyclerViewHolder.findViewById(R.id.provider_add_bt).setOnClickListener(this);
                    easyRecyclerViewHolder.findViewById(R.id.provider_del_bt).setOnClickListener(this);
                    easyRecyclerViewHolder.findViewById(R.id.provider_mod_bt).setOnClickListener(this);
                    easyRecyclerViewHolder.findViewById(R.id.provider_query_bt).setOnClickListener(this);
                    break;
                case ITEM_PROVIDER_DATA:
                    ProviderData data = (ProviderData) this.getList().get(position);
                    TextView idTV = easyRecyclerViewHolder.findViewById(R.id.provider_id_tv);
                    TextView contentTV = easyRecyclerViewHolder.findViewById(R.id.provider_content_tv);
                    idTV.setText(data.id + "");
                    contentTV.setText(data.content + "");
                    break;
            }
        }

        @Override
        public int getRecycleViewItemType(int i) {
            if (i == 0) {
                return ITEM_PROVIDER_OPERATION;
            } else {
                return ITEM_PROVIDER_DATA;
            }
        }

        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.provider_add_bt: {
                    ContentValues values = new ContentValues();
                    values.put("content", "Save you from anything");
                    this.resolver.insert(this.uri, values);
                    this.refresh();
                    break;
                }
                case R.id.provider_del_bt: {
                    this.resolver.delete(this.uri, null, null);
                    this.refresh();
                    break;
                }
                case R.id.provider_mod_bt: {
                    List<ProviderData> allData = this.queryAll();
                    int firstId = allData.get(0).id;
                    ContentValues values = new ContentValues();
                    values.put("content", UUID.randomUUID().toString());
                    String path = this.uri.toString();
                    this.resolver.update(Uri.parse(path.substring(0, path.lastIndexOf("/")) +"/message/"+ firstId), values, null, null);
                    this.refresh();
                    break;
                }
                case R.id.provider_query_bt: {
                    this.refresh();
                    break;
                }
            }
        }

        private void refresh() {
            List<ProviderData> l = this.queryAll();
            l.add(0,new ProviderData());
            this.setList(l);
            this.notifyDataSetChanged();
        }

        private List<ProviderData> queryAll() {
            List<ProviderData> allData = new ArrayList<>();
            Cursor result = this.resolver.query(this.uri, null, null, null, null);
            for (result.moveToFirst(); !result.isAfterLast(); result.moveToNext()) {
                ProviderData data = new ProviderData();
                data.id = result.getInt(result.getColumnIndex("_id"));
                data.content = result.getString(result.getColumnIndex("content"));
                allData.add(data);
            }
            result.close();
            return allData;
        }

    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章