ContentProvider
底层采用Binder机制
ContentProvider:将数据以安全的方式进行封装,最终提供统一的获取数据的接口供其他进程调用,从而实现跨进程的数据共享
ContentResolver:统一管理不同ContentProvider间的操作,降低操作成本和难度
权限
普通权限
在ManiFest声明
危险权限
在ManiFest声明→6.0+需要申请运行时权限(必须用户手动授权)
申请运行时权限
检查是否授权
button.setOnClickListener((View v)->{
//检查用户授权,返回PackageManager.PERMISSION_GRANTED就说明已授权
if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)!= PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},1);//弹出请求授权对话框,参数为(上下文,要请求的授权数组,请求码)
else
call();//如果已授权,就执行打电话逻辑
});
弹出对话框,无论哪种选择结果,都要来执行这个方法
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {//grantResult数组里封装的是请求结果
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)//如果结果等于“权限已授权”
call();
else
Toast.makeText(this, "未授权", Toast.LENGTH_SHORT).show();
break;
}
}
PermissionX
权限请求工具
implementation 'com.guolindev.permissionx:permissionx:1.5.0'
PermissionX.init(this)
.permissions(Manifest.permission.READ_CALENDAR,Manifest.permission.WRITE_CALENDAR)
//.explainReasonBeforeRequest() 如果有这一句,那么会在第一次询问之前就做出解释
.onExplainRequestReason((ExplainScope scope,List<String> deniedList,boolean beforeRequest)->{
scope.showRequestReasonDialog(deniedList,"这个权限很重要,建议不要拒绝","yes","no");
})//默认是在第一次询问拒绝之后做出解释,选yes就再询问一次
.onForwardToSettings((ForwardScope scope, List<String> deniedList)->{
scope.showForwardToSettingsDialog(deniedList,"您需要去设置里手动开启权限","跳转设置","拒绝");
})//默认是在第二次询问拒绝之后弹出
.request((boolean allGranted, List<String> grantedList, List<String> deniedList)->{
if(allGranted)
Toast.makeText(this,"全部授权",Toast.LENGTH_SHORT).show();
});
URI
Uniform Resource Identifier(统一资源标识符)
-
系统预置URI
-
自定义URI
通配符 * #
匹配该provider的所有表 content://com.example.app.provider/* 匹配该provider的table表的所有行 content://com.example.app.provider/table/#
MIME数据类型
Multipurpose Internet Mail Extensions(多功能Internet邮件扩充业务)
用于指定某个扩展名的文件用某种应用程序来打开
MIME类型格式:类型/子类型
text/html text/css application/pdf
以vnd开头表示父类型和子类型具有非标准的形式
如:vnd.android.cursor.dir/vnd.com.example.databasetest.content.book
辅助工具类
ContentUris
可以操作URI
//withAppendedId()作用:向URI追加一个id Uri uri = Uri.parse("content://cn.scu.myprovider/user") Uri resultUri = ContentUris.withAppendedId(uri, 7); //最终生成后的Uri为:content://cn.scu.myprovider/user/7 //parseId()作用:从URL中获取ID Uri uri = Uri.parse("content://cn.scu.myprovider/user/7") long personid = ContentUris.parseId(uri); //获取的结果为:7
UriMatcher
见下文
ContentObserver
当观察的URI指向的数据有变化时,收到通知
创建ContentObserver
ContentObserver contentObserver = new ContentObserver(handler) { @Override public boolean deliverSelfNotifications() { //如果观察的是本程序的数据库变化,需要重写该方法并返回true return true; } @Override public void onChange(boolean selfChange) { //当book表被加入了新数据,回调到这里 Toast.makeText(getContext(), "book表被加入了新数据", Toast.LENGTH_SHORT).show(); } }; //将该观察者注册 getContext().getContentResolver().registerContentObserver(uri, true, contentObserver);
记得在合适的时机取消注册(如
Activity:onDestroy()
)
getContext().getContentResolver().unregisterContentObserver(contentObserver);
在数据有变化的时候编写
//当book表被加入了新数据,通知观察者(第二个参数我感觉填啥都没有影响) getContext().getContentResolver().notifyChange(uri, null);
ContentResolver
统一管理不同ContentProvider间的操作,降低操作成本和难度
跟SQLite的CURD很相似,只是使用getContentResolver()
获取其它程序的ContentProvider并把表名替换成了URI
例:查询其它程序的数据
//参数为(Uri,查询哪些列的信息,约束,占位符内容,结果的排序方式) Cursor cursor = getContentResolver().query(Uri.parse("content://..."),null,null,null,null); if(cursor.moveToFirst()){ do { String display=cursor.getString(cursor.getColumnIndex(列名)); } while (cursor.moveToNext()); cursor.close(); }
出于安全考虑,Android 11 要求应用事先说明需要访问的其他软件包
<queries> <package android:name="com.example.databasetest" /> </queries>
创建本程序的ContentProvider
给外部提供CURD接口来访问自身的数据库
外部执行CURD方法时运行在ContentProvider进程的Binder线程池中(不是主线程)
存在多线程并发访问的问题
数据存储方式 | 是否需要线程同步 | 原因 |
---|---|---|
1个SQLite | ✘ | SQLite内部实现好了 |
多个SQLite | ✔ | SQL对象之间无法线程同步 |
内存 | ✔ |
首先使用UriMatcher对传入的URI进行格式匹配,搞清楚它的目的是哪张表或者哪条数据,给每种情形指定一个int值,便于switch
public class DatabaseProvider extends ContentProvider {
//访问码
static final int BOOK_DIR = 1;
static final int BOOK_ITEM = 2;
static final int CATEGORY_DIR = 3;
static final int CATEGORY_ITEM = 4;
private static final UriMatcher mUriMatcher;
MyDatabaseHelper mHelper;
SQLiteDatabase db;
static final String AUTHORITY="com.example.databasetest.provider";
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
mUriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
mUriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
mUriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
}
@Override
public boolean onCreate() {
//运行在ContentProvider进程的主线程,不可以执行耗时操作
mHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,3);
db = mHelper.getWritableDatabase();
return true;
}
@Override
public Uri insert(Uri uri, ContentValues values) {//Provider的insert()返回新行URI
Uri uriReturn=null;
switch (mUriMatcher.match(uri)){
case BOOK_DIR:
case BOOK_ITEM:
long newBookId=db.insert("Book",null,values);//数据库的insert()返回新行id
uriReturn=Uri.parse("content://"+AUTHORITY+"/book/"+newBookId);
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 delete(Uri uri, String selection, String[] selectionArgs) {
int deleteRows=0;
switch (mUriMatcher.match(uri)){
case BOOK_DIR:
deleteRows=db.delete("Book",selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId=uri.getPathSegments().get(1);//就是得到那个#所指代的具体id
deleteRows=db.delete("Book","id=?",new String[]{bookId});
break;
case CATEGORY_DIR:
deleteRows=db.delete("Category",selection,selectionArgs);
case CATEGORY_ITEM:
String categoryId=uri.getPathSegments().get(1);
deleteRows=db.delete("Category","id=?",new String[]{categoryId});
break;
default:
break;
}
return deleteRows;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int updateRows=0;
switch (mUriMatcher.match(uri)){
case BOOK_DIR:
updateRows=db.update("Book",values,selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId=uri.getPathSegments().get(1);
updateRows=db.update("Book",values,"id=?",new String[]{bookId});
break;
case CATEGORY_DIR:
updateRows=db.update("Category",values,selection,selectionArgs);
case CATEGORY_ITEM:
String categoryId=uri.getPathSegments().get(1);
updateRows=db.update("Category",values,"id=?",new String[]{categoryId});
break;
default:
break;
}
return updateRows;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor=null;
switch (mUriMatcher.match(uri)){
case BOOK_DIR:
//查询范围是Book表的所有数据
cursor=db.query("Book",projection,selection,selectionArgs,null,null,
sortOrder);
break;
case BOOK_ITEM:
//查询范围是Book表的某行数据
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 String getType(Uri uri) {
//返回当前URI所代表的MIME类型,格式见上文
switch (mUriMatcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.content.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.content.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.content.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.content.category";
default:
return null;
}
}
}
在Manifest中注册,可以给它加个权限
<!--指定permission属性后还要在这里声明一下--> <permission android:name="com.example.databasetest.do" android:protectionLevel="normal"/> <application ... > <provider android:name=".DatabaseProvider" android:authorities="com.example.databasetest.provider" android:permission="com.example.databasetest.do" android:enabled="true" android:exported="true" > </provider> </application>
权限还可以细分为读和写(但是不要同时有总、读、写3个权限)
<permission android:name="com.example.databasetest.read" android:protectionLevel="normal"/> <permission android:name="com.example.databasetest.write" android:protectionLevel="normal"/> <application ... > <provider ... android:readPermission="com.example.databasetest.read" android:writePermission="com.example.databasetest.write" ... > </provider> </application>
访问方必须申请相应权限才可以使用该ContentProvider
<uses-permission android:name="com.example.databasetest.do"/>
或
<uses-permission android:name="com.example.databasetest.read"/> <uses-permission android:name="com.example.databasetest.write"/>