文章

Android权限与访问其他程序中数据

一、Android10为止所有危险权限

如需使用以下危险权限,需要在AndroidManifest.xml中进行声明。
Android权限与访问其他程序中数据_1.png
Android权限与访问其他程序中数据_2.png

二、申请电话权限并拨打电话示例

1.声明权限

<!-- 首先在AndroidManifest.xml中声明打电话权限 -->
<uses-permission android:name="android.permission.CALL_PHONE" />

2.在线程中向用户申请权限并拨打10086

override fun onCreate(...) {
    ...
    // 先用checkSelfPermission()判断软件是否已经获得打电话权限
    btn.setOnClickListener {
        if(ContextCompat.checkSelfPermission(this, 
            Manifest.permission.CALL_PHONE) != 
            PackageManager.PERMISSION_GRANTED) {
            // 未获得权限,向用户申请
            // 接受Activity,String权限和请求码3个参数
            ActivityCompat.requestPermissions(this,
                arrayOf(Manifest.permission.CALL_PHONE), 1)
        } else {
            // 已获得权限,拨打电话
            call()
        }
    }
}

/* 用户拒绝/接受权限后的回调方法,在这里判断用户是否接受权限申请 */
override fun onRequestPermissionsResult(requestCode: Int,
        permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions,
            grantResults)
    when(requestCode) {
        1 -> {
            if (grantResults.isNotEmpty() && 
                grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 用户接受权限申请,拨打电话
                call()
            } else {
                // 用户拒绝权限申请
                Toast.makeText(this,"你拒绝了",Toast.LENGTH_SHORT).show()
            }
        }
    }
}

/* 拨打电话方法,需要在权限申请成功后调用 */
private fun call() {
    try {
        val intent = Intent(Intent.ACTION_CALL)
        intent.data = Uri.parse("tel:10086")
        startActivity(intent)
    } catch (e: SecurityException) {
        e.printStackTrace()
    }
}

三、访问其他程序中的数据——ContentResolver

ContentProvider有两种用法:1.用现有ContentProvider读取和操作程序中的数据。2.创建自己的ContentProvider,给外部程序访问数据的接口。先来学习如何访问其他程序中的数据。

1.ContentResolver基本用法

想访问ContentProvider中的内容就要用到ContentResolver,通过Context中的getContentResolver()方法获取实例。

ContentResolver可以进行增删改查操作,对应insert()、delete()、update()、query(),类似于SQLiteDatabase,不过第一参数不是表名,而是Uri参数。

Uri给ContentProvider中数据建立唯一标识符,由authoritypath组成,authority一般以包名命名,path则是对同一程序中不同表做区分,因此标准URI格式如下:content://com.example.app.provider/table1

val uri = Uri.parse("content://com.example.app.provider/table1")

2.读取系统联系人

添加权限声明

<!-- 添加权限声明 -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>

完整代码

private val contactsList = ArrayList<String>() // 存放联系人
override fun onCreate(...) {
    ...
    // 先判断是否已获得读取联系人权限
    if (ContextCompat.checkSelfPermission(this,
        Manifest.permission.READ_CONTACTS) != 
        PackageManager.PERMISSION_GRANTED) {
        // 未获得权限,向用户申请
        ActivityCompat.requestPermissions(this,
                arrayOf(Manifest.permission.READ_CONTACTS), 1)
    } else {
        // 已获得权限,读取联系人
        readContacts()
    }
}

override fun onRequestPermissionResult(requestCode: Int,
        permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions,
            grantResults)
    when(requestCode) {
        1 -> {
            if(grantResults.isNotEmpty() &&
                grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 用户接受权限申请,读取联系人
                readContacts()
            } else {
                // 用户拒绝权限申请
                Toast.makeText(this,"你拒绝了",Toast.LENGTH_SHORT).show()
            }
        }
    }
}

private fun readContacts() {
    // ContactsContact已经为我们做好了读取联系人Uri封装
    contentResolver.query(ContactsContact.CommonDatakinds.
        Phone.CONTENT_URI,null,null,null,null)?.apply{
        while(moveToNext()) {
            // 获取联系人姓名和手机号,用ContactsContact封装的常量
            val name = getString(getColumnIndex(ContactsContact.
                CommonDatakinds.Phone.DISPLAY_NAME))
            val number = getString(getColumnInedx(COntactsContact.
                CommonDatakinds.Phone.NUMBER))
            contactsList.add("$name\n$number")
        }
        close()
    }
}

四、创建自己的ContentProvider

要想实现跨程序共享数据的功能,需要新建一个类去继承ContentProvider来实现,ContentProvider类有6个抽象方法,需将这6个方法全部重写。
Android权限与访问其他程序中数据_3.png

1.根据不同URI匹配不同内容

// 访问table1表中id为1的数据
"content://com.example.app.provider/table1/1"

// 访问table1表中所有数据
"content://com.example.app.provider/table1"

内容URI格式主要就上面两种

1.1.用通配符匹配上面两种格式

// *表示匹配任意长度任意字符
"content://com.example.app.provider/*" // 任意表内容

// #表示匹配任意长度数字
"content://com.example.app.provider/table1/#" // 表中任意数据

2.自定义ContentProvider完整代码

class DatabaseProvider : ContentProvider() {
    // 分别表示读取整个book表还是book表中某一id数据
    private val bookDir = 0
    private val bookItem = 1
    private val authority = "com.example.databasetest.provider"
    private val dbHelper : MyDatabaseHelper? = null
    // 懒加载,只有在uriMatcher被调用时才执行Lambda中代码
    private val uriMatcher by lazy {
         // UriMatcher用于解析Uri
        val matcher = UriMatcher(UriMather.NO_MATCH)
        // 添加需要响应的Uri格式
        matcher.addURI(authority, "book", bookDir)
        matcher.addURI(authority, "book/#", bookItem)
        mather
    }
    
    override fun onCreate() = context?.let {
        dbHelper = MyDatabaseHelper(it, "BookStore.db", 2)
        true
    } ?: false
    
    override fun query(uri: Uri, projection: Array<String>?,
            selection: String?, selectionArgs: Array<String>?,
            sortOrder: String?) = dbHelper?.let {
        // 查询数据
        val db = it.readableDatabase
        val cursor = when(uriMatcher.match(uri)) {
            bookDir -> db.query("Book", projection, selection,
                    selectionArgs, null, null, sortOrder)
            bookItem -> {
                // UriMatcher将path按/号分割,0是表名,1是id
                val bookId = uri.pathSegments[1]
                db.query("Book", projection, "id = ?",
                        arrayOf(bookId), null, null, sortOrder)
            }
            else -> null
        }
        cursor
    }
    
    override fun insert(uri: Uri, values: ContentValues?) = 
            dbHelper?.let {
        // 添加数据
        val db = it.writableDatabase
        val uriReturn = when(uriMatcher.match(uri)) {
            bookDir, bookItem -> {
                val newBookId = db.insert("Book", null, values)
                Uri.parse("content://$authority/book/$newBookId")
            }
            else -> null
        }
        uriReturn
    }
    
    override fun update(uri: Uri, values: ContentValues?,
            selection: String?, selectionArgs: Array<String>?) = 
            dbHelper?.let {
        // 更新数据
        val db = dbHelper.writableDatabase
        val updateRows = when(uriMatcher.match(uri)) {
            bookDir -> db.update("Book", values, selection,
                    selectionArgs)
            bookItem -> {
                val bookId = uri.pathSegments[1]
                db.update("Book", values, "id = ?", arrayOf(bookId))
            }
            else -> 0
        }
        updateRows
    } ?: 0
    
    override fun delete(uri: Uri, selection: String?, 
            selectionArgs: Array<String>?) = dbHelper?.let {
        //删除数据
        val db = it.writableDatabase
        val deletedRows = when(uriMatcher.match(uri)) {
            bookDir -> db.delete("Book", selection, selectionArgs)
            bookItem -> {
                val categoryId = uri.pathSegments[1]
                db.delete("Book", "id = ?", arrayOf(bookId))
            }
            else -> 0
        }
        deletedRows
    } ?: 0
    
    override fun getType(uri: Uri) = when(uriMatcher.match(uri)) {
        // Uri以路径结尾返回dir,然后接上包名和表名
        bookDir -> "vnd.android.cursor.dir/" + 
                "vnd.com.example.databasetest.provider.book"
        // Uri以id结尾返回item,然后接上包名和表名
        bookItem -> "vnd.android.cursor.item/" + 
                "vnd.com.example.databasetest.provider.book"
        else -> null
    }
}

UriMatcher用于匹配Uri内容,addURI()方法接收authority,path,自定义代码三个参数。调用match()方法时,传入Uri对象,返回值就是匹配上方addURI()方法添加的URI的自定义代码,因此我们可以判断调用者期望访问哪张表。

2.1.在AndroidManifest.xml中配置ContentProvider

自动创建的Provider不需要手动配置

<manifest ...>
    <application ...>
        <provider
            android:name=".DatabaseProvider"
            android:authorities="com.example.databasetest.provider"
            android:enabled="true"
            android:exported="true">
        </provider>
    </application>
</manifest>
License:  CC BY 4.0