Android学习笔记之ContentProvider

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个SQLiteSQLite内部实现好了
多个SQLiteSQL对象之间无法线程同步
内存

首先使用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"/>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值