共享参数SharedPreferences是Android最简单的数据存储方式,常用于存取“Key-Value”键值对数据。在使用共享参数之前,要先调用getSharedPreferences方法声明文件名与操作模式,示例代码如下:

    SharedPreferences sps = getSharedPreferences("share", Context.MODE_PRIVATE);

该方法的第一个参数是文件名,例子中的share表示当前的共享参数文件是share.xml;第二个参数是操作模式,一般填MODE_PRIVATE表示私有模式。
共享参数若要存储数据则需借助于Editor类,示例的Java代码如下:

    SharedPreferences.Editor editor = sps.edit();
editor.putString("name", "阿四");
editor.putInt("age", 25);
editor.putBoolean("married", false);
editor.putFloat("weight", 50f);
editor.commit();

使用共享参数读取数据则相对简单,直接调用其对象的get方法即可获取数据,注意get方法的第二个参数表示默认值,示例的Java代码如下:

    String name = sps.getString("name", "");
int age = sps.getInt("age", 0);
boolean married = sps.getBoolean("married", false);
float weight = sps.getFloat("weight", 0);

从上述数据读写的代码可以看出,共享参数的存取操作有些繁琐,因此实际开发常将共享参数相关操作提取到一个工具类,在新的工具类中封装SharedPreferences的常用操作,下面便是一个共享参数工具类的Java代码例子:

public class SharedUtil {
private static SharedUtil mUtil;
private static SharedPreferences mShared; public static SharedUtil getIntance(Context ctx) {
if (mUtil == null) {
mUtil = new SharedUtil();
}
mShared = ctx.getSharedPreferences("share", Context.MODE_PRIVATE);
return mUtil;
} public void writeShared(String key, String value) {
SharedPreferences.Editor editor = mShared.edit();
editor.putString(key, value);
editor.commit();
} public String readShared(String key, String defaultValue) {
return mShared.getString(key, defaultValue);
}
}

有了共享参数工具类,外部读写SharedPreferences就比较方便了,比如下面的Java代码,无论是往共享参数写数据还是从共享参数读数据,均只要一行代码:

    //调用工具类写入共享参数
SharedUtil.getIntance(this).writeShared("name", "阿四");
//调用工具类读取共享参数
String name = SharedUtil.getIntance(this).readShared("name", "");

不过这个工具类并不完善,因为它只支持字符串String类型的数据读写,并不支持整型、浮点数、布尔型等其它类型的数据读写。另外,如果外部需要先读取某个字段的数值,等处理完了再写回共享参数,则使用工具类也要两行代码(一行读数据、一行写数据),依旧有欠简洁。找毛病其实都是容易的,如果仍然使用Java编码,能完善的就完善,不能完善的也不必苛求了。
之所以挑Java实现方式的毛病,倒不是因为看它不顺眼整天吹毛求疵,而是因为Kotlin有更好的解决办法。为了趁热打铁方便比较两种方式的优劣,下面开门见山直接给出Kotlin封装共享参数的实现代码例子:

class Preference<T>(val context: Context, val name: String, val default: T) : ReadWriteProperty<Any?, T> {

    val prefs: SharedPreferences by lazy { context.getSharedPreferences("default", Context.MODE_PRIVATE) }

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return findPreference(name, default)
} override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
putPreference(name, value)
} private fun <T> findPreference(name: String, default: T): T = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is String -> getString(name, default)
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}
return res as T
} private fun <T> putPreference(name: String, value: T) = with(prefs.edit()) {
//putInt、putString等方法返回Editor对象
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}.apply() //commit方法和apply方法都表示提交修改
}
}

外部在使用该工具类之时,可在Activity代码中声明来自于Preference的委托属性,委托属性一旦声明,则它的初始值便是从共享参数读取的数值;后续代码若给委托属性赋值,则立即触发写入动作,把该属性的最新值保存到共享参数中。于是外部操作共享参数的某个字段,真正要书写的仅仅是下面的一行委托属性声明代码:

    //声明字符串类型的委托属性
private var name: String by Preference(this, "name", "")
//声明整型数类型的委托属性
private var age: Int by Preference(this, "age", 0)

既然Kotlin对共享参数的处理也如此传神,那么大家肯定很好奇,这个高大上的Preference究竟运用了哪些黑科技呢?且待笔者下面细细道来:
一、模板类
因为共享参数允许保存的数据类型包括整型、浮点数、字符串等等,所以Preference定义成模板类,具体的参数类型在调用之时再指定。
除却代表模板类泛型的T,该类中还有两个与之相似的元素,分别是Any和*,各自表示不同的涵义。下面简单说明一下T、Any和*三者之间的区别:
1、T是抽象的泛型,在模板类中用来占位子,外部调用模板类时才能确定T的具体类型;
2、Any是Kotlin的基本类型,所有Kotlin类都从Any派生而来,故而它相当于Java里面的Object;
3、*星号表示一个不确定的类型,同样也是在外部调用时才能确定,这点跟T比较像,但T出现在模板类的定义中,而*与模板类无关,它出现在单个函数定义的参数列表中,因此星号相当于Java里面的问号?;

二、委托属性/属性代理
注意到外部利用Preference声明参数字段时,后面跟着表达式“by Preference(...)”,这个by表示代理动作,早在第五章的“5.3.4 接口代理”就介绍了如何让类通过关键字by实现指定接口的代理,当时举例说明给不同的鸟类赋予不同的动作。第五章的例子是接口代理或称类代理,而这里则为属性代理,所谓属性代理,是说该属性的类型不变,但是属性的读写行为被后面的类接管了。
为什么需要接管属性的读写行为呢?举个例子,市民每个月都要交电费,自己每月跑去电力营业厅交钱显然够呛,于是后来支持在电力网站上自助缴费,然而上网缴费仍显麻烦,因为需要用户主动上网付费,要是用户忘记就不好办了。所以很多银行都推出了“委托代扣”的业务,只要用户跟银行签约并指定委托扣费的电力账户,那么在每个月指定时间,银行会自动从用户银行卡中扣费并缴纳给指定的电力账户,如此省却了用户的人工操作。
现实生活中的委托扣费场景,对应到共享参数这里,开发者的人工操作指的是手工编码从SharedPreferences类读取数据和保存数据,而自动操作指的是约定代理的属性自动通过模板类Preference<T>完成数据的读取和保存,也就是说,Preference<T>接管了这些属性的读写行为,接管后的操作则是模板类的getValue和setValue方法。属性被接管的行为叫做属性代理,而被代理的属性称作委托属性。

三、关键字lazy
模板类Preference<T>声明了一个共享参数的prefs对象,其中用到了关键字lazy,lazy的意思是懒惰,表示只在该属性第一次使用时执行初始化。联想到Kotlin还有类似的关键字名叫lateinit,意思是延迟初始化,加上lazy可以归纳出Kotlin变量的三种初始化操作,具体说明如下:
1、声明时赋值:这是最常见的变量初始化,在声明某个变量时,立即在后面通过等号“=”给它赋予具体的值。
2、lateinit延迟初始化:变量声明时没有马上赋值,但该变量仍是个非空变量,何时初始化由开发者编码决定。
3、lazy首次使用时初始化:声明变量时指定初始化动作,但该动作要等到变量第一次使用时才进行初始化。
此处的prefs对象使用lazy规定了属性值在首次使用时初始化,且初始化动作通过by后面的表达式来指定,即“{ context.getSharedPreferences("default", Context.MODE_PRIVATE) }”。连同大括号在内的这个表达式,其实是个匿名实例,它内部定义了prefs对象的初始化语句,并返回SharedPreferences类型的变量值。

四、with函数
with函数的书写格式形如“with(函数头语句) { 函数体语句 }”,看这架势,with方法的函数语句分为两部分,详述如下:
1、函数头语句:头部语句位于紧跟with的圆括号内部。它先于函数体语句执行,并且头部语句返回一个对象,函数体语句在该对象的命名空间中运行;即体语句可以直接调用该对象的方法,而无需显式指定该对象的实例名称。
2、函数体语句:体语句位于常规的大括号内部。它要等头部语句执行完毕才会执行,同时体语句在头部语句返回对象的命名空间中运行;即体语句允许直接调用头部对象的方法,而无需显式指定该对象的实例名称。
综上所述,在模板类Preference<T>的编码过程中,联合运用了Kotlin的多项黑科技,方才实现了优于Java的共享参数操作方式。

Kotlin入门(25)共享参数模板的更多相关文章

  1. Kotlin入门(28)Application单例化

    Application是Android的又一大组件,在App运行过程中,有且仅有一个Application对象贯穿应用的整个生命周期,所以适合在Application中保存应用运行时的全局变量.而开展 ...

  2. Kotlin入门教程——目录索引

    Kotlin是谷歌官方认可的Android开发语言,Android Studio从3.0版本开始就内置了Kotlin,所以未来在App开发中Kotlin取代Java是大势所趋,就像当初Android ...

  3. Kotlin入门(10)七十二变的输入参数

    上一篇文章介绍了Kotlin对函数的基本用法,包括函数的定义.输入参数的声明.输出参数的声明等等,这些足够对付简单的场合了.当然了,倘若一门新语言仅仅满足于这些雕虫小技,那也实在没什么前途.既然Kot ...

  4. 写给Android开发者的Kotlin入门

    写给Android开发者的Kotlin入门 转 https://www.jianshu.com/p/bb53cba6c8f4 Google在今年的IO大会上宣布,将Android开发的官方语言更换为K ...

  5. Kotlin入门(11)江湖绝技之特殊函数

    上一篇文章介绍了Kotlin对函数的输入参数所做的增强之处,其实函数这块Kotlin还有好些重大改进,集中体现在几类特殊函数,比如泛型函数.内联函数.扩展函数.尾递归函数.高阶函数等等,因此本篇文章就 ...

  6. Kotlin入门(15)独门秘笈之特殊类

    上一篇文章介绍了Kotlin的几种开放性修饰符,以及如何从基类派生出子类,其中提到了被abstract修饰的抽象类.除了与Java共有的抽象类,Kotlin还新增了好几种特殊类,这些特殊类分别适应不同 ...

  7. Kotlin入门第一课:从对比Java开始

    1. 介绍 今年初,甲骨文再次对谷歌所谓的安卓侵权使用Java提起诉讼,要求后者赔偿高达90亿美元.随后便传出谷歌因此计划将主力语言切换到苹果主导的Swift,不过这事后来没了跟进. 但谷歌在这两天的 ...

  8. Kotlin入门(32)网络接口访问

    手机上的资源毕竟有限,为了获取更丰富的信息,就得到辽阔的互联网大海上冲浪.对于App自身,也要经常与服务器交互,以便获取最新的数据显示到界面上.这个客户端与服务端之间的信息交互,基本使用HTTP协议进 ...

  9. Kotlin入门(26)数据库ManagedSQLiteOpenHelper

    共享参数毕竟只能存储简单的键值对数据,如果需要存取更复杂的关系型数据,就要用到数据库SQLite了.尽管SQLite只是手机上的轻量级数据库,但它麻雀虽小.五脏俱全,与Oracle一样存在数据库的创建 ...

随机推荐

  1. PostgreSQL踩坑现场

    1.PostgreSQL表名.字段名.别名等大小敏感,默认都会转化成小写形式.如果名字中有大写字母,必须分别添加双引号.在写后台时,注意添加\ 如表名:TestTable中有个字段名userName ...

  2. Hibernate实体类注解中如何忽略某些字段的映射

    使用注解 @Transient在该字段上 例如: @Transient private int  name: 这样在映射数据表和对象关系时候就不会报在表中不存在该字段的问题:

  3. mktemp 命令

    Linux mktemp命令用于建立暂存文件. mktemp建立的一个暂存文件,供shell script使用. mktemp命令专门用来创建临时文件,并且其创建的临时文件是唯一的.shell会根据m ...

  4. ES6躬行记(1)——let和const

    古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...

  5. MySQL的使用及优化

    前言 最近听了公司里的同事做的技术分享,然后觉得对自己还是挺有帮助的.都是一些日常需要注意的地方,我们目前在开发过程中,其实用不到MySQL太深的内容的.只是能适用我们日常开发的知识就可以了.所以我将 ...

  6. HTTP协议学习(一)

    一.HTTP报文的组成 请求报文由 请求行.请求头.请求空行.请求实体四部分组成.其中,请求行和请求头共同组成 请求报文头部 请求行:一行,依次由 请求方法.URI(或者应该说是域名?).HTTP协议 ...

  7. 在go modules中使用replace替换无法直接获取的package(golang.org/x/...)

    上一篇里我们介绍了使用go get进行包管理. 不过因为某些未知原因,并不是所有的包都能直接用go get获取到,这时我们就需要使用go modules的replace功能了.(当然大部分问题挂个梯子 ...

  8. 【转】探讨:ASP.NET技术的学习顺序问题

    摘要:很多人对于ASP.NET的入门和学习顺序比较迷茫,今天让我们一起来跟随作者的思路学习探讨ASP.NET的学习顺序问题,希望有所帮助. 如果你已经有较多的面向对象开发经验,跳过以下这两步: 第一步 ...

  9. Linq中的Select与Select many

    Select与Select Many 之前在项目中查询数据库中的数据,都是通过sql语句来查询的,但是随着时代的发展,微软在.Net Framework 4.5版中推出的一个主要的特性——LINQ. ...

  10. JQuery官方学习资料(译):避免与其他库的冲突

    避免与其他库的冲突     JQuery库和它所有的插件都是包含在jQuery命名空间下的,作为一般规则,全局对象被存储在jQuery的命名空间内,所以你不会得到jQuery和任何其他库之间的冲突(例 ...