ContentProvider内容提供者
内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是Android实现跨程序共享数据的标准方式。
运行时权限
运行时权限机制
Android现在将所有的权限归成了两类,一类是普通权限,一类是危险权限。准确地讲,其实还有第三类特殊权限,不过这种权限使用得很少,因此不在本书的讨论范围之内。普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权,而不需要用户再去手动操作了,比如在BroadcastTest项目中申请的两个权限就是普通权限。危险权限则表示那些可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等,对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。
查看Android系统中完整的权限列表:Manifest.permission | Android Developers (google.cn)
程序运行时申请权限
Android6.0之前
Android6.0之前,不需要动态申请权限就可以使用危险权限
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
但需要在AndroidManifest.xml中申请静态权限
<uses-permission android:name="android.permission.CALL_PHONE" />
Android6.0以后,动态申请权限
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button makeCall = (Button) findViewById(R.id.make_call);
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.
permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new
String[]{ Manifest.permission.CALL_PHONE }, 1);
} else {
call();
}
}
});
}
private void call() {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.
PERMISSION_GRANTED) {
call();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_
SHORT).show();
}
break;
default:
break;
}
}
}
-
先判断用户是不是已经给过授权
借助的是
ContextCompat.checkSelfPermission()
方法。- checkSelfPermission() 方法:第一个参数是Context;第二个参数是具体的权限名
- 方法的返回值和
PackageManager.PERMISSION_GRANTED
做比较,相等就说明用户已经授权,不等就表示用户没有授权
-
如果已经授权,则调用相关执行逻辑
-
如果没有授权,则需要调用
ActivityCompat.requestPermissions()
方法来向用户申请授权requestPermissions()
方法- 第一个参数要求是Activity的实例
- 第二个参数是一个String 数组,存放要申
请的权限名 - 第三个参数是请求码(唯一值)这里传入了
1
-
最终都会回调到
onRequestPermissionsResult()
方法中,而授权的结果则会封装在rantResults
参数当中。这里我们只需要判断一下最后的授权结果,如果用户同意的话就调用call() 方法来拨打电话,如果用户拒绝的话我们只能放弃操作,并且弹出一条失败提示。
访问其他程序中数据
- 内容提供器的用法:
- 使用现有的内容提供器来读取和操作相应程序中的数据
- 创建自己的内容提供器给我们程序的数据提供外部访问接口
ContentResolver的基本用法
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver
类,可以通过Context
中的getContentResolver()
方法获取到该类的实例。
ContentResolver
中提供了一系列的方法用于对数据进行CRUD
操作,其中insert()
方法用于添加数据,update()
方法用于更新数据,delete()
方法用于删除数据,query()
方法用于查询数据。
-
不同于
SQLiteDatabase
,ContentResolver
中的增删改查方法都是不接收表名参数的,而是使用一个Uri
参数代替,这个参数被称为内容URI
-
内容URI
给内容提供器中的数据建立了唯一标识符-
主要由两部分组成:authority和path
-
authority是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序对应的authority就可以命名为com.example.app.provider。一般为
[PackageName].provider
-
path则是用于对同一应用程序中不同的表做区分的,通常都会添加到authority的后面。比如某个程序的数据库里存在两张表:table1和table2,这时就可以将path分别命名为/table1和/table2,然后把authority和path进行组合,内容URI就变成了com.example.app.provider/table1和com.example.app.provider/table2。不过,目前还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。
-
内容URI标准格式写法
content://com.example.app.provider/table1 content://com.example.app.provider/table2
-
-
-
-
得到内容URI字符串之后,我们还需要将它解析成
Uri 对象
才可以作为参数传入需要调用
Uri.parse()
方法Uri uri = Uri.parse("content://com.example.app.provider/table1");
-
接下来可以使用
ContentResolver
来查询其他程序 数据库中表的数据了Cursor cursor = getContentResolver().query( uri, projection, selection, selectionArgs, sortOrder);
ContentResolver.query()方法参数 对应SQL部分 描述 uri from table_name 指定查询某个应用程序下的某一张表 projection select column1, column2 指定查询的列名 selection where column = value 指定where 的约束条件 selectionArgs 为where 中的占位符提供具体的值 sortOrder order by column1, column2 指定查询结果的排序方式
- insert(uri, contentValues);
- update(uri, contentValues, “column1 = ? and column2 = ?”, new String[] {“text”, “1”});
- delete(uri, “column2 = ?”, new String[] { “1” });
读取系统联系人
public class MainActivity extends AppCompatActivity {
ArrayAdapter<String> adapter;
List<String> contactsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView contactsView = (ListView) findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this, android.R.layout. simple_list_
item_1, contactsList);
contactsView.setAdapter(adapter);
// 判断权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_
CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{ Manifest.
permission.READ_CONTACTS }, 1);
} else {
readContacts();
}
}
private void readContacts() {
Cursor cursor = null;
try {
// 查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.
Phone.CONTENT_URI, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
// 获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex
(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
// 获取联系人手机号
String number = cursor.getString(cursor.getColumnIndex
(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
}
// 通知刷新adapter
adapter.notifyDataSetChanged();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
}
// 接收权限申请返回结果
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.
PERMISSION_GRANTED) {
readContacts();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_
SHORT).show();
}
break;
default:
}
}
}
同时还需在AndroidManifest.xml
中添加权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
创建自己的内容提供器
创建内容提供器的步骤
-
新建一个类去继承
ContentProvider
的方式来创建一个自己的内容提供器。该类有6个抽象方法-
onCreate():初始化内容提供器的时候调用。。通常会在这里完成对数据库的创建和升级等操作,返回true 表示内容提供器初始化成功,返回false则表示失败。
-
query():从内容提供器中查询数据。使用uri 参数来确定查询哪张表,projection 参数用于确定查询哪些列,selection 和selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在Cursor对象中返回。
-
insert():向内容提供器中添加一条数据。使用uri 参数来确定要添加到的表,待添加的数据保存在values 参数中。添加完成后,返回一个用于表示这条新记录的URI。
-
update():更新内容提供器中已有的数据。使用uri 参数来确定更新哪一张表中的数据,新数据保存在values 参数中,selection 和selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。
-
delete():从内容提供器中删除数据。使用uri 参数来确定删除哪一张表中的数可以看到,几乎每一个方法都会带有Uri 这个参数,这个参数也正是调用ContentResolver的增删改查方法时传递过来的。而现在,我们需要对传入的Uri 参数进行解析,从中分析出调用方期望访问的表和数据。
-
getType():根据传入的内容URI来返回相应的MIME类型。
一个内容URI所对应的MIME字符串主要由3部分组成
- 必须以vnd 开头。
- 如果内容URI以路径结尾,则后接
android.cursor.dir/
;如果内容URI以id
结尾,则后接android.cursor.item/
。 - 最后接上
vnd.<authority>.<path>
。
public class MyProvider extends ContentProvider { ... @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case TABLE1_DIR: return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"; case TABLE1_ITEM: return "vnd.android.cursor.item/vnd.com.example.app.provider.table1"; case TABLE2_DIR: return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"; case TABLE2_ITEM: return "vnd.android.cursor.item/vnd.com.example.app.provider.table2"; default: break; } return null; } }
-
-
URI
参数-
标准URI写法
content://[Package Name]/[Table Name]/[ID]
-
通配符
*
:表示匹配任意长度的任意字符- 如匹配任意表:
content://[Package Name]/*
- 如匹配任意表:
#
:表示匹配任意长度的数字- 如匹配table表任意一行内容:
content://[Package Name]/table/#
- 如匹配table表任意一行内容:
-
-
UriMatcher
类:可以轻松地实现匹配内容URI的功能
- addURI() 方法: 接收3个参数,分别为
authority
、path
和一个自定义代码传进去
public class MyProvider extends ContentProvider { public static final int TABLE1_DIR = 0; public static final int TABLE1_ITEM = 1; public static final int TABLE2_DIR = 2; public static final int TABLE2_ITEM = 3; private static UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR); uriMatcher.addURI("com.example.app.provider ", "table1/#", TABLE1_ITEM); uriMatcher.addURI("com.example.app.provider ", "table2", TABLE2_DIR); uriMatcher.addURI("com.example.app.provider ", "table2/#", TABLE2_ITEM); } ... @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (uriMatcher.match(uri)) { case TABLE1_DIR: // 查询table1表中的所有数据 break; case TABLE1_ITEM: // 查询table1表中的单条数据 break; case TABLE2_DIR: // 查询table2表中的所有数据 break; case TABLE2_ITEM: // 查询table2表中的单条数据 break; default: break; } ... } ... }
- addURI() 方法: 接收3个参数,分别为
实现跨程序数据共享
public class DatabaseProvider extends ContentProvider {
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final int CATEGORY_DIR = 2;
public static final int CATEGORY_ITEM = 3;
public static final String AUTHORITY = "com.example.databasetest.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
}
@Override
public boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[]
selectionArgs, String sortOrder) {
// 查询数据
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
cursor = db.query("Book", projection, selection, selectionArgs, null,
null, sortOrder);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
cursor = db.query("Book", projection, "id = ?", new String[] { bookId },
null, null, sortOrder);
break;
case CATEGORY_DIR:
cursor = db.query("Category", projection, selection, selectionArgs,
null, null, sortOrder);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
cursor = db.query("Category", projection, "id = ?", new String[]
{ categoryId }, null, null, sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// 添加数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("Book", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" +
newBookId);
/**
* Uri uri = Uri.parse("content://org.providers.prods/word");
* Uri resultUri = ContentUris.withAppendedId(uri,5);
* // 返回resultUri为:content://org.providers.prods/word/5
* Long id = ContentUris.parseId(resultUri);
* // 返回5
*/
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = db.insert("Category", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" +
newCategoryId);
break;
default:
break;
}
return uriReturn;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[]
selectionArgs) {
// 更新数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
int updatedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
updatedRows = db.update("Book", values, selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updatedRows = db.update("Book", values, "id = ?", new String[]
{ bookId });
break;
case CATEGORY_DIR:
updatedRows = db.update("Category", values, selection,
selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
updatedRows = db.update("Category", values, "id = ?", new String[]
{ categoryId });
break;
default:
break;
}
return updatedRows;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 删除数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deletedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deletedRows = db.delete("Book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deletedRows = db.delete("Book", "id = ?", new String[] { bookId });
break;
case CATEGORY_DIR:
deletedRows = db.delete("Category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deletedRows = db.delete("Category", "id = ?", new String[]
{ categoryId });
break;
default:
break;
}
return deletedRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
}
return null;
}
}
其中还需要在AndroidManifest.xml
中注册provider
<provider
android:name=".DatabaseProvider"
android:authorities="com.example.databasetest.provider"
android:enabled="true"
android:exported="true">
</provider>
android:name
属性指定了DatabaseProvider
的类名android:authorities
属性指定了DatabaseProvider
的authority
enabled
和exported
属性则是根据我们刚才勾选的状态自动生成的,这里表示允许DatabaseProvider
被其他应用程序进行访问;Enabled 属性表示是否启用
另写一程序来调用该数据库
public class MainActivity extends AppCompatActivity {
private String newId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 添加数据
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
ContentValues values = new ContentValues();
values.put("name", "A Clash of Kings");
values.put("author", "George Martin");
values.put("pages", 1040);
values.put("price", 22.85);
Uri newUri = getContentResolver().insert(uri, values);
newId = newUri.getPathSegments().get(1);
}
});
Button queryData = (Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 查询数据
Uri uri = Uri.parse("content://com.example.databasetest. provider/book");
Cursor cursor = getContentResolver().query(uri, null, null, null,
null);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor. getColumnIndex
("name"));
String author = cursor.getString(cursor. getColumnIndex
("author"));
int pages = cursor.getInt(cursor.getColumnIndex ("pages"));
double price = cursor.getDouble(cursor. getColumnIndex
("price"));
Log.d("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
}
cursor.close();
}
}
});
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 更新数据
Uri uri = Uri.parse("content://com.example.databasetest. provider/book/" + newId);
ContentValues values = new ContentValues();
values.put("name", "A Storm of Swords");
values.put("pages", 1216);
values.put("price", 24.05);
getContentResolver().update(uri, values, null, null);
}
});
Button deleteData = (Button) findViewById(R.id.delete_data);
deleteData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 删除数据
Uri uri = Uri.parse("content://com.example.databasetest. provider/book/" + newId);
getContentResolver().delete(uri, null, null);
}
});
}
}