内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。
不同于文件存储和SharedPreferences存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。
运行时权限
用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。
Android 权限机制
Android现在将所有的权限归成了两类,一类是普通权限,一类是危险权限。
- 普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权;
- 危险权限则表示那些可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等。
对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。
下列为Android 中的危险权限(9组24个权限):
注意:每当要使用一个权限时,可以先到这张表中来查一下,如果是属于这张表中的权限,那么就需要进行运行时权限处理,如果不在这张表中,那么只需要在AndroidManifest.xml
文件中添加一下权限声明就可以了。
表格中每个危险权限都属于一个权限组,我们在进行运行时权限处理时使用的是权限名,但是用户一旦同意授权了,那么该权限所对应的权限组中所有的其他权限也会同时被授权。
在程序运行时申请权限
例如申请拨打电话的权限:
首先添加权限声明:
<uses-permission android:name="android.permission.CALL_PHONE" />
然后在需要拨打电话的地方:
/* 判断用户是否已经授权 */
if (ContextCompat.checkSelfPermission
(MainActivity.this, Manifest.permission.CALL_PHONE) !=
PackageManager.PERMISSION_GRANTED){
/* 向用户申请授权 */
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CALL_PHONE}, 1);
}else {
/* 判断为已授权,直接拨打电话 */
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
e.printStackTrace();
}
}
- 第一步先判断用户是否已经授权;
- 若已授权则直接拨打电话;
- 若没有授权则向用户申请授权。
ActivityCompat.requestPermissions()
方法传入三个参数,分别是Activity 的实例、权限名的String 数组、请求码(唯一即可)
访问其他程序中的数据
内容提供器的用法一般有两种:
- 使用现有的内容提供器来读取和操作相应程序中的数据;
- 创建自己的内容提供器给我们程序的数据提供外部访问接口。
若一个应用程序通过内容提供器对其数据提供了外部访问接口,那么任何其他的应用程序就都可以对这部分数据进行访问。
ContentResolver 的基本用法
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver
类。
可以通过Context
中的getContentResolver()
方法获取到该类的实例。
ContentResolver
中提供了一系列的方法用于对数据进行CRUD操作,其中insert()
方法 用于添加数据,update()
方法用于更新数据,delete()
方法用于删除数据,query()
方法用于查询数据。
不同于SQLiteDatabase
,ContentResolver
中的增删改查方法都是不接收表名参数的,而是使用一个Uri
参数代替,这个参数被称为内容URI。
内容URI 给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:authority
和path
。
-
authority
是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。 -
path
则是用于对同一应用程序中不同的表做区分的,通常都会添加到authority
的后面。
比如某个程序的包名是com.example.app
,那么该程序对应的authority
就可以命名为com.example.app.provider
。数据库里存在两张表:table1
和table2
,这时就可以将path
分别命名为/table1
和/table2
内容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")
查询
Cursor cursor = getContentResolver().query (
uri,
projection,
selection,
selectionArgs,
sortOrder);
查询完成后返回的仍然是一个Cursor
对象,这时我们就可以将数据从Cursor
对象中逐个读取出来了。
读取的思路仍然是通过移动游标的位置来遍历Cursor
的所有行,然后再取出每一行中相应列的数据:
if(cursor != null){
while(cursor.moveToNext()){
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
添加
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
更新
ContentValues values = new ContentValues();
values.put("column1", "text2");
getContentResolver().update(uri, values, "column = ? and column2 = ?", new String[]{"text", "1"});
使用了selection 和selectionArgs 参数来对想要更新的数据进行约束,以防止所有的行都受到影响。
删除
getContentResolver().delete(uri, "column2 = ?", new String[]{"1"});
获取手机联系人信息
首先在AndroidManifest.xml
文件中声明读取联系人权限:
<uses-permission android:name="android.permission.READ_CONTACTS" />
然后为程序构造一个RecyclerView
用来显示联系人:
/* RecyclerView 的子项类 */
class ContractMan_Item {
String mName;
String mPhone_number;
ContractMan_Item(String name, String phone_number) {
this.mName = name;
this.mPhone_number = phone_number;
}
String getName() {
return mName;
}
String getPhone_number() {
return mPhone_number;
}
}
/* RecyclerView 的构造类 */
static class ContractMan_Adapter extends RecyclerView.Adapter<ContractMan_Adapter.ViewHolder> {
private List<ContractMan_Item> mContractMan_Item;
/* 每次添加联系人到RecyclerView 中时,利用ViewHolder 获取其子项中的控件映射 */
static class ViewHolder extends RecyclerView.ViewHolder {
TextView name_tv;
TextView phone_number_tv;
ViewHolder(@NonNull View itemView) {
super(itemView);
name_tv = itemView.findViewById(R.id.name_string);
phone_number_tv = itemView.findViewById(R.id.phone_number_string);
}
}
ContractMan_Adapter(List<ContractMan_Item> contractMan_items) {
mContractMan_Item = contractMan_items;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate
(R.layout.contract_man_recyclerview, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
/* 依次获取每一个联系人List 的子项对象 */
ContractMan_Item contractMan_item = mContractMan_Item.get(position);
/* 依次对每一个联系人子项进行赋值 */
holder.name_tv.setText(contractMan_item.getName());
holder.phone_number_tv.setText(contractMan_item.getPhone_number());
}
@Override
public int getItemCount() {
return mContractMan_Item.size();
}
}
RecyclerView
构建完成之后就应该在onCreate() 方法中使用。
RecyclerView recyclerView = findViewById(R.id.contract_man);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter = new ContractMan_Adapter(ContractMan_List);
recyclerView.setAdapter(adapter);
然后搞个按钮,添加点击事件,响应就是获取读取联系人权限:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
/* 判断是否取得读取联系人信息权限 */
if (ContextCompat.checkSelfPermission
(MainActivity.this, Manifest.permission.READ_CONTACTS) !=
PackageManager.PERMISSION_GRANTED) {
/* 请求读取联系人权限 */
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_CONTACTS}, 1);
} else {
Cursor cursor = null;
try{
/* ContactsContract.CommonDataKinds.Phone 类已经做好了封装,提供了一个CONTENT_URI 常量 */
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));
ContractMan_List.add(new ContractMan_Item(displayName, number));
}
adapter.notifyDataSetChanged();
}
} catch (Exception e){
e.printStackTrace();
} finally {
if (cursor != null){
cursor.close();
}
}
}
}
});
好了,大功告成: