第八章 ContentProvider
-
运行时权限
-
Android将常用的权限大致分为了两类:普通权限和危险权限
- 普通权限是指那些不会直接威胁到用户安全和隐私的权限,系统会自动帮我们授权,比如监听开机广播的权限
- 危险权限是指可能会触及用户隐私或者对设备安全性造成影响的权限
-
危险权限(11组30个)
权限组名 权限名 CALENDAR android.permission.READ_CALENDAR
android.permission.WRITE_CALENDARCALL_LOG android.permission.READ_CALL_LOG
android.permission.WRITE_CALL_LOGCAMERA android.permission.CAMERA CONTACTS android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNTSLOCATION android.permission.ACCESS_FINE_LOCATION
android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_BACKGROUND_LOCATIONMICROPHONE android.permission.RECORD_AUDIO PHONE android.permission.READ_PHONE_STATE
android.permission.READ_PHONE_NUMBERS
android.permission.CALL_PHONE
android.permission.ANSWER_PHONE_CALLS
android.permission.ADD_VOICEMAIL
android.permission.USE_SIP
android.permission.ACCEPT_HANDOVERSENSORS android.permission.BODY_SENSORS ACTIVITY_RECOGNITION android.permission.ACTIVITY_RECOGNITION SMS android.permission.SEND_SMS
android.permission.RECEIVE_SMS
android.permission.READ_SMS
android.permission.RECEIVE_MMSSTORAGE android.permission.WRITE_EXTERNAL_STORAGE
android.permission.READ_EXTERNAL_STORAGE
android.permission.ACCESS_MEDIA_LOCATION -
运行时申请权限
-
XML文件中添加权限声明
<uses-permission android:name="android.permission.CALL_PHONE" /> -
检查并获取权限
if (ContextCompat.checkSelfPermission( this, Manifest.permission.CALL_PHONE ) != PackageManager.PERMISSION_GRANTED ) { // 第二个参数为权限的数组,可请求多个权限 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 1) } -
申请权限的回调
// 第二个参数为权限的数组,第三个为对应权限数组的结果数组 override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { 1 -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startCallIntent() } else { Toast.makeText(this, "you've denied", Toast.LENGTH_SHORT).show() } } } }
-
-
-
ContentResolver
-
Uri介绍
authority和path组成- authority主要是对不同的应用程序做区分
- path对程序中不同的表做区分
-
示例读取联系人
contentResolver.query( Uri.withAppendedPath( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, "1"), null, null, null, null )?.apply { while (moveToNext()) { contactList.add( "${getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))} ${ getString( getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER ) ) }" ) } adapter.notifyDataSetChanged() close() }
-
-
ContentProvider
-
Uri
-
以路径结尾表示期望访问表中所有的数据,以id结尾表示期望访问该表中拥有相应id的数据。
content://com.youngly.firstlineofcode/table表中所有数据content://com.youngly.firstlineofcode/table/1表中id为1的数据 -
我们可以使用通配符分别匹配着两种格式的内容URI,规则如下
*表示匹配任意长度的任意字符#表示匹配任意长度的数字
-
-
getType()方法
主要获取Uri对象所对应的MIME类型。一个内容URI对应的MIME字符串由3部分组成,规定如下
- 必须以vnd开头
- 如果内容以路径结尾,则后接android.cursor.dir/;如果内容URI以id结尾,则后接android.cursor.item/
- 最后接上vnd..
content://com.youngly.firstlineofcode/table的MIMEvnd.android.cursor.dir/vnd.com.youngly.firstlineofcode.tablecontent://com.youngly.firstlineofcode/table/1的MIMEvnd.android.cursor.item/vnd.com.youngly.firstlineofcode.tableMIME类型主要是Activity的Intent-filter的data域
MIME类型在Activity中是用来指定,当前的Activity所支持打开的文件类型
MIME类型其实就是一个字符串,中间有一个“/”来隔开,“/”前面的部分是系统识别的部分,就相当于我们定义一个变量时的变量数据类型,通过这个“数据类型”,系统能够知道我们所要表示的是个什么东西。至于“/”后面的部分就是我们自已来随便定义的“变量名”了。
重写getType()的作用:
<activity android:name=".chapter8.ContentResolverActivity" > <intent-filter> <action android:name="com.youngly.providertypetest"/> <category android:name="android.intent.category.DEFAULT"/> // 该activity的data中mimeType为getType中的 <data android:mimeType="vnd.android.cursor.item/vnd.com.youngly.firstlineofcode.book"/> </intent-filter> </activity>// 可以通过mimeType进行跳转 val intent = Intent(packageName) intent.action = "com.youngly.providertypetest" // 通过getType就能匹配上data中的mimeType intent.data = Uri.parse("content://com.youngly.firstlineofcode/book/1") startActivity(intent)getType在什么时候用?
- 隐式调用activity传入data数据. 且这个data数据, 是某个ContentProvider的uri参数
- 为了防止data是activity无法处理, 所以activity才需要设置mime进行data校验
- 为了acvity在校验自定义ContentProvider时能有结果, 所以才要实现ContentProvider的getType
package com.youngly.firstlineofcode.chapter8 import android.content.ContentProvider import android.content.ContentValues import android.content.UriMatcher import android.database.Cursor import android.net.Uri import com.youngly.firstlineofcode.chapter7.database.MyDatabaseHelper class BookStoreContentProvider : ContentProvider() { private val bookDir = 0 private val bookItem = 1 private val categoryDir = 2 private val categoryItem = 3 private val authority = "com.youngly.firstlineofcode" private lateinit var dbHelper: MyDatabaseHelper private val uriMatcher by lazy { val matcher = UriMatcher(UriMatcher.NO_MATCH) matcher.addURI(authority, "book", bookDir) // 通配符,任意一行数据 matcher.addURI(authority, "book/#", bookItem) matcher.addURI(authority, "category", categoryDir) matcher.addURI(authority, "category/#", categoryItem) matcher } 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? ): Cursor? = dbHelper.let { val db = it.readableDatabase var cursor = when (uriMatcher.match(uri)) { bookDir -> db.query( "Book", projection, selection, selectionArgs, null, null, sortOrder ) bookItem -> { val bookId = uri.pathSegments[1] db.query("Book", projection, "id = ?", arrayOf(bookId), null, null, sortOrder) } categoryDir -> db.query( "Category", projection, selection, selectionArgs, null, null, sortOrder ) categoryItem -> { val categoryId = uri.pathSegments[1] db.query( "Category", projection, "id = ?", arrayOf(categoryId), 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 insertId = db.insert("Book", null, values) Uri.parse("content://$authority/book/$insertId") } categoryDir, categoryItem -> { val insertId = db.insert("Category", null, values) Uri.parse("content://$authority/category/$insertId") } else -> null } uriReturn } override fun update( uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>? ) = dbHelper.let { val db = it.writableDatabase val updateId = when (uriMatcher.match(uri)) { bookDir -> db.update("Book", values, selection, selectionArgs) bookItem -> { val bookId = uri.pathSegments[1] db.update("Book", values, "id = ?", arrayOf(bookId)) } categoryDir -> db.update("Category", values, selection, selectionArgs) categoryItem -> { val categoryId = uri.pathSegments[1] db.update("Category", values, "id = ?", arrayOf(categoryId)) } else -> 0 } updateId } override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = dbHelper.let { val db = it.writableDatabase val deleteId = when (uriMatcher.match(uri)) { bookDir -> db.delete("Book", selection, selectionArgs) bookItem -> { val bookId = uri.pathSegments[1] db.delete("Book", "id = ?", arrayOf(bookId)) } categoryDir -> db.delete("Category", selection, selectionArgs) categoryItem -> { val categoryId = uri.pathSegments[1] db.delete("Category", "id = ?", arrayOf(categoryId)) } else -> 0 } deleteId } override fun getType(uri: Uri): String? = when (uriMatcher.match(uri)) { bookDir -> "vnd.android.cursor.dir/vnd.$authority.book" bookItem -> "vnd.android.cursor.item/vnd.$authority.book" categoryDir -> "vnd.android.cursor.dir/vnd.$authority.category" categoryItem -> "vnd.android.cursor.item/vnd.$authority.category" else -> null } }
-
-
Kotlin课堂
-
泛型
-
泛型类
class MyClass<T> { fun method(params: T): T { return params } }调用
val myClass = MyClass<Int>() val method = myClass.method(2) -
泛型方法
class MyClass { fun <T> method(params: T): T { return params } }调用
val myClass = MyClass() val method = myClass.method<Int>(2) -
类型限制
泛型上界设置为Number类型
fun <T : Number> method(params: T): T { return params }
-
-
类委托和委托属性
-
类委托
委托是一种设计模式,它的基本理念是:操作对象自己不去处理某段逻辑,而是把工作委托给另外一个辅助对象处理。
class MySet<T>(val helper: HashSet<T>) : Set<T> { override val size: Int get() = helper.size override fun contains(element: T) = helper.contains(element) override fun containsAll(elements: Collection<T>) = helper.containsAll(elements) override fun isEmpty() = helper.isEmpty() override fun iterator() = helper.iterator() }helper相当于辅助对象。这其实就是一种委托模式。
这种写法弊端是借口待实现的方法过多,写起来比较繁琐。
class MySet<T>(val helper: HashSet<T>) : Set<T> by helper { }Kotlin中委托使用的关键字是
by,我们只需要在接口声明的后面使用by关键字,再接上受委托的辅助对象,就可以免去之前所写的一大堆模版代码。如果我们需要对某个方法进行重新实现,只需要单独重写那一个方法就可以了。
class MySet<T>(val helper: HashSet<T>) : Set<T> by helper { override fun isEmpty(): Boolean { TODO("Not yet implemented") } } -
属性委托
属性委托的核心是将一个属性的具体实现委托给另一个类取完成
Delegate类定义:
class Delegate { var propValue: Any? = null // 第一个参数表明该Delegate类的委托功能在什么类中使用,第二个参数是Kotlin中的一个属性操作类,可用户获取各种属性相关的值,<*>这种泛型写法表示你不知道或者不关心泛型的具体类型,类似Java中的<?> operator fun getValue(thisRef: Any?, property: KProperty<*>): Any? { return propValue } // 最后一个参数表示要赋值给委托属性的值 operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Any?) { propValue = value } }实现自己的lazy
先定义一个kt文件
class Lazy<T>(val block: () -> T) { var value: Any? = null operator fun getValue(any: Any?, prop: KProperty<*>): T { if (value == null) { value = block() } return value as T } }由于懒加载技术不会对属性进行赋值,所以不需要提供setValue()方法
fun <T> later(block: () -> T) = Lazy(block)
-
-