欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Kotlin入门(25)共享参数模板

程序员文章站 2022-07-02 15:04:53
共享参数SharedPreferences是Android最简单的数据存储方式,常用于存取“Key-Value”键值对数据。在使用共享参数之前,要先调用getSharedPreferences方法声明文件名与操作模式,示例代码如下: 该方法的第一个参数是文件名,例子中的share表示当前的共享参数文 ......

共享参数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的共享参数操作方式。