說(shuō)說(shuō)在 Android 中如何自定義內(nèi)容提供器

1 自定義內(nèi)容提供器

首先新建一個(gè)繼承自 ContentProvider 的類,實(shí)現(xiàn)它的 6 個(gè)抽象方法:

方法 說(shuō)明
public boolean onCreate() 初始化時(shí)被調(diào)用,只有 ContentResolver 嘗試訪問(wèn)我們 APP 的程序數(shù)據(jù)時(shí)才會(huì)執(zhí)行初始化操作;在此完成創(chuàng)建與升級(jí)數(shù)據(jù)庫(kù)的操作,返回 true 表示初始化成功。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 查詢數(shù)據(jù);uri 確定要查詢的表;projection 確定查詢列;selection 和 selectionArgs 約束條件;sortOrder 確定排序。
public String getType(Uri uri) 根據(jù)傳入的的內(nèi)容 Uri 返回相應(yīng)的 MIME 類型。
public Uri insert(Uri uri, ContentValues values) 新增數(shù)據(jù);uri 確定要新增的表;values 存放需要添加的數(shù)據(jù)。
public int delete(Uri uri, String selection, String[] selectionArgs) 刪除數(shù)據(jù);uri 確定要更新的表;selection 和 selectionArgs 約束條件;返回受影響的行數(shù)。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 更新數(shù)據(jù);uri 確定要?jiǎng)h除的表;selection 和 selectionArgs 約束條件;返回受影響的行數(shù)。

內(nèi)容 URI 的寫(xiě)法類型有這些:

類型 寫(xiě)法 示例 說(shuō)明
標(biāo)準(zhǔn) content://<app 包名>/<表名> content://net.deniro.app/table 訪問(wèn)應(yīng)用中 table 表的所有數(shù)據(jù)。
加 id content://<app 包名>/<表名>/<id> content://net.deniro.app/table/1 訪問(wèn)應(yīng)用中 table 表 id 為1 的數(shù)據(jù)。

還可以使用通配符來(lái)匹配以上兩種格式的內(nèi)容 URI:

通配符 說(shuō)明
* 匹配任意長(zhǎng)度的任意字符。
# 匹配任意長(zhǎng)度的數(shù)字。

示例:

  1. content://net.deniro.app/*:訪問(wèn)應(yīng)用中任意表。
  2. content://net.deniro.app/table/#:訪問(wèn)應(yīng)用中 table 表的任意一行數(shù)據(jù)。

getType() 方法返回的 MIME 類型字符串由三個(gè)部分組成:

  • 以 vnd 開(kāi)頭。
  • 如果內(nèi)容 URI 以路徑結(jié)尾,則后接 android.cursor.dir/; 如果內(nèi)容 URI 以 id 結(jié)尾,則后接 android.cursor.item/。
  • 最后為 vnd.<authority>.<path>

比如內(nèi)容 URI 為content://net.deniro.app/table 的 MIME 類型字符串是:
vnd.android.cursor.dir/vnd.net.deniro.app.table。

2 跨 APP 數(shù)據(jù)共享

假設(shè)有一個(gè)工程管理項(xiàng)目,本身包含對(duì)人員的 CRUD 操作,現(xiàn)在把這些操作開(kāi)放給其他 APP 。

使用 IDEA 創(chuàng)建一個(gè)內(nèi)容提供器,右鍵點(diǎn)擊包名 → New → Other → Content Provider:

在彈出的對(duì)話框中,輸入類名與 URI 權(quán)限路徑:

  • Exported:是否允許外部 APP 訪問(wèn)這個(gè)內(nèi)容提供器。
  • Enabed:是否啟用這個(gè)內(nèi)容提供器。

以上兩項(xiàng)全部勾選。

/**
 * 自定義內(nèi)容提供器
 */
public class CustomContentProvider extends ContentProvider {

    private static final String TAG = "CustomContentProvider";

    /**
     * 表代碼
     */
    public static final int DIR = 0;

    /**
     * 記錄代碼
     */
    public static final int ITEM = 1;

    /**
     * URI 權(quán)限
     */
    public static final String AUTHORITY = "net.deniro.app.provider";

    /**
     * 表名
     */
    public static final String TABLE_NAME = "people";

    /**
     * 內(nèi)容 URI 前綴
     */
    public static final String CONTENT_PREFIX = "content://";

    /**
     * MIME 類型前綴
     */
    public static final String MIME_PREFIX = "vnd.";

    /**
     * MIME 類型前綴(所有記錄)
     */
    public static final String MIME_INFIX_DIR = "android.cursor.dir";

    /**
     * MIME 類型前綴(某個(gè) ID 記錄)
     */
    public static final String MIME_INFIX_ITEM = "android.cursor.item";

    private static UriMatcher matcher;

    static {
        matcher = new UriMatcher(UriMatcher.NO_MATCH);
        matcher.addURI(AUTHORITY, TABLE_NAME, DIR);
        matcher.addURI(AUTHORITY, TABLE_NAME + "/#", ITEM);
    }

    private PeopleDatabaseHelper helper;

    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate: ");
        helper = new PeopleDatabaseHelper(getContext(), "People.db", null, 3);
        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.d(TAG, "insert: "+values);
        SQLiteDatabase db = helper.getWritableDatabase();
        Uri result = null;
        switch (matcher.match(uri)) {
            case DIR:
            case ITEM:
                long id = db.insert(TABLE_NAME, null, values);
                result = Uri.parse(CONTENT_PREFIX + AUTHORITY + "/" + TABLE_NAME + "/" + id);
                break;
            default:
                break;
        }
        return result;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        SQLiteDatabase db = helper.getWritableDatabase();
        int rows = 0;
        switch (matcher.match(uri)) {
            case DIR://更新帶條件約束表記錄
                rows = db.update(TABLE_NAME, values, selection, selectionArgs);
                break;
            case ITEM://更新指定 ID 的表記錄
                String id = uri.getPathSegments().get(1);
                rows = db.update(TABLE_NAME, values, "id = ?", new String[]{id});
                break;
            default:
                break;
        }
        return rows;
    }


    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = helper.getWritableDatabase();
        int rows = 0;
        switch (matcher.match(uri)) {
            case DIR://刪除帶條件約束表記錄
                rows = db.delete(TABLE_NAME, selection, selectionArgs);
                break;
            case ITEM://刪除指定 ID 的表記錄
                String id = uri.getPathSegments().get(1);
                rows = db.delete(TABLE_NAME, "id = ?", new String[]{id});
                break;
            default:
                break;
        }
        return rows;
    }

    @Override
    public String getType(Uri uri) {
        switch (matcher.match(uri)) {
            case DIR:
                return MIME_PREFIX + MIME_INFIX_DIR + "/" + MIME_PREFIX + AUTHORITY + "." + TABLE_NAME;
            case ITEM:
                return MIME_PREFIX + MIME_INFIX_ITEM + "/" + MIME_PREFIX + AUTHORITY + "." + TABLE_NAME;
        }
        return null;
    }


    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = null;
        switch (matcher.match(uri)) {
            case DIR:
                cursor = db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case ITEM:
                String id = uri.getPathSegments().get(1);
                cursor = db.query(TABLE_NAME, projection, "id = ?", new String[]{id}, null, null, sortOrder);
                break;
            default:
                break;
        }
        return cursor;
    }
}
  • 這里在靜態(tài)代碼塊中,使用了 UriMatcher 類來(lái)實(shí)現(xiàn)匹配內(nèi)容 URI 的功能。它的 addURI() 方法接收 authority、path 和自定義碼。
  • Uri 的 getPathSegments() 會(huì)把內(nèi)容 URI 權(quán)限之后的部分以 / 進(jìn)行分割,所以索引位置為 0 存放的是路徑,索引位置為 1 存放的 id。

注意:內(nèi)容 URI 前綴是 content://,它是大小寫(xiě)敏感的, Content://contents:// 都是錯(cuò)誤的寫(xiě)法。

最后一步是配置自定義的內(nèi)容提供器,因?yàn)槲覀兪鞘褂?IDEA 添加的,所以在 AndroidManifest.xml 中已經(jīng)自動(dòng)添加咯:

<provider
    android:name=".CustomContentProvider"
    android:authorities="net.deniro.app.provider"
    android:enabled="true"
    android:exported="true"></provider>

關(guān)閉這個(gè)項(xiàng)目 A,重新建立一個(gè)新項(xiàng)目 B,在這個(gè)新項(xiàng)目 B中通過(guò)自定義內(nèi)容提供器來(lái)訪問(wèn)之前的項(xiàng)目 A。

布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="新增" />

    <Button
        android:id="@+id/query"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查詢" />

    <Button
        android:id="@+id/update"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="修改" />

    <Button
        android:id="@+id/delete"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="刪除" />
</LinearLayout>

布局很簡(jiǎn)單,放置 4 個(gè)按鈕,點(diǎn)擊它們觸發(fā)另一個(gè) APP 的相關(guān)操作。

Activity:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private String id;

    /**
     * 內(nèi)容 URI
     */
    public static final String URI = "content://net.deniro.app.provider/people";

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

        /**
         * 新增
         */
        findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ContentValues values = new ContentValues();
                values.put("name", "jack");
                values.put("age", 20);
                values.put("weight", 120.5);
                Uri uri = getContentResolver().insert(Uri.parse(URI), values);
                id = uri.getPathSegments().get(1);
            }
        });

        /**
         * 查詢
         */
        findViewById(R.id.query).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Cursor cursor = getContentResolver().query(Uri.parse(URI), null, null, null, null);
                if (cursor != null) {
                    while (cursor.moveToNext()) {
                        Log.d(TAG, "姓名: " + cursor.getString(cursor.getColumnIndex("name")));
                        Log.d(TAG, "年齡: " + cursor.getInt(cursor.getColumnIndex("age")));
                        Log.d(TAG, "體重(斤): " + cursor.getDouble(cursor.getColumnIndex("weight")));
                    }
                    cursor.close();
                }
            }
        });

        /**
         * 修改
         */
        findViewById(R.id.update).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ContentValues values = new ContentValues();
                values.put("age", 25);
                values.put("weight", 140.25);
                getContentResolver().update(Uri.parse(URI + "/" + id), values, null, null);
            }
        });

        /**
         * 刪除
         */
        findViewById(R.id.delete).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getContentResolver().delete(Uri.parse(URI + "/" + id), null, null);
            }
        });
    }
}

這些方法都是通過(guò) getContentResolver() 獲得 ContentResolver 后,調(diào)用 ContentResolver 的 CRUD 方法來(lái)實(shí)現(xiàn)相應(yīng)操作的。

進(jìn)行測(cè)試階段,首先安裝項(xiàng)目 A,然后安裝項(xiàng)目 B,在項(xiàng)目 B 中點(diǎn)擊 CRUD 按鈕:

點(diǎn)擊【新增】按鈕后,查看日志:

D/CustomContentProvider: insert: name=jack weight=120.5 age=20

點(diǎn)擊【查詢】按鈕后,查看日志:

D/MainActivity: 姓名: jack
D/MainActivity: 年齡: 20
D/MainActivity: 體重(斤): 120.5

點(diǎn)擊【修改】按鈕后,查看日志:

D/MainActivity: 姓名: jack
D/MainActivity: 年齡: 25
D/MainActivity: 體重(斤): 140.25

是不是很酷呀 O(∩_∩)O哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容