本章我们介绍了跨程序共享CotentProvider。从权限机制分类(普通权限、危险权限)到6.0(API>=23)之后的运行时权限;从访问系统联系人程序的数据到创建自己的ContentProvider供外部程序进行CRUD;从泛型、委托到实现自己的lazy函数,比较充实。
8.1.ContentProvider简介
上一章谈到的持久化存储技术只能在当前程序中访问,如何进行跨程序数据共享,考虑使用ContentProvider。譬如:系统通讯录共享、短信、媒体库等。ContentProvider可以选择哪一部分数据进行共享,保证隐私不会被泄漏。ContentProvider会使用到运行时权限。
8.2.运行时权限
8.2.1.Android权限机制详解
之前在BroadcastTest项目的AndroidManifest.xml中有一个这样的权限声明:
....
检测开机广播涉及用户安全,若不声明会崩溃掉。加入此声明的作用有两个:1.低于6.0的系统上安装程序,会清楚地知道申请了哪些权限,2.用于随时在应用程序管理界面查看任意一个程序的权限申请情况。
理想很丰满,现实很骨感,现在的app存在滥用权限的问题。在Android6.0之后加入了运行时权限功能,用户无需一次性授权所有权限,可在使用过程中再对其进行授权。并不是所有权限都需要运行时申请,权限分为普通和危险权限两种,前者不会直接威胁隐私和安全,系统自动授权,譬如RECEIVE_BOOT_COMPLETED;后者可能会触及用户隐私或对设备安全性进行影响,譬如读取设备联系人和定位GPS,手动授权。
Android共有100多种权限,危险权限有11组30个权限。如果是下表中的危险权限,需要运行时权限处理。
一旦用户同意某个权限申请,那么同组其他权限也会被自动授权。但Android系统可能随时调整权限分组。
8.2.2.在程序运行时申请权限
新建RuntimePermissionTest项目,使用Call_PHONE权限来作为示例,在6.0系统出现之前,实现很简单。
package com.example.myapplication
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
make_call.setOnClickListener {
try {
//构建隐式Intent,Intent的action指定为系统内置打电话工作,data部分指定了协议为tel,号码是10086.
//实现拨打电话的功能。ACTION_CALL是直接拨打电话,需要权限,ACTION_DIAL的是到拨号界面,无需权限。
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
}
.....
Android6.0之前可以正常运行,Android6.0(API大于等于23)之后会报错Permission Denied。
package com.example.myapplication
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_main.*
import java.util.jar.Manifest
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
make_call.setOnClickListener {
//1.首先判断用户是不是已经授权过了,借助checkSelfPermission函数,第一个参数是context,第二个是权限名,然后该返回值与PERMISSION_GRANTED比较。
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
//未曾授权
//ActivityCompat.requestPermissions申请授权,第一个参数是Activity实例,第二个String数组,申请的权限名放入数组即可;第三个参数是请求码,确保唯一即可。
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CALL_PHONE), 1)
} else {
//已经授权
call()
}
}
}
//调用完requestPermissions自动弹出对话框申请权限,无论同意与否都会调用onRequestPermissionsResult方法
//grantResults是授权结果
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
call()
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun call() {
try {
//构建隐式Intent,Intent的action指定为系统内置打电话工作,data部分指定了协议为tel,号码是10086.
//实现拨打电话的功能。ACTION_CALL是直接拨打电话,需要权限,ACTION_DIAL的是到拨号界面,无需权限。
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
8.3.访问其他程序中的数据
ContentProvider有两种写法:1.使用现有的ContentProvider读取和操作相应程序的数据;2.创建自己的ContentProvider供外部访问。Android系统的通讯录、短信和媒体库都有类似的访问接口。
8.3.1.ContentResolver的基本写法
借助ContentProvider类来访问共享实例,通过Context的getContentResolver方法来获取该类实例,ContentProvider类提供了类似SQLiteBase的CRUD方法。不同于SQLiteBase,前者不接收表名参数,而借助于Uri参数,内容URI是唯一标识符,分为authority和path。前者是包名.provider,后者对不同的表进行区分,譬如/table1和/table2。最后还需要加上头部协议声明。因此完整的URI如下所示:
content://com.example.app.provider/table1
在得到URI字符串后,需要使用URi.parse方法将其解析为Uri对象后作为参数传入,现在我们可以使用Uri对象来查询,query的参数列表并不复杂,依次为:uri、查询列名projection、约束条件selection、约束条件占位符具体值selectionArgs和排序方式sortOrder。查询完后返回的是cursor对象。
8.3.2.读取系统联系人
实现读取系统联系人CotentProvier的功能,新建ContactsTest项目,修改activity_main.xml:
修改MainActivity.java:
package com.example.myapplication
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.ContactsContract
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val contactsList = ArrayList()
private lateinit var adapter: ArrayAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//ListView标准写法
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList)
contacts_view.adapter = adapter
//1.首先判断用户是不是已经授权过了,借助checkSelfPermission函数,第一个参数是context,第二个是权限名,然后该返回值与PERMISSION_GRANTED比较。
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
//未曾授权
//ActivityCompat.requestPermissions申请授权,第一个参数是Activity实例,第二个String数组,申请的权限名放入数组即可;第三个参数是请求码,确保唯一即可。
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.READ_CONTACTS), 1)
} else {
//已经授权
readContacts()
}
}
//调用完requestPermissions自动弹出对话框申请权限,无论同意与否都会调用onRequestPermissionsResult方法
//grantResults是授权结果
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts()
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun readContacts() {
//首先通过contentResolver.query查询联系人数据,返回的是一个cursor对象。
//uri对象并没有通过uri.parse去解析内容URI,这是因为Phone类已经做好封装,拿到的是Uri.parse解析后的结果
contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null)?.apply {
//通过移动游标来遍历Cursor的所有行,取出每一列中相应行的数据
while (moveToNext()) {
//获取联系人姓名
val displayname = getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
//获取联系人号码
val number = getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
contactsList.add("$displayname\n$number")
}
//刷新一下ListView
adapter.notifyDataSetChanged()
//将Cursor对象关闭
close()
}
}
}
最后添加权限:
8.4.创建自己的ContentProvider
上一节讲了获得程序的内容URI后,使用ContentResolver来进行CRUD。本节我们将介绍给外界提供此接口?
8.4.1.创建ContentProvider的步骤
新建MyProvider类,代码示例如下:
package com.example.myapplication
import android.content.ContentProvider
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.net.Uri
/**
* 一个标准的URI写法为:content://com.example.app.provider/table/1,期望访问的是com.example.app这个应用中tale表的id的1的数据
*/
class MyProvider : ContentProvider() {
private val table1Dir = 0
private val table1Item = 1
private val table2Dir = 2
private val table2Item = 3
//借助URIMatch可以轻松实现匹配内容URI的功能
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
init {
//URIMatch提供了addURI方法,接受三个参数:可以分别把authority(应用程序包名.provider)、path和一个自定义代码传进去
//这样调用uriMath.match方法可以将URI对象解析为自定义代码,这样可以得到用户期望调到那张表
//将期望匹配的内容URI格式传递进去,这里是传入的路径参数是可以使用通配符的。
uriMatcher.addURI("com.example.app.provider", "table1", table1Dir)
uriMatcher.addURI("com.example.app.provider", "table1/#", table1Item)
uriMatcher.addURI("com.example.app.provider", "table2", table2Dir)
uriMatcher.addURI("com.example.app.provider", "table2/#", table2Item)
}
//初始化ContentProvider。完成数据库的初始化和升级等操作,返回true表示成功,否则失败
override fun onCreate(): Boolean {
TODO("Not yet implemented")
}
//向ContentProvider添加数据。uri用于确定要添加到的表,待添加的数据保存values参数中,最后返回一个用于表示新记录的URI
override fun insert(uri: Uri, values: ContentValues?): Uri? {
TODO("Not yet implemented")
}
//从ContentProvider查询数据,uri确定要添加那张表,projectoin确定查询那些列,selcetion和selectionArgs用于约束查询那些行,返回结果以cursor对象返回
override fun query(
uri: Uri,
projectoin: Array?,
selcetion: String?,
selectionArgs: Array?,
sortorder: String?
): Cursor? {
//判断调用方期望访问的是那张表
when (uriMatcher.match(uri)) {
//访问table1表的所有数据
table1Dir -> {
}
//访问table1表的任意一行的数据内容
table1Item -> {
}
table2Dir -> {
}
table2Item -> {
}
}
return null
}
//更新ContentProvider中已有的数据。uri表示哪一张表,新数据保存在values中,selcetion和selcetionArgs用于约束那些行。
override fun update(
uri: Uri,
values: ContentValues?,
selcetion: String?,
selcetionArgs: Array?
): Int {
TODO("Not yet implemented")
}
//删除ContentProvider的数据
override fun delete(p0: Uri, p1: String?, p2: Array?): Int {
TODO("Not yet implemented")
}
//根据传入的内容URI来返回相应的MIME类型,MIME类型主要由三部分组成:
//1.必须以vnd开头;2.如果内容URI以路径结尾,则后接android.cursor.dir/;如果内容URI以ID结尾,则后接android.cursor.item/;3.最后街上vnd..
override fun getType(uri: Uri): String? {
when(uriMatcher.match(uri)){
//内容URI为content://com.example.app.provider.table1
table1Dir ->{return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"}
//内容URI为content://com.example.app.provider.table1/1
table1Item ->{return "vnd.android.cursor.item/vnd.com.example.app.provider.table1"}
table2Dir ->{return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"}
table2Item ->{return "vnd.android.cursor.item/vnd.com.example.app.provider.table2"}
else ->{return null}
}
}
}
8.4.2.实现跨程序数据共享
打开DatabaseTest项目,新建类名为DatabaseProvider、URI为com.eample.myapplication.provider的ContentProvider。修改其中的代码提供外部程序访问DatabaseTest项目的ContentProvideer的入口:
package com.example.myapplication
import android.content.ContentProvider
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.net.Uri
/**
* 需要在XML中进行注册,已经自动注册过了,注册代码如下:
*
*/
class DatabaseProvider : ContentProvider() {
//访问Book表的所有数据、单条数据;访问Category表的所有数据、单条数据
private val bookDir = 0
private val bookItem = 1
private val categoryDir = 2
private val categoryItem = 3
private val authority = "com.example.myapplication.provider"
private var dbhelper: MyDatabaseHelper? = null
//by lazy代码块中完成UriMatcher的初始化操作,将希望匹配的几种URI添加进去,by lazy是Kotlin提供的懒加载技术。
//即代码一开始并不会执行,只有当uriMatcher变量首次被调用时候才会执行
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)
//将返回值赋给uriMatcher
matcher
}
//使用了语法糖、?.操作符、let函数以及?:操作符,以及单行函数语法糖
//首先调用getContext方法并借助?.操作符判断返回值为空,若为空则用?:返回false,若为false表明失败。若为true执行let函数。
override fun onCreate(): Boolean = context?.let {
//创建MyDataBaseHelper实例
dbhelper = MyDatabaseHelper(it, "BookStore.db", 2)
//ContentProvider初始化成功
true
} ?: false
override fun query(
uri: Uri, projection: Array?, selection: String?,
selectionArgs: Array?, sortOrder: String?
): Cursor? = dbhelper?.let {
//获取SQLiteDataBase的实例
val db = it.readableDatabase
//根据传入的uri选择访问哪张表,再调用query进行查询,最后将cursor对象返回即可
val 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 -> {
//访问单条数据,将URI权限的部分以/进行分割,将分割后的结果放入一个字符串列表中,第0个位置是路径,第1个位置是id。
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?): Uri? = 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")
}
categoryDir, categoryItem -> {
val newCategoryId = db.insert("Category", null, values)
Uri.parse("content://$authority/book/$newCategoryId")
}
else -> null
}
uriReturn
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int =
dbhelper?.let {
val db = it.writableDatabase
val deleteRows = 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
}
deleteRows
} ?: 0
override fun getType(uri: Uri): String? = when (uriMatcher.match(uri)) {
bookDir -> "vnd.android.cursor.dir/vnd.com.example.myapplication.provider.book"
bookItem -> "vnd.android.cursor.item/vnd.com.example.myapplication.provider.book"
categoryDir -> "vnd.android.cursor.dir/vnd.com.example.myapplication.provider.category"
categoryItem -> "vnd.android.cursor.item/vnd.com.example.myapplication.provider.category"
else -> null
}
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array?
): Int = dbhelper?.let {
val db = it.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))
}
categoryDir -> db.update("Category", values, selection, selectionArgs)
categoryItem -> {
val categotyId = uri.pathSegments[1]
db.update("Category", values, "id=?", arrayOf(categotyId))
}
else -> 0
}
updateRows
} ?: 0
}
新建项目ProviderTest,修改布局文件:
修改MainActivity.java,这样可以获取DatabaseTest向外界提供的ContentProvider接口,ContentProvider本质上也是基于Database的。
package com.example.myapplication1
import android.content.UriMatcher
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.core.content.contentValuesOf
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
var bookId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addData.setOnClickListener {
//将内容URI解析为Uri对象,将要添加的数据存放至ContentValues中
val uri = Uri.parse("content://com.example.myapplication.provider/book")
val values = contentValuesOf(
"name" to "A Clash of Kings",
"author" to "George",
"pages" to 1040,
"prices" to 22.85
)
//执行insert方法,返回一个uri对象,通过getpathSegments将这个id取出。
val newuri = contentResolver.insert(uri, values)
bookId = newuri?.pathSegments?.get(1)
}
queryData.setOnClickListener {
val uri = Uri.parse("content://com.example.myapplication.provider/book")
contentResolver.query(uri, null, null, null, null)?.apply {
//遍历cursor里面的所有内容
while (moveToNext()) {
val name = getString(getColumnIndex("name"))
val author = getString(getColumnIndex("author"))
val pages = getInt(getColumnIndex("pages"))
val prices = getDouble(getColumnIndex("prices"))
Log.d("MAinActivity", "book name is $name")
Log.d("MAinActivity", "book author is $author")
Log.d("MAinActivity", "book pages is $pages")
Log.d("MAinActivity", "book prices is $prices")
}
close()
}
}
updateData.setOnClickListener {
bookId?.let {
//只影响添加数据返回的id。
val uri = Uri.parse("content://com.example.myapplication.provider/book/$it")
val values = contentValuesOf(
"name" to "A Storm of Swords",
"pages" to 1216,
"prices" to 24.05
)
contentResolver.update(uri, values, null, null)
}
}
deleteData.setOnClickListener {
bookId?.let {
val uri = Uri.parse("content://com.example.myapplication.provider/book/$it")
contentResolver.delete(uri, null, null)
}
}
}
}
8.5.泛型与委托
8.5.1.泛型的基本用法
Kotlin泛型与Java泛型有同有异,泛型允许我们在不指定具体类型的情况下进行编程。譬如:List是可存放数据的列表,但并未限制只能存整形或字符型数组。那么如何定义自己的泛型实现呢?
泛型有两种定义方式,定义泛型类和定义泛型方法,使用的语法结构都是
package com.company
/**
* 定义一个泛型类Myclass,Myclass方法允许使用T类型的参数和返回值
*/
//class Myclass {
// fun method(param: T): T {
// return param
// }
//}
/**
* 定义一个泛型方法,将定义泛型的语法结构放在方法前
*/
//class Myclass {
// fun method(param: T): T {
// return param
// }
//}
/**
* 我们设置可以将泛型指定为任意类型,指定为Number类型可以为Int、Double和Float。泛型的的上界默认为Any?。
*/
class Myclass {
fun method(param: T): T {
return param
}
}
调用该泛型方法的代码如下:
package com.company
fun main() {
//在这里Myclass类的泛型可以指定为Int类型,于是method可以接受一个Int类型的参数,而且返回值也为Int类型
// val myclass = Myclass()
// val result = myclass.method(123)
// println("result is $result")
val myclss = Myclass()
val result = myclss.method(123)
//因为有着类型推导机制,你可以省略int型
val result1 = myclss.method(123)
}
学废了么?看看6.5.1小节所述的高阶函数。
//为StringBuilder类定义一个build扩展函数,此函数接收函数类型参数,返回值类型为StringBuilder。
//函数声明与之前不一样的是加上了一个StringBuilder.语法结构,这是高阶函数完整语法规则,在函数类型前加上ClassName
//表明这是在哪一个类当中的。这样做的好处是调用该方法时自动拥有StringBuilder的上下文。
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
可以利用泛型函数简化为如下所示,随后可以将contentResolver的apply函数改为build函数:
fun T.build(block: T.() -> Unit): T {
block()
return this
}
8.5.2.类委托和委托属性
委托是设计模式,即操作对象自己不会处理某段逻辑,而是会把工作为委托给另外一个扶助对象去处理,Java语言级别没有委托。Kotlin支持委托:类委托和委托属性。
类委托的核心思想是将一个类的具体实现委托给另一个类去完成。譬如Set接口,要使用它,必须借助具体实现类譬如HashSet。借助委托模式,可以轻松实现Set接口:
package com.company
//这是一种委托模式,MySet的构造函数中接受一个HashSet参数,相当于一个辅助对象,然后再Set接口的方法实现中,我们都调用了辅助对象中相应的方法实现
//好处是:大部分调用辅助对象方法,少部分自己重写,MySet会成为全新数据结构类。
class MySet(val helperSet: HashSet) : Set {
override val size: Int
get() = helperSet.size
override fun contains(element: T): Boolean = helperSet.contains(element)
override fun containsAll(elements: Collection): Boolean = helperSet.containsAll(elements)
override fun isEmpty(): Boolean = helperSet.isEmpty()
override fun iterator(): Iterator = helperSet.iterator()
}
假设接口的实现方法有成百上千,那一个一个写不累死,在Kotlin中委托使用的关键字是by,只需在接口声明的后面使用by关键字,再加上委托的辅助对象对象,可以免去一大堆模板代码:
class MySet(val helperSet: HashSet) : Set by helperSet{
//如果需要对某个方法重新实现,单独重写某个方法即可,其他set接口里的功能,与HashSet保持一致。
fun helloworld() = println("hello,world")
override fun isEmpty(): Boolean = false
}
委托属性的本质是将一个属性(字段)的具体实现委托给另一个类去完成:
/**
* 委托属性的句法结构如下,by关键字链接了左边的p属性和右边的Delegate实例,将p属性的具体实现委托给了Delegate类去完成
* 当调用p属性时回信自动调用Delegate的getValue方法,赋值会调用setValue方法
*/
class Myclass {
//如果声明为val则不用在Delegate中创建setValue方法。
var p by Delegate()
}
//标准代码实现模板,必须实现getValue和setValue方法,并且使用operator关键字进行声明
class Delegate {
var propValue: Any? = null
//第一个参数声明该Delegate类的委托功能可在什么类中使用,第二个参数是Kotlin的属性操作类,用于获取属性相关的值,目前用不到
//<*>为你不知道或者不关注泛型的具体类型
operator fun getValue(myclass: Myclass, prop: KProperty<*>): Any? {
return propValue
}
operator fun setValue(myclass: Myclass, prop: KProperty<*>, value: Any?) {
propValue = value
}
}
8.5.3.实现一个自己的lazy函数
懒加载技术是将延迟执行的代码放到by lazy代码块中,一开始不会执行,当调用到相应变量时,代码块中的代码才会执行。基本语法结构如下val p by lazy{...},by是关键字,lazy是高阶函数。实现一个自己的lazy函数代码如下:
package com.company
import kotlin.reflect.KProperty
//顶层函数:创建Later类的实例,并将节后首的函数类型参数传给Later类的构造函数。这样可以替代之前的lazy函数
fun later(block: () -> T) = Later(block)
//定义Later类将其指定为泛型类,接受一个函数类型参数,此函数类型参数不接受任何参数。
class Later(val block: () -> T) {
var value: Any? = null
//第一个参数为任意类型,希望Later的委托功能在所有类中都能够使用,使用一个value值进行缓存,若空,就调用构造函数中传入的函数类型参数去获取值,否则返回。
operator fun getValue(any: Any?, prop: KProperty<*>): T {
if (value == null) {
value = block()
}
return value as T
}
//懒加载技术不会对属性进行赋值,不用实现setValue方法。
}