复习Android——ContentProvider

简介

ContentProvider主要用于在不同的应用之间实现数据共享,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时保证数据的安全性。

ContentProvider是Android实现跨程序数据共享的标准方式。

运行时权限

Android权限机制

权限申请即在Manifest文件中引入<uses-permission>元素:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zcd.flcdemo">
    <!--声明接收开机广播的权限-->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
</manifest>

在Android6之后加入了运行时权限功能,即用户不需要在安装程序时就一次性授权所有的申请,而是可以在软件的使用过程中再对某一项权限申请进行授权。

危险权限表示那些可能触及用户隐私或者对设备安全性造成影响的权限,对于这部分权限申请,必须由用户手动授权才行,否则程序无法使用相应的功能。

Android10中的危险权限:

权限组名权限名
CALENDAR(日历)READ_CALENDAR
WRITE_CALENDAR
CALL_LOG(电话日志)READ_CALL_LOG
WRITE_CALL_LOG
PROCESS_OUTGOING_CALLS
CAMERA(相机)CAMERA
CONTACTS(联系人)READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
LOCATION(地理坐标)ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
ACCESS_BACKGROUND_LOCATION
MICROPHONE(麦克风)RECORD_AUDIO
PHONE(手机状态)READ_PHONE_STATE
READ_PHONE_NUMBER
CALL_PHONE
ANSWER_PHONE_CALLS
ADD_VOICEMAIL
S
ACCEPT_HANDOVER
SENSORS(传感器)BODY_SENSORS
ACTIVITY_RECOGNITIONACTIVITY_RECOGNITION
SMS(短信)SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_SMS
RECEIVE_SMS
STORAGE(存储)READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
ACCESS_MEDIA_LOCATION

在程序运行时申请权限

以拨打电话为例:

//拨打电话
btnCallPhone = findViewById(R.id.btn_call_phone);
btnCallPhone.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (checkSelfPermission(Manifest.permission.CALL_PHONE)
            != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(FirstActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
        } else {
            callPhone();
        }
    }
});

因为拨打电话是危险权限需要动态申请权限:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == 1) {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            callPhone();
        } else {
            Toast.makeText(this, "拒绝了权限申请", Toast.LENGTH_LONG).show();
        }
    }
}

拨打电话:

private void callPhone() {
    try {
        Intent callPhone = new Intent(Intent.ACTION_CALL);
        Uri phone = Uri.parse("tel:10086");
        callPhone.setData(phone);
        if (checkSelfPermission(Manifest.permission.CALL_PHONE)
            != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        startActivity(callPhone);
    }catch (Exception e){
        e.printStackTrace();
    }
}

检查权限的checkSelfPermission()方法有两个参数:

  • 第一个参数

    Context,上下文环境;

  • 第二个参数

    具体的权限名称;

用于请求权限的ActivityCompat.requestPermissions()方法有三个参数:

  • 第一个参数

    Activity实例;

  • 第二个参数

    String数组,数组中为要申请的权限名;

  • 第三个参数

    请求码,需要是唯一的;

用于检查申请结果的方法onRequestPermissionsResult(),在申请结果回调方法中需要先验证请求码是不是之前请求的,然后检查grantResults数组的第一个元素是否与PackageManager.PERMISSION_GRANTED相同,如果相同那证明请求权限成功,否则失败。

访问其他程序中的数据

ContentProvider的使用一般有两种:第一种是使用现有的ContentProvider读取和操作相应程序中的数据,第二种就是创建自己的ContentProvider供其他程序访问。

ContentProvider的基本用法

在应用程序中如果想要访问ContentProvider中的共享数据,必须要借助ContentResolver类,可以通过Context中的getContentResolver()方法来获取实例对象;

ContentResovler中提供了增删改查的方法,分别为insert(),delete(),update(),query();

ContentResolver的CRUD方法不再是接收表名,而是接收Uri;

内容Uri给ContentProvider中的数据建立了唯一的标识符,由两部分组成:authority和path;

示例Uri格式:

content://<package name>/<name>

示例代码:

Uri uri = Uri.parse("content://<package name>/<name>");

query()方法的参数:

query()方法参数对应的SQL部分描述
urifrom table_name指定查询哪个应用程序下的表名
projectionselect col1,col2,…指定查询的列名
selectionwhere col = value指定where的约束条件
selectionArgs为where中的占位符提供具体值
sortOrderorder by col1,col2,…指定查询结果的排序方式

insert()方法的参数:

  • uri

    指定插入哪个应用下的表名

  • values

    插入的值

update()方法的参数:

update()方法参数对应SQL部分描述
uriupdate table_name指定更新哪个应用程序下的表名
valuesset col1 = val1,col2 = val2,…指定更新的列及新值
selectionwhere col = value指定where的约束条件
selectionArgs为where中的占位符提供具体值

delete()方法的参数:

  • uri

    指定要删除哪个应用下的表名;

  • selection

    指定删除的约束条件;

  • selectionArgs

    为约束条件中的占位符提供具体值;

读取系统联系人

在Manifest中添加权限:

<!--获取读取联系人的权限-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>

在需要的地方引入:

//获取联系人
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(FirstActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 2);
} else {
    readContacts();
}

对动态申请权限的结果进行处理:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == 2){
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            readContacts();
        } else {
            Toast.makeText(this, "拒绝了权限申请", Toast.LENGTH_LONG).show();
        }
    }
}

获取联系人:

private void readContacts(){
    Cursor queryResult = getContentResolver().query(
        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
        null,
        null,
        null,
        null
    );
    while (queryResult.moveToNext()){
        //联系人姓名
        String displayName = queryResult.getString(queryResult.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
        //获取联系人手机号
        String number = queryResult.getString(queryResult.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
        Log.i(TAG, "联系人姓名:" + displayName + ",手机号:" + number);
    }
    queryResult.close();
}

创建ContentProvider的步骤

创建ContentProvider,需要继承ContentProvider类并重写其中的6个抽象方法:

  • onCreate()

    初始化ContentProvider时调用,用以创建或者升级数据库;

    返回true时表示初始化成功,返回false时表示初始化失败;

  • query()

    用于从ContentProvider中查询数据;

    uri参数用于确定查询哪张表,projection参数表示查询哪些列,selection和selectionArgs表示约束条件及其值,sortOrder参数用于对结果进行排序,查询结果被放在Cursor对象中返回;

  • insert()

    用于向ContentProvider中添加数据;

    uri参数用于确定要添加到的表,添加的数据保存在values参数中;

    添加完成后,返回一个用于表示此记录的Uri;

  • update()

    用于更新ContentProvider中现有数据;

    uri参数用于确定要添加到的表,values中保存新值,selection和selectionArgs表示约束条件及其值;

    更新完成后,返回受影响的行数;

  • delete()

    用于删除ContentProvider中现有数据;

    uri参数用于确定要添加到的表,selection和selectionArgs表示约束条件及其值;

    删除完成后,返回受影响的行数;

  • getType()

    根据传入的内容URI返回相应的MIME类型;

    一个内容URI对应的MIME字符串主要由3部分组成,Android对此部分做出了如下规定:

    • 必须以vnd开头;

    • 如果内容URI以路径结尾,则后接android.cursor.dir/;

      如果内容URI以id结尾,则后接android.cursor.item/;

    • 最后接上vnd.<authority>.<path>;

    例如,以下Uri:

    content://com.zcd.flcdemo/book
    

    对应的MEMI类型可以写为:

    vnd.android.cursor.dir/vnd.com.zcd.flcdemo.book
    

    再例如,以下Uri:

    content://com.zcd.flcdemo/book/1
    

    对应的MEMI类型可以写为:

    vnd.android.cursor.item/vnd.com.zcd.flcdemo.book
    

实现跨程序数据共享

在Manifest文件中注册该ContentProvider:

<!--注册ContentProvider-->
<provider
          android:authorities="com.zcd.flcdemo.provider"
          android:name=".Utils.MyContentProvider"
          android:exported="true"
          android:enabled="true"/>

新建并重写相关方法:

public class MyContentProvider extends ContentProvider {

    private final String TAG = "MyContentProvider";
    private final String BOOK_TABLE_NAME = "Book";
    private final String AUTHORITY = "com.zcd.flcdemo.provider";

    private static final int BOOK_TABLE_DIR = 0;
    private static final int BOOK_ITEM = 1;

    private UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private FLCDBHelper dbHelper = null;

    @Override
    public boolean onCreate() {
        uriMatcher.addURI(AUTHORITY, "book", BOOK_TABLE_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        dbHelper = new FLCDBHelper(getContext(), "Book.db", null,1);
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor resultCursor = null;
        switch (uriMatcher.match(uri)){
            case BOOK_TABLE_DIR:
                resultCursor = db.query(BOOK_TABLE_NAME, null, null, null, null, null, null);
                break;
            case BOOK_ITEM:
                resultCursor = db.query(BOOK_TABLE_NAME, strings, s, strings1, null,null,null,null);
                break;
            default:
                Log.i(TAG, "query: 未知uri");
        }
        return resultCursor;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)){
            case BOOK_TABLE_DIR:
                return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd." + AUTHORITY + ".book";
            default:
                return null;
        }
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        long newBookID = db.insert(BOOK_TABLE_NAME, null, contentValues);
        return Uri.parse("content://" + AUTHORITY + "/book/" + newBookID);
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        return db.delete(BOOK_TABLE_NAME, s, strings);
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        return db.update(BOOK_TABLE_NAME, contentValues, s, strings);
    }
}

测试方法:

private void testContentProvider() {
    //插入数据
    insertByContentProvider();
    //查询数据
    queryByContentProvider();
    //更新数据
    updateByContentProvider();
    //查询数据
    queryByContentProvider();
    //删除数据
    deleteByContentProvider();
    //查询数据
    queryByContentProvider();
}

private void queryByContentProvider(){
    Uri queryUri = Uri.parse("content://com.zcd.flcdemo.provider/book");
    Cursor resCursor = getContentResolver().query(queryUri, null, null, null, null);
    if (resCursor == null){
        Log.i(TAG, "queryByContentProvider: 目标数据库不存在");
        return;
    }
    StringBuilder res = new StringBuilder();
    while (resCursor.moveToNext()){
        String author = resCursor.getString(resCursor.getColumnIndex("author"));
        double price = resCursor.getDouble(resCursor.getColumnIndex("price"));
        int pages = resCursor.getInt(resCursor.getColumnIndex("pages"));
        String name = resCursor.getString(resCursor.getColumnIndex("name"));
        res.append("[name=").append(name)
            .append(",price=").append(price)
            .append(",pages=").append(pages)
            .append(",author=").append(author)
            .append("]");
    }
    resCursor.close();
    Log.i(TAG, "queryByContentProvider: 全部图书:" + res.toString());
}

private void insertByContentProvider(){
    Uri insertUri = Uri.parse("content://com.zcd.flcdemo.provider/book");
    ContentValues values = new ContentValues();
    values.put("author", "Bruce Eckel");
    values.put("price", 89.00);
    values.put("pages", 800);
    values.put("name", "Thinking In Java");
    Uri resUri = getContentResolver().insert(insertUri, values);
    if (resUri == null){
        Log.i(TAG, "insertByContentProvider: 通过ContentProvider插入新书未成功");
    }else {
        Log.i(TAG, "insertByContentProvider: 通过ContentProvider插入的新书id:" + resUri.getPathSegments().get(1));
    }
}

private void updateByContentProvider(){
    Uri updateUri = Uri.parse("content://com.zcd.flcdemo.provider/book/1");
    ContentValues values = new ContentValues();
    values.put("author", "Casablanca");
    values.put("price", 99.99);
    values.put("pages", 60);
    values.put("name", "Casablanca");
    int updateRows = getContentResolver().update(updateUri, values, null, null);
    Log.i(TAG, "updateByContentProvider: 更新数据个数:" + updateRows);
}

private void deleteByContentProvider(){
    Uri deleteUri = Uri.parse("content://com.zcd.flcdemo.provider/book/1");
    int updateRows = getContentResolver().delete(deleteUri, null, null);
    Log.i(TAG, "deleteByContentProvider: 删除数据个数:" + updateRows);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值