简介
ContentProvider主要用于在不同的应用之间实现数据共享,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时保证数据的安全性。
ContentProvider是Android实现跨程序数据共享的标准方式。
运行时权限
Android权限机制
权限申请即在Manifest文件中引入<uses-permission>元素:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zcd.flcdemo">
<!--声明接收开机广播的权限-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
</manifest>
在Android6之后加入了运行时权限功能,即用户不需要在安装程序时就一次性授权所有的申请,而是可以在软件的使用过程中再对某一项权限申请进行授权。
危险权限表示那些可能触及用户隐私或者对设备安全性造成影响的权限,对于这部分权限申请,必须由用户手动授权才行,否则程序无法使用相应的功能。
Android10中的危险权限:
权限组名 | 权限名 |
CALENDAR(日历) | READ_CALENDAR |
WRITE_CALENDAR | |
CALL_LOG(电话日志) | READ_CALL_LOG |
WRITE_CALL_LOG | |
PROCESS_OUTGOING_CALLS | |
CAMERA(相机) | CAMERA |
CONTACTS(联系人) | READ_CONTACTS |
WRITE_CONTACTS | |
GET_ACCOUNTS | |
LOCATION(地理坐标) | ACCESS_FINE_LOCATION |
ACCESS_COARSE_LOCATION | |
ACCESS_BACKGROUND_LOCATION | |
MICROPHONE(麦克风) | RECORD_AUDIO |
PHONE(手机状态) | READ_PHONE_STATE |
READ_PHONE_NUMBER | |
CALL_PHONE | |
ANSWER_PHONE_CALLS | |
ADD_VOICEMAIL | |
S | |
ACCEPT_HANDOVER | |
SENSORS(传感器) | BODY_SENSORS |
ACTIVITY_RECOGNITION | ACTIVITY_RECOGNITION |
SMS(短信) | SEND_SMS |
RECEIVE_SMS | |
READ_SMS | |
RECEIVE_WAP_SMS | |
RECEIVE_SMS | |
STORAGE(存储) | READ_EXTERNAL_STORAGE |
WRITE_EXTERNAL_STORAGE | |
ACCESS_MEDIA_LOCATION |
在程序运行时申请权限
以拨打电话为例:
//拨打电话
btnCallPhone = findViewById(R.id.btn_call_phone);
btnCallPhone.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (checkSelfPermission(Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(FirstActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
} else {
callPhone();
}
}
});
因为拨打电话是危险权限需要动态申请权限:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
callPhone();
} else {
Toast.makeText(this, "拒绝了权限申请", Toast.LENGTH_LONG).show();
}
}
}
拨打电话:
private void callPhone() {
try {
Intent callPhone = new Intent(Intent.ACTION_CALL);
Uri phone = Uri.parse("tel:10086");
callPhone.setData(phone);
if (checkSelfPermission(Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
startActivity(callPhone);
}catch (Exception e){
e.printStackTrace();
}
}
检查权限的checkSelfPermission()方法有两个参数:
- 第一个参数
Context,上下文环境; - 第二个参数
具体的权限名称;
用于请求权限的ActivityCompat.requestPermissions()方法有三个参数:
- 第一个参数
Activity实例; - 第二个参数
String数组,数组中为要申请的权限名; - 第三个参数
请求码,需要是唯一的;
用于检查申请结果的方法onRequestPermissionsResult(),在申请结果回调方法中需要先验证请求码是不是之前请求的,然后检查grantResults数组的第一个元素是否与PackageManager.PERMISSION_GRANTED相同,如果相同那证明请求权限成功,否则失败。
访问其他程序中的数据
ContentProvider的使用一般有两种:第一种是使用现有的ContentProvider读取和操作相应程序中的数据,第二种就是创建自己的ContentProvider供其他程序访问。
ContentProvider的基本用法
在应用程序中如果想要访问ContentProvider中的共享数据,必须要借助ContentResolver类,可以通过Context中的getContentResolver()方法来获取实例对象;
ContentResovler中提供了增删改查的方法,分别为insert(),delete(),update(),query();
ContentResolver的CRUD方法不再是接收表名,而是接收Uri;
内容Uri给ContentProvider中的数据建立了唯一的标识符,由两部分组成:authority和path;
示例Uri格式:
content://<package name>/<name>
示例代码:
Uri uri = Uri.parse("content://<package name>/<name>");
query()方法的参数:
query()方法参数 | 对应的SQL部分 | 描述 |
uri | from table_name | 指定查询哪个应用程序下的表名 |
projection | select col1,col2,… | 指定查询的列名 |
selection | where col = value | 指定where的约束条件 |
selectionArgs | 为where中的占位符提供具体值 | |
sortOrder | order by col1,col2,… | 指定查询结果的排序方式 |
insert()方法的参数:
- uri
指定插入哪个应用下的表名 - values
插入的值
update()方法的参数:
update()方法参数 | 对应SQL部分 | 描述 |
uri | update table_name | 指定更新哪个应用程序下的表名 |
values | set col1 = val1,col2 = val2,… | 指定更新的列及新值 |
selection | where col = value | 指定where的约束条件 |
selectionArgs | 为where中的占位符提供具体值 |
delete()方法的参数:
- uri
指定要删除哪个应用下的表名; - selection
指定删除的约束条件; - selectionArgs
为约束条件中的占位符提供具体值;
读取系统联系人
在Manifest中添加权限:
<!--获取读取联系人的权限-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
在需要的地方引入:
//获取联系人
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(FirstActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 2);
} else {
readContacts();
}
对动态申请权限的结果进行处理:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 2){
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts();
} else {
Toast.makeText(this, "拒绝了权限申请", Toast.LENGTH_LONG).show();
}
}
}
获取联系人:
private void readContacts(){
Cursor queryResult = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
null,
null,
null
);
while (queryResult.moveToNext()){
//联系人姓名
String displayName = queryResult.getString(queryResult.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取联系人手机号
String number = queryResult.getString(queryResult.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.i(TAG, "联系人姓名:" + displayName + ",手机号:" + number);
}
queryResult.close();
}
创建ContentProvider的步骤
创建ContentProvider,需要继承ContentProvider类并重写其中的6个抽象方法:
- onCreate()
初始化ContentProvider时调用,用以创建或者升级数据库;
返回true时表示初始化成功,返回false时表示初始化失败; - query()
用于从ContentProvider中查询数据;
uri参数用于确定查询哪张表,projection参数表示查询哪些列,selection和selectionArgs表示约束条件及其值,sortOrder参数用于对结果进行排序,查询结果被放在Cursor对象中返回; - insert()
用于向ContentProvider中添加数据;
uri参数用于确定要添加到的表,添加的数据保存在values参数中;
添加完成后,返回一个用于表示此记录的Uri; - update()
用于更新ContentProvider中现有数据;
uri参数用于确定要添加到的表,values中保存新值,selection和selectionArgs表示约束条件及其值;
更新完成后,返回受影响的行数; - delete()
用于删除ContentProvider中现有数据;
uri参数用于确定要添加到的表,selection和selectionArgs表示约束条件及其值;
删除完成后,返回受影响的行数; - getType()
根据传入的内容URI返回相应的MIME类型;
一个内容URI对应的MIME字符串主要由3部分组成,Android对此部分做出了如下规定:
- 必须以vnd开头;
- 如果内容URI以路径结尾,则后接android.cursor.dir/;
如果内容URI以id结尾,则后接android.cursor.item/; - 最后接上vnd.<authority>.<path>;
例如,以下Uri:
content://com.zcd.flcdemo/book
对应的MEMI类型可以写为:
vnd.android.cursor.dir/vnd.com.zcd.flcdemo.book
再例如,以下Uri:
content://com.zcd.flcdemo/book/1
对应的MEMI类型可以写为:
vnd.android.cursor.item/vnd.com.zcd.flcdemo.book
实现跨程序数据共享
在Manifest文件中注册该ContentProvider:
<!--注册ContentProvider-->
<provider
android:authorities="com.zcd.flcdemo.provider"
android:name=".Utils.MyContentProvider"
android:exported="true"
android:enabled="true"/>
新建并重写相关方法:
public class MyContentProvider extends ContentProvider {
private final String TAG = "MyContentProvider";
private final String BOOK_TABLE_NAME = "Book";
private final String AUTHORITY = "com.zcd.flcdemo.provider";
private static final int BOOK_TABLE_DIR = 0;
private static final int BOOK_ITEM = 1;
private UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private FLCDBHelper dbHelper = null;
@Override
public boolean onCreate() {
uriMatcher.addURI(AUTHORITY, "book", BOOK_TABLE_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
dbHelper = new FLCDBHelper(getContext(), "Book.db", null,1);
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor resultCursor = null;
switch (uriMatcher.match(uri)){
case BOOK_TABLE_DIR:
resultCursor = db.query(BOOK_TABLE_NAME, null, null, null, null, null, null);
break;
case BOOK_ITEM:
resultCursor = db.query(BOOK_TABLE_NAME, strings, s, strings1, null,null,null,null);
break;
default:
Log.i(TAG, "query: 未知uri");
}
return resultCursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)){
case BOOK_TABLE_DIR:
return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd." + AUTHORITY + ".book";
default:
return null;
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
long newBookID = db.insert(BOOK_TABLE_NAME, null, contentValues);
return Uri.parse("content://" + AUTHORITY + "/book/" + newBookID);
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
return db.delete(BOOK_TABLE_NAME, s, strings);
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
return db.update(BOOK_TABLE_NAME, contentValues, s, strings);
}
}
测试方法:
private void testContentProvider() {
//插入数据
insertByContentProvider();
//查询数据
queryByContentProvider();
//更新数据
updateByContentProvider();
//查询数据
queryByContentProvider();
//删除数据
deleteByContentProvider();
//查询数据
queryByContentProvider();
}
private void queryByContentProvider(){
Uri queryUri = Uri.parse("content://com.zcd.flcdemo.provider/book");
Cursor resCursor = getContentResolver().query(queryUri, null, null, null, null);
if (resCursor == null){
Log.i(TAG, "queryByContentProvider: 目标数据库不存在");
return;
}
StringBuilder res = new StringBuilder();
while (resCursor.moveToNext()){
String author = resCursor.getString(resCursor.getColumnIndex("author"));
double price = resCursor.getDouble(resCursor.getColumnIndex("price"));
int pages = resCursor.getInt(resCursor.getColumnIndex("pages"));
String name = resCursor.getString(resCursor.getColumnIndex("name"));
res.append("[name=").append(name)
.append(",price=").append(price)
.append(",pages=").append(pages)
.append(",author=").append(author)
.append("]");
}
resCursor.close();
Log.i(TAG, "queryByContentProvider: 全部图书:" + res.toString());
}
private void insertByContentProvider(){
Uri insertUri = Uri.parse("content://com.zcd.flcdemo.provider/book");
ContentValues values = new ContentValues();
values.put("author", "Bruce Eckel");
values.put("price", 89.00);
values.put("pages", 800);
values.put("name", "Thinking In Java");
Uri resUri = getContentResolver().insert(insertUri, values);
if (resUri == null){
Log.i(TAG, "insertByContentProvider: 通过ContentProvider插入新书未成功");
}else {
Log.i(TAG, "insertByContentProvider: 通过ContentProvider插入的新书id:" + resUri.getPathSegments().get(1));
}
}
private void updateByContentProvider(){
Uri updateUri = Uri.parse("content://com.zcd.flcdemo.provider/book/1");
ContentValues values = new ContentValues();
values.put("author", "Casablanca");
values.put("price", 99.99);
values.put("pages", 60);
values.put("name", "Casablanca");
int updateRows = getContentResolver().update(updateUri, values, null, null);
Log.i(TAG, "updateByContentProvider: 更新数据个数:" + updateRows);
}
private void deleteByContentProvider(){
Uri deleteUri = Uri.parse("content://com.zcd.flcdemo.provider/book/1");
int updateRows = getContentResolver().delete(deleteUri, null, null);
Log.i(TAG, "deleteByContentProvider: 删除数据个数:" + updateRows);
}