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ù)字。 |
示例:
-
content://net.deniro.app/*:訪問(wèn)應(yīng)用中任意表。 -
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哈哈~