1 自定义内容提供器
首先新建一个继承自 ContentProvider 的类,实现它的 6 个抽象方法:
方法 | 说明 |
---|---|
public boolean onCreate() | 初始化时被调用,只有 ContentResolver 尝试访问我们 APP 的程序数据时才会执行初始化操作;在此完成创建与升级数据库的操作,返回 true 表示初始化成功。 |
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) | 查询数据;uri 确定要查询的表;projection 确定查询列;selection 和 selectionArgs 约束条件;sortOrder 确定排序。 |
public String getType(Uri uri) | 根据传入的的内容 Uri 返回相应的 MIME 类型。 |
public Uri insert(Uri uri, ContentValues values) | 新增数据;uri 确定要新增的表;values 存放需要添加的数据。 |
public int delete(Uri uri, String selection, String[] selectionArgs) | 删除数据;uri 确定要更新的表;selection 和 selectionArgs 约束条件;返回受影响的行数。 |
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) | 更新数据;uri 确定要删除的表;selection 和 selectionArgs 约束条件;返回受影响的行数。 |
内容 URI 的写法类型有这些:
类型 | 写法 | 示例 | 说明 |
---|---|---|---|
标准 | content://<app 包名>/<表名> | content://net.deniro.app/table | 访问应用中 table 表的所有数据。 |
加 id | content://<app 包名>/<表名>/<id> | content://net.deniro.app/table/1 | 访问应用中 table 表 id 为1 的数据。 |
还可以使用通配符来匹配以上两种格式的内容 URI:
通配符 | 说明 |
---|---|
* | 匹配任意长度的任意字符。 |
# | 匹配任意长度的数字。 |
示例:
content://net.deniro.app/*
:访问应用中任意表。content://net.deniro.app/table/#
:访问应用中 table 表的任意一行数据。getType() 方法返回的 MIME 类型字符串由三个部分组成:
- 以 vnd 开头。
- 如果内容 URI 以路径结尾,则后接
android.cursor.dir/
; 如果内容 URI 以 id 结尾,则后接android.cursor.item/
。 - 最后为
vnd.<authority>.<path>
。
比如内容 URI 为content://net.deniro.app/table
的 MIME 类型字符串是:
vnd.android.cursor.dir/vnd.net.deniro.app.table
。
2 跨 APP 数据共享
假设有一个工程管理项目,本身包含对人员的 CRUD 操作,现在把这些操作开放给其他 APP 。
使用 IDEA 创建一个内容提供器,右键点击包名 → New → Other → Content Provider:
在弹出的对话框中,输入类名与 URI 权限路径:
- Exported:是否允许外部 APP 访问这个内容提供器。
- Enabed:是否启用这个内容提供器。
以上两项全部勾选。
/**
* 自定义内容提供器
*/
public class CustomContentProvider extends ContentProvider {
private static final String TAG = "CustomContentProvider";
/**
* 表代码
*/
public static final int DIR = 0;
/**
* 记录代码
*/
public static final int ITEM = 1;
/**
* URI 权限
*/
public static final String AUTHORITY = "net.deniro.app.provider";
/**
* 表名
*/
public static final String TABLE_NAME = "people";
/**
* 内容 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 类型前缀(某个 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;
}
}
- 这里在静态代码块中,使用了 UriMatcher 类来实现匹配内容 URI 的功能。它的 addURI() 方法接收 authority、path 和自定义码。
- Uri 的 getPathSegments() 会把内容 URI 权限之后的部分以
/
进行分割,所以索引位置为 0 存放的是路径,索引位置为 1 存放的 id。
注意:内容 URI 前缀是 content://
,它是大小写敏感的, Content://
或 contents://
都是错误的写法。
最后一步是配置自定义的内容提供器,因为我们是使用 IDEA 添加的,所以在 AndroidManifest.xml 中已经自动添加咯:
<provider
android:name=".CustomContentProvider"
android:authorities="net.deniro.app.provider"
android:enabled="true"
android:exported="true"></provider>
关闭这个项目 A,重新建立一个新项目 B,在这个新项目 B中通过自定义内容提供器来访问之前的项目 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>
布局很简单,放置 4 个按钮,点击它们触发另一个 APP 的相关操作。
Activity:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private String id;
/**
* 内容 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);
}
});
}
}
这些方法都是通过 getContentResolver() 获得 ContentResolver 后,调用 ContentResolver 的 CRUD 方法来实现相应操作的。
进行测试阶段,首先安装项目 A,然后安装项目 B,在项目 B 中点击 CRUD 按钮:
点击【新增】按钮后,查看日志:
D/CustomContentProvider: insert: name=jack weight=120.5 age=20
点击【查询】按钮后,查看日志:
D/MainActivity: 姓名: jack
D/MainActivity: 年龄: 20
D/MainActivity: 体重(斤): 120.5
点击【修改】按钮后,查看日志:
D/MainActivity: 姓名: jack
D/MainActivity: 年龄: 25
D/MainActivity: 体重(斤): 140.25
是不是很酷呀 O(∩_∩)O哈哈~