跨程序共享数据
一、内容提供器
1. 简介
内容提供器主要用于在不同应用程序之间实现数据共享的功能
提供一套完整的机制,允许一个程序访问另一个程序中的数据,同时保证被访问数据的安全性
2. 与其他共享数据方式的区别
不同于文件存储和SharedPreferences存储中的两种全局可读写操作模式
内容选择器可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄露的风险
二、运行时权限
1. Android权限机制
1.1 例子
当使用广播机制时,为了要访问系统的网络状态和监听开机广播
在AndroidManifest.xml文件中添加了两句权限声明
是因为访问系统的网络状态和监听开机广播涉及了用户设备的安全性,因此必须声明
1.2 如何起到保护作用
添加了这两句权限声明之后,用户主要得到两个方面的保护
-
如果用户在低于6.0系统的设备上安装该程序,会在安装页面给出程序申请哪些程序的提醒
用户可以根据这些权限决定是否要安装该程序
-
用户可以随时在应用程序管理界面查看任意一个程序的权限申请情况
以保证应用程序不会出现滥用权限的情况
1.3 弊端—>需要运行时权限
很多我们离不开的常用软件普遍存在着滥用权限的情况
于是Android在6.0系统中加入了运行时权限的功能
用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权
1.4 Android权限分类
-
普通权限
那些不会直接威胁到用户的安全和隐私的权限
对于这部分权限申请,系统会自动帮我们进行授权,不需要用户手动操作
只需要在AndroidManifest.xml文件中添加以下权限声明
-
危险权限
那些可能会触及用户隐私,或者对设备安全性造成影响的权限
这部分权限必须由用户手动点击授权才可以,否则程序无法使用相应的功能
需要用到运行时权限
2. 在运行时申请权限
申请CALL_PHONE权限
2.1 Android6.0系统下
2.1.1 主页面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--当点击该按钮,就会去触发拨打电话的逻辑-->
<Button
android:id="@+id/make_call"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Make Call"/>
</LinearLayout>
2.1.2 主活动
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) {
try {
//为了防止程序崩溃,将所有操作都放在了异常捕获代码块中
//隐式Intent
//action被指定为Intent.ACTION_CALL,这是一个系统内置的打电话的动作
Intent intent=new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
catch (SecurityException e)
{
e.printStackTrace();
}
}
});
}
}
2.1.3 权限声明
<uses-permission android:name="android.permission.CALL_PHONE"/>
2.1.4 结果
在6.0以下的系统,能正常运行
在6.0以上的系统,会崩溃
2.2 Android6.0系统以上
运行时权限的核心就是程序运行过程中由用户授权我们去执行某些危险操作
第一步要先判断用户是否已经给我们授权过:
ContextCompat.checkSelfPermission方法:参数1Context,参数2 具体的权限名Manifest.permission.CALL_PHONE
如果已经授权:直接拨打电话
如果没有授权:
需要调用 ActivityCompat.requestPermissions 来向用户申请授权
ActivityCompat.requestPermissions:参数1 Activity的实例,参数2 String数组:申请的权限名,参数3:请求码
调用完之后,系统会弹出一个权限申请的对话框,用户选择同意与否
不管是哪种结果,最后都会回调到onRequestPerssionsResult方法中
授权结果会封装在grantResults参数中
判断授权结果,如果同意就call,不同意就弹出失败提示
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);
//6.0以上
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();
}
}
/**
*
* @param requestCode:回调函数判断的依据,请求码
* @param permissions
* @param grantResults:授权结果
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, 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:
}
}
}
三、内容选择器的使用(访问其他程序中的数据)
1. 内容选择器的两种用法
- 使用现有的内容提供器来读取和操作相应程序中的数据
- 创建自己的内容提供器给我们程序的数据提供外部访问接口
2. ContentResolver的用法
想要访问内容提供器中共享的数据,要借助ContentResolver类
可以通过Context中的getContentResolver方法获得该类的实例
ContentResolver中提供了一系列方法用于对数据进行CRUD操作
2.1 增删改查方法接收URI
ContentResolver的增删改查方法不接受表明参数,使用一个Uri参数代替,该参数被称为URI
内容URI给内容提供器中的数据建立一个标识符,由两个部分组成:authority和path
authority:
用于对不同的应用程序进行区分
一般采取程序包名的方式命名,如:com.example.app.provider
path:
用于对同一应用程序的不同表进行区分
如某程序数据库中存在两个表table1和table2,path:/table1,path:/table2
则内容URI:com.example.app.provider/table1
需要在字符串的头部加上协议声明,来辨认出这个字符串是内容URI:content://com.example.app.provider/table1
需要将内容URI字符串解析成Uri对象才可以作为参数传入
Uri uri=Uri.parse("content://com.example.app.provider/table1");
2.2 查询
2.2.1 查询
Cursor cursor=getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);
参数:
uri:指定某个程序的某张表
projection:指定查询的列名
selection:指定where的约束条件
selectionArgs:为where中的占位符提供具体的值
orderBy:指定查询结果的排序方式
2.2.2 取出数据
查询返回一个Cursor对象
读取Cursor对象中的数据:通过移动游标的位置来遍历Cursor的所有行,逐个取出每一行中相应的数据
if(cursor!=null)
{
while(cursor.moveToNext())
{
String column1=cursor.getString(cursor.getColumnIndex("column1"));
int column2=cursor.getInt(cursor.getColumnIndex("column2"));
}
}
2.3 添加
将待添加的数据组装到ContentValues中
ContentValues values=new ContentValues();
values.put("column1","text");
values.put("column2",1);
getContextResolver().insert(uri,values);
2.4 修改
如果想要更新一条数据,将column1的值清空
ContentValues values=new ContentValues();values.put("column1","");
getContentResolver().update(uri,values,"column1=? and colum2=?",new String[]{"text","1"});
2.5 删除
getContentResolver().delete(uri,"column2=?",new String[]{"1"});
3. 读取系统联系人
3.1 主页面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--读取出来的联系人信息在ListView中显示-->
<ListView
android:id="@+id/contacts_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
3.2 主活动
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
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.notifyDataSetChanged();
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
if(cursor!=null)
{
cursor.close();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode)
{
case 1:
if(grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED)
{
readContacts();
}
else
{
Toast.makeText(this,"You denied the perssion",Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
}
3.3 声明权限
<uses-permission android:name="android.permission.READ_CONTACTS"/>