Android Studio —— 内容提供器

365 阅读5分钟

内容提供器

  • 主要用于在不同的应用程序之间实现数据共享的功能
  • 可以选择只对哪一部分数据进行共享
  • 使用现有的内容提供器来读取和操作相应程序中的数据
  • 或创建自己的内容提供器给我们程序的数据提供外部访问接口

运行时权限

机制

  • 普通权限系统自动进行授权
  • 危险权限(权限组名 | 权限名)
    • CALENDAR: READ_CALENDAR / WRITE_CALENDAR
    • CAMERA: CAMERA
    • CONTACTS: READ_CONTACTS / WRITE_CONTACTS / GET_CONTACTS
    • LOCATION: ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION
    • MICROPHONE: RECORD_AUDIO
    • PHONE: READ_PHONE_STATE / CALL_PHONE / READ_CALL_LOG / WRITE_CALL_LOG / ADD_VOICEMAIL / USE_SIP / PROCESS_OUTGOING_CALLS
    • SENSORS: BODY_SENSORS
    • SMS: SEND_SMS / RECEIVE_SMS / READ_SMS / RECEIVE_WAP_PUSH / REACEIVE_MMS
    • STORAGE: READ_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGE
    • 同意授权某个危险权限后,该权限对应的权限组中所有的其他权限也会同时被授权

运行时申请权限

  • 在AndroidManifest.xml文件中声明
    <uses-permission android:name="android.permission.CALL_PHONE" />
  • @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        Button makeCall = findViewById(R.id.make_call);
        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();
        }
    }
    
    @Override
    public void onRequestPermissionResult(int requestCode, String[] permissions, int[] 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:
        }
    }
    
    • ContextCompat.checkSelfPermission:先判断用户是否已经授权
      第一个参数是Context
      第二个参数是具体的权限名
      用返回值和PackageManager.PERMISSION_GRANTED比较,相等则已经授权,不等则没有授权
    • 如果没有授权,则调用ActivityCompat.requestPermissions()向用户申请授权
      第一个参数要求Acitivity的实例
      第二个参数是一个String数组,把要申请的权限名放在数组中
      第三个参数是请求码,只要是唯一值即可
    • 调用完ActivityCompat.requestPermissions(),系统会弹出一个权限申请的对话框
      用户选择同意或拒绝后,回调到onRequestPermissionResult方法中
      授权的结果封装在grantResults参数中
    • 拨打电话
      • 构建一个隐式Intent
        action指定为Intent.ACTION_CALL,是系统内置的打电话动作
        data部分指定了协议是tel,号码是10086
      • Intent.ACTION_DIAL表示打开拨号界面,不需要声明权限
        Intent.ACTION_CALL直接拨打电话,必须声明权限

访问其他程序中的数据

ContentResolver的基础用法

  • 通过Context中的getContentResolver()方法获取该类的实例
  • 提供了一系列方法用于对数据进行CRUD操作
    insert() update() delete() query()
  • 内容URI:给内容提供器中的数据建立了唯一标识符
    • authority:区分不同的应用程序,一般采用<package_name>.provider的方式命名
    • path:对同一应用程序中不同的表做区分,通常添加到authority后面
    • 在字符串头部加上协议声明
    • E.g. content://com.example.app.provider/table1
    • 解析为Uri对象
      Uri uri = Uri.parse(content://com.example.app.provider/table1);
  • 查询query():
    Cursor cursor = getContentResolver().query(
        uri,
        projection,
        selection,
        selectionArgs,
        sortOrder);
    
    • uri :指定查询某个应用程序下的某张表
    • projection:指定查询的列名
    • selection:指定where的约束条件
    • selectionArgs:为where中的占位符提供具体的值
    • sortOrder:指定查询结果的排序方式
    if (cursor != null) {
        while (cursor.moveToNext()) {
           String column1 = cursor.getString(cursor.getColumnIndex("column1"));
           int column2 = cusor.getInt(cursor.getColumnIndex("column2"));
        }
        cursor.close()
    }
    
    • 移动游标遍历所有行,再取出每一行中相应列的数据
  • 添加insert()
    ContentValue values = new ContentValues();
    values.put("column1", "text");
    values.put("column2", 1);
    getContentResolver().insert(uri, values);
    
  • 修改update()
    ContentValues values = new ContentValues();
    values.put("column1", "");
    getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[] {"text", "1"});
    
  • 删除delete()
    getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });
    

创建自己的内容提供器

创建步骤

  • 新建一个类继承ContentProvider
    重写6个抽象方法
    • boolean onCreate()
      • 初始化内容提供器时调用,通常会完成对数据库的创建和升级等操作
      • 只有当存在ContentResolver尝试访问程序中的数据时才会被初始化
    • Cursor query(Uri uri, String[] projection, String selection, String[] SelectionArgs, String sortOrder)
      • 查询的结果存放在Cursor对象中返回
    • Uri insert(Uri uri, ContentValues values)
      • 返回一个用于表示这条新记录的URI
    • int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
      • 返回受影响的行数
    • int delete(Uri uri, String selection, String[] selectionArgs)
      • 返回被删除的行数
    • String getType(Uri uri)
      • 返回相应的MIME类型
      • MIME字符串主要由3部分组成
        • 必须以vnd开头
        • 如果内容URI以路径结尾,则后接android.cursor.dir/
          如果内容URI以id结尾,则后接android.cursor.item/
        • 最后接上vnd.<authority>.<path>
        • E.g. vnd.android.cursor.dir/vnd.com.example.app.provide.table1

内容URI

  • 给内容提供器中的数据建立了唯一标识符
  • authority:区分不同的应用程序,一般采用<package_name>.provider的方式命名
  • path:对同一应用程序中不同的表做区分,通常添加到authority后面
  • 在字符串头部加上协议声明
  • 可以在内容URI后面加上id
    content://com.example.app.provider/table1/1
    • 表示想访问com.example.app这个应用的table1表中id为1的数据
  • 可以使用通配符匹配
    • *表示匹配任意长度的任意字符
    • #表示匹配任意长度的数字
    • E.g. content://com.example.app.provider/*匹配任意表的内容
      content://com.example.app.provider/table1/#匹配table1表中任意一行数据
  • UriMatcher
    • 提供addURI()方法
      接收3个参数,可以分别传入authoritypath和一个子弟能够以代码
    • 调用match()方法是可以将一个Uri对象传入,返回某个能匹配这个Uri对象所对应的自定义代码
      通过这个代码判断需要访问哪张表中的数据
  • 所有的CRUD操作都一定要匹配到相应内容的URI格式才能进行,防止隐私数据泄露
public class MyProvider extends ContentProvider {
    public static final int TABLE1_DIR = 0;
    public static final int TABLE1_ITEM = 1;
    public static final int TABLE2_DIR = 2;
    public static final int TABLE2_ITEM = 3;
    private static UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider", "table2", TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
    }

    ...

    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        switch (uriMatcher.match(uri)) {
        case TABLE1_DIR:
            // 查询table1表中的所有数据
            break;
        case TABLE1_ITEM:
            // 查询table2表中的单条数据
            break;
        case TABLE2_DIR:
            // 查询table2表中的所有数据
            break;
        case TABLE2_ITEM:
            // 查询table2表中的单条数据
            break;
        default:
            break;
        }
        ...
    }
    
    @Override
    public String getType(Uri uri) {
        switch (uriMathcer.match(uri)) {
        case TABLE1_DIR:
            return "vnd.android.cursor.dir/vnd.com.example.app.provide.table1";
        case TABLE1_ITEM:
            return "vnd.android.cursor.item/vnd.com.example.app.provide.table1";
        case TABLE2_DIR:
            return "vnd.android.cursor.dir/vnd.com.example.app.provide.table2";
        case TABLE2_ITEM:
            return "vnd.android.cursor.item/vnd.com.example.app.provide.table2";
        default:
            break;
        }
        return null;
    }
    ...
}