01. 内容提供器简介
- 跨程序共享数据,内容提供器 Content Provider 主要用于在不同的应用程序之间是实现数据共享功能。它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。
- 内容提供器可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。
02. 运行时权限
Android 6.0 以下
版本安装时授权,不授权不安装。可在应用管理界面查看权限申请情况。Android 6.0 及以上
版本运行时授权,不授权部分功能不能用。可在应用管理界面管理权限授权或不授权。权限分类
- 普通权限
- 危险权限
- 特殊权限
每个危险权限都属于一个权限组,申请的某个权限被授权时,该组所有权限也会同时被授权。
请求权限核心方法
ContextCompat.checkSelfPermission(@NonNull Context context, @NonNull String permission)
检查是否有权限ActivityCompat.requestPermissions(Activity activity, String[] permissions, int requestCode)
请求权限onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
权限请求结果的回调/** * 点击按钮执行操作 */ public void request(View view) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {// 判断没有权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1);// 请求权限[上下文][权限数组集合][请求码] return; } else {// 判断有权限 callPhone(); } } /** * 请求权限用户操作后回调函数 * * @param requestCode 请求码 * @param permissions 权限数组集合 * @param grantResults 授权情况数组集合 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { callPhone(); } else { ToastUtil.showShortToast(this, "You denied the permission"); } break; } }
03. 访问其他程序的数据
读取系统联系人
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));// 手机号 } } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close();// 关闭游标 } } }
ContentResolver 的基本用法
通过
Context
中的getContentResolver()
方法获取到ContentResolver
的实例。利用
ContentResolver
实例进行数据的CRUD
操作insert()
方法进行添加
数据update()
方法进行更新
数据delete()
方法进行删除
数据query()
方法进行查询
数据
不同于
SQLite 的是方法都不接收表名参数,而是使用一个Uri
参数代替。内容 URI 给内容提供器中的数据建立了唯一标识符,主要由两部分组成:
authority
和path
authority
用于对不同的应用程序做区分,采用包名进行命名path
则是用于对同一应用不同表名进行区分,添加在authority
之后schema
协议添加于头部
String uriString = "content://com.just.first/table"; Uri uri = Uri.parse(uriString);
查询数据
Cursor cursor = getContentResolver().query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
- 查询返回
Cursor
对象
- 查询返回
query() 方法参数 对应 SQL 部分 描述 uri from table_name 指定查询某应用程序的某张表 projection select column1, column2 指定查询的列名 selection where column = value 指定 where 的约束条件 selectionArgs - 为 where 中的占位符提供具体的值 sortOrder order by column1, column2 指定查询结果的排序方式 添加数据
getContentResolver().insert(Uri url, ContentValues values);
- 同样使用
ContentValues
键值对进行数据的封装
- 同样使用
修改数据
getContentResolver().update(Uri uri, ContentValues values, String where, String[] selectionArgs)
删除数据
getContentResolver().delete(Uri uri, ContentValues values, String where, String[] selectionArgs)
04. 创建自己的内容提供器
自定义内容提供器继承
ContentProvider
实现 6 个抽象方法
/** * 7.4.1 自定义内容提供器 * * @author JustDo23 */ public class FirstContentProvider extends ContentProvider { /** * 初始化内容提供器。完成数据库的创建和升级操作。[只有当存在 ContentResolver 尝试访问时才会初始化] * * @return [true, 初始化成功][false,初始化失败] */ @Override public boolean onCreate() { return false; } /** * 从内容提供器查询数据。 * * @param uri 指定查询哪张表 * @param projection 确定查询哪些列 * @param selection 约束查询哪些行 * @param selectionArgs 为约束赋值 * @param sortOrder 查询结果排序 * @return 游标对象 */ @Nullable @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } /** * 向内容提供器中添加数据。 * * @param uri 指定哪张表 * @param values 待添加数据键值对 * @return 返回一个用户表示这条新纪录的 URI */ @Nullable @Override public Uri insert(Uri uri, ContentValues values) { return null; } /** * 更新内容提供器中已有数据。 * * @param uri 指定哪张表 * @param values 待更新数据键值对 * @param selection 约束更新哪些行 * @param selectionArgs 为约束赋值 * @return 返回受影响的行数 */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } /** * 从内容提供器中删除数据。 * * @param uri 指定哪张表 * @param selection 约束删除哪些行 * @param selectionArgs 为约束赋值 * @return 返回被删除的行数 */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } /** * 返回 MIME 类型 * * @param uri 指定哪张表 * @return 返回 MIME 类型 */ @Nullable @Override public String getType(Uri uri) { return null; } }
通配符
一个标准的内容 URI 写法
content://com.just.first/table
表示访问应用
com.just.first
中的table
数据表。还可以在其后添加一个id
content://com.just.first/table/23
表示访问表中
id
为23
的数据。内容 URI
的格式主要有以上两种,以路径结尾就表示期望访问该表中的所有数据,以 id 结尾就表示期望访问该表中拥有相应 id 的数据。可以使用通配符来分别匹配这两种格式的内容 URI。- 星号表示匹配任意长度的任意字符
- 井号表示匹配任意长度的数字
一个能够匹配任意表的内容 URI 格式可以写成
content://com.just.first/*
一个能够匹配表中任意一行数据的内容 URI 格式可以写成
content://com.just.first/table/#
通配符使用
public class FirstContentProvider extends ContentProvider { public static final int TABLE_1_DIR = 0;// 自定义码 public static final int TABLE_1_ITEM = 1; public static final int TABLE_2_DIR = 2; public static final int TABLE_2_ITEM = 3; public static UriMatcher uriMatcher;// 用于匹配的对象 public static final String PACKAGE_NAME = "com.just.first";// 主包名 static {// 静态代码块 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);// 用于匹配的对象 uriMatcher.addURI(PACKAGE_NAME, "table1", TABLE_1_DIR);// 添加路径 uriMatcher.addURI(PACKAGE_NAME, "table1/#", TABLE_1_ITEM);// 可以使用通配符 uriMatcher.addURI(PACKAGE_NAME, "table2", TABLE_2_DIR); uriMatcher.addURI(PACKAGE_NAME, "table2/#", TABLE_2_ITEM); } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (uriMatcher.match(uri)) {// 进行匹配并返回相应的自定义码 case TABLE_1_DIR: LogUtils.e("查询 table1 表中的所有数据"); break; case TABLE_1_ITEM: LogUtils.e("查询 table1 表中的单条数据"); break; case TABLE_2_DIR: LogUtils.e("查询 table2 表中的所有数据"); break; case TABLE_2_ITEM: LogUtils.e("查询 table2 表中的单条数据"); break; } return null; } }
关于类型
getType()
方法是所有内容提供器必须提供的一个方法,用于获取相应的 MIME 类型。一个内容 URI 所对应的
MIME
字符串主要由 3 部分组成必须
以 vnd 开头- 如果 URI 以
路径
结尾则后接android.cursor.dir/
- 如果 URI 以
id
结尾则后接android.cursor.item/
- 最后接上
vnd.<authority>.<path>
内容 URI
content://com.just.first/table
返回 MIME 类型
vnd.android.cursor.dir/vnd.com.just.first.table
内容 URI
content://com.just.first/table/23
返回 MIME 类型
vnd.android.cursor.item/vnd.com.just.first.table
根据以上内容重写
getType()
方法
数据安全问题
因为所有的 CRUD 操作都一定要匹配到相应的内容 URI 格式才能进行,而我们当然不可能向 UriMatcher 中添加隐私数据的 URI,所以这部分数据根本无法被外部程序访问到,安全问题也就不存在了。
05. 实现跨程序数据共享
跨进程访问时不能直接使用 Toast
使用内容提供器需要进行注册
<provider android:name=".chapter07.DataBaseProvider" android:authorities="com.just.first.provider" android:enabled="true" android:exported="true" />
06. 进阶 Git
忽略文件
项目目录下
.gitignore
是忽略文件,允许用户将指定的文件排除在版本控制之外。查看状态
$ git status
查看修改内容
$ git diff
其后可以指定文件来查看该文件的更改记录
- 加号代表新增
- 减号代表删除
撤销未添加的修改
$ git checkout fileName
- 前提是还没有执行
add
命令
- 前提是还没有执行
撤销未提交的修改
$ git reset HEAD
- 前提是执行了
add
命令但还没有执行commit
命令
- 前提是执行了
查看提交记录
$ git log
- 提交记录包含
提交 id
及提交人
及提交日期
及提交描述
这4个信息
$ git log id -l
- 命令后为
提交 id
及小写-L
查看该 ID 的记录
$ git log id -l -p
- 查看该 ID 的修改内容
- 提交记录包含
07. 小结
- 运行时权限
- 内容提供者