当前位置:   article > 正文

Kotlin进行数据存储_managedsqliteopenhelper

managedsqliteopenhelper

Kotlin与Android的数据持久化操作


《Kotlin从0基础到精通Android开发》学习笔记(与Java对比):

学习目标:
关于Kotlin与Android的数据持久化操作,我们需要

  • 学会利用工具类Preference进行数据共享参数的键值对管理工作、并掌握委托属性、lazy修饰符、with函数的基本用法。
  • 学会使用Kotlin的ManagedSQLiteOpenHelper工具进行数据库操作编码。
  • 学会通过Kotlin的文件I/O函数库进行文件相关处理,包括文本文件读写、图片文件读写、文件目录遍历等。
  • 学会按照Koltin的编码风格实现Application的单例化,并通过单例Application操作全局变量。

	对于Android中的4种主要存储方式的用法,包括共享参数SharedPreference、数据库SQLite、文件I/O操作、App的全局变量。
  • 1

1.使用共享参数SharedPreferences:

共享参数是Android系统最简单的数据持久化存储方式。不是因为它存储结构简单,而是因为开发编码简单。即使通过Java编写共享参数读写的代码,也不过寥寥数行。那么Kotlin究竟采用了什么技术手段,优化了java的不足呢?

共享参数SharedPreferences是Android最简单的数据存储方式,常用于存储“key-value”键值对数据。在使用共享参数之前,首先要调用getSharedPreferences方法声明文件名与操作模式。

SharedPreferences sps = getSharedPreferences("share",Context.MODE_PRIVATE);
// 该方法的第一个参数是文件名,参数share表示当前的共享参数文件是share.xml
//第2个参数是操作模式,一般填MODE_PRIVATE表示私有模式
  • 1
  • 2
  • 3

使用共享参数要存储数据,要借助Editor类。

SharedPreferences.Editor editor=sps.edit();
editor.putString("name","尹磊");
editor.putInt("age",20);
editor.putBoolean("married",false);
editor.commit();
  • 1
  • 2
  • 3
  • 4
  • 5

使用共享参数读取数据直接调用其对象的get()方法即可获取数据,
注意get()方法的第二个参数表示默认值。

String name=sps.getString("name","");
int age=sps.getInt("age",0);
boolean married=sps.getBoolean("married",false);
  • 1
  • 2
  • 3

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

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

//使用工具类:
//调用工具类写入共享参数
SharedUtil.getInstance(this).writeShared("name",“尹磊”);
//调用工具类读取共享参数
String name =SharedUtil.getInstance(this).readShared("name","");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

从上面的代码可以看出,其他数据类型的数据读写还没有写,如果外部需要先读取某个字段的数值,等处理完了再写回共享参数,那么使用该工具类也要2行代码。

以下是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 findPrefence(name,default)
    }

    //利用with函数定义临时的命名空间
    private fun findPrefence(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)
            else ->   throw IllegalArgumentException("This type can be saved into Preferences")
        }
        return res as T
    }

    //接管属性值的修改行为
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        putPreference(name,value)
    }

    private fun 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)
            else -> throw IllegalArgumentException("This type can be saved into Preference")
        }.apply()//commit()方法和apply()方法都表示提交修改
    }
}

//声明字符串的委托属性
private var name: String by Preference(this,"name","")
//声明整型数类型的委托属性
private var age: Int by Preference(this,"age",0)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

上述代码的运行结果和Java的运行结果是一致的。

上述涉及到的知识点补充说明:
这个Preference运用了:

  1. 模板类
    因为共享参数允许保存的数据类型包括整形、浮点型、字符串等,所有将Preference定义成模板类,具体的参数类型在调用的时候再指定。
    除了代表模板类泛型的T,该类还有与之相似的Any和*。
    T、Any、* 三者之间的区别:
    1):T是抽象的泛型,再模板类中用来占位子,外部再调用模板类时才能确定T的具体类型。
    2):Any是Kotlin的基本类型,所有kotlin类都从Any派生而来,相当于Java里面的Object。
    3):星号* 表示一个不确定的类型,同样也是在外部调用时才能确定。但T出现在模板类的定义中,而与模板类无关,它出现在单个函数帝国一的参数列表中,因此相当于Java里面的问号?

  2. 委托属性/属性代理
    注意到外部利用Preference声明参数字段的时候,后面跟着表达式“by Preference(…)”,这个by表示代理的动作。所谓的属性代理,就是说该属性的类型不变,但是属性的读写行为被后面的类接管了。

接管属性的读写行为的必要性:
举例,交电费,市民需要每个月都交电费,每个月自己跑去营业厅交钱很麻烦,后来支持在网上自主缴费,但是需要用户主动上网缴费,可能出现用户忘记缴费。所以银行推行“委托代扣”的业务,用户只要跟银行签约并指定委托扣费的电力账户,那么每个月指定时间,银行会自动从用户银行卡扣费并缴纳给指定的电力账户,免去了用户的人工操作。
委托缴费场景对应到共享参数这里,开发者的人工操作是:手工的编码从SharedPreference类读取数据和保存数据。而自动操作指的是给出一个月约定:代理的属性自动通过模板类“Preference<T>”完成数据的读取和保存,也就是说,Preference<T>接管了这希望属性的读写行为,接管后的操作即为模板类的getValue和setValue方法,因此,属性被接管的行为叫做属性代理,而被代理的属性称作委托属性。

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

例如下方的with(){}函数使用示例:

private fun findPrefence(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)
            else ->   throw IllegalArgumentException("This type can be saved into Preferences")
        }
        return res as T
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

可以看出,with方法的函数语句分为2部分:

  1. 函数头语句:
    头部语句位于with的圆括号内部。它先于函数体语句执行,并且头部语句返回一个对象,函数体语句语句在该对象的命名空间中运行。也就是说,体语句可以直接调用该对象的方法,而无须显式指定头部对象的实例名称。
  2. 函数体语句:
    体语句位于常规的大括号内部。它要等头部语句处理完毕才会执行,同时体语句在头部语句返回对象的命名空间中运行。也就是说,体语句允许直接调用头部对象的方法,而无须显式指定该对象的实例名称。

2.数据库帮助类:SQLiteOpenHelper

SQLite是手机上的轻量级数据库,但与Oracle一样存在数据库的创建、变更、删除、连接等DDL操作,以及数据表的增、删、改、查等DML操作。
Android为SQLite提供了2个管理类:SQLiteDatabase、SQLiteOpenHelper类。

  1. SQLiteDatabase:
    其是SQLite的数据库管理类,开发者可在Activity页面代码或者能取到Context的地方获取数据库实例。
//创建数据库,如果已经存在,就打开
SQLiteDatabase db =getApplicationContext().openOrCreateDatabase("test.db",Context.MODE_PRIVATE,null);

//删除数据库
getApplicationContext().deleteDatabase("test.db");
  • 1
  • 2
  • 3
  • 4
  • 5

SQLiteDatabase的常见方法:

  • openDatabase:打开指定路径的数据库
  • isOpen:判断数据库是否已打开
  • close:关闭数据库
  • execSQL:执行拼接好的SQL控制语句。一般用于建表、删表、变更表结构。
  • delete:删除符合条件的记录。
  • update:更新符合条件的记录。
  • insert:插入一条记录。
  • query:执行查询操作,返回结果集的游标。
  • rawQuery:执行拼接好的SQL查询语句,返回结果集的游标。
    上述种,insert、update方法可直接使用的数据结构是ContentValues类,它类似于映射Map,也提供了put和get方法用来存取键值对。区别在于,ContentValues的键只能是字符串,查看ContentValues的源码会发现其内部保存键值对的数据结构就是HashMap"private HashMap<String,Object> mVlues;"。
    另外,注意表的查询操作还借助于游标类Cursor来实现,上述方法种,query和rawQuery这2个查询方法返回的都是Cursor对象,那么获取查询结果就得根据游标的指示一条条遍历结果集合。

游标Cursor类的常用方法:
·1.游标控制类方法,用于指定游标的状态。
1):close:关闭游标。
2):isClosed:判断游标是否关闭。
3):isFirst:判断游标是否在开头。
4):isLast:判断游标是否在末尾。
2.游标移动类方法,把游标移动到指定位置。
1):moveToFirst:移动游标到开头。
2):moveToLast:移动游标到末尾。
3):moveToNext:移动游标到下一个。
4):moveToPrevious:移动游标到上一个。
5):move:往后移动游标若干偏移量。
3.获取记录类方法,可获取记录的数量、类型以及取值。
1):getCount:获取记录数。
2):getInt:获取指定字段的整型值。
3):getFloat:获取指定字段的浮点数值。
4):getString:获取指定字段的字符串值。
5):getType:获取指定字段的字段类型。

  1. SQLiteOpentHelper:
    SQLite仅仅提供数据库的DDL(数据定义)和DML(数据管理)操作。SQLiteOpenHelper是SQLite的使用帮助类,它是一个数据库操作的辅助工具,用于知道开发者合理使用SQLite。
    在App开发中进行业务数据的保存和读取,按照以下步骤:
  • 新建一个数据库操作类继承自SQLiteOpenHelper,提示要重写onCreate和onUpgrade这2个方法。其中onCreate()方法只在第一次打开数据库时执行,在此可进行表结构创建的操作;onUpgrade()方法在数据库版本升高时执行,因此在onUpgrade()内部可以根据不同的新旧版本号进行表结构变更处理。
  • 要封装保证数据库安全的必要方法,包括获取单例对象、打开数据库连接、关闭数据库连接等。
    (1).获取单例对象:确保运行时数据库只被打开一次,避免重复打开数据库扔出异常。
    (2).打开数据库连接:SQLite也有锁机制,即读锁和写锁的处理,故而数据库连接分为2种:调用getReadableDatabase()【读】、getWriteableDatabase()【写】
    (3).关闭数据库连接:数据库操作完毕,应当调用SQLiteDatabase对象的close()方法关闭数据库连接。
  • 提供对表记录进行增、删、改、查的操作方法。

对于Kotlin,运用更加安全的ManagedSQLiteOpenHelper:
系统自带的SQLiteOpenHelper它并未封装数据库管理类SQLiteDataabse,造成一个后果:开发者需要操作表之前中手工打开数据库连接,然后再操作结束后手工关闭数据库连接。可是手工开关数据库连接存在着诸多问题。比如数据库连接是否造成业务异常。都制约SQLiteOpenHelper的安全性。
于是,Kotin结合Anko库推出了改良版的SQLite管理工具————ManagedSQLiteOpenHelper.它封装了数据库连接的开关操作,使得开发者完全无须关心SQLiteDatabase在何处、何时调用,避免了手工开关数据库连接可能导致的各种异常。ManagedSQLiteOpenHelper的用法与SQLiteOpenHelper几乎一模一样,唯一区别:数据表的增删改查语句需要放在use语句块之中,具体格式:

use{
	//1.插入记录
	//insert(...)
	//2.更新记录
	//update(...)
	//3.删除记录
	//delete(...)
	//4.查询记录
	//query(...)/rawQuery(...)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注:新的管理类ManagedSQLiteOpenHelper来自于Anko库,所有在文件头部,需要导入import org.jetbrains.anko.db.ManagedSQLiteOpenHelper。另外,有别于常见的anko-common包,Anko库把和数据库有关的部分放到了anko-sqlit包中,所有,需要修改模块下的build.gradle文件,在dependencies节点中补充anko-sqlite包编译配置:compile "org.jetbrains.anko:anko-sqlite:$anko_version"

3.文件I/O操作

数据库不是万能的,更多的其他格式的数据仍然要以文件形式保存。Java的文件I/O功能很强大,但是很啰嗦。

3.1文件保存空间

手机上的存储空间:内部存储、外部存储。内部存储放的是手机系统以及各应用的安装目录。外部存储放的是公共文件,如图片、文档、音视频文件等。
早期的外部存储被作为可拔插的SD卡,然而SD卡质量不统一,经常影响APP的正常运行。现如今,我们的手机把SD卡固化到手机内部,但Android仍然称之为外部存储。由于内部存储空间优先,因此为了不影响系统的流畅运行,APP运行过程中需要处理的文件都保存在外部存储空间。
为了保证App正常的读写外部存储,需要在清单文件增加SD卡权限配置:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
  • 1
  • 2
  • 3

完成上面的权限配置后,代码里面就可以正常读写SD卡的文件。Android从7.0开始加强了SD卡的权限管理,即使声明了完整的SD卡操作权限,系统仍然默认禁止该APP访问外部存储。不过系统默认关闭存储只是关闭外部存储的公共空间,外部存储的私有空间仍然可以正常读写。因为Android把外部存储分为了2块区域:所有应用均可访问的公共空间、只有应用自己才可以访问的专享空间。
内部存储保存着每个应用的安装目录,但是安装目录的空间很紧张,所有Android在SD卡的“Android/data”目录下给每个应用又单独建了一个文件目录,用于给应用保存自己需要处理的临时文件。这个给每个应用单独建立的文件目录只有当前应用才能够读写文件,其他应用是不允许进行读写的,所以"Android/data"目录算是外部存储上的私有空间。这个私有空间本身已经做了访问权限的控制,因此它不受系统禁止访问的影响,应用操作自己的文件目录就不成问题。私有文件的目录只有属主应用才能访问,所以一旦属主应用被用户卸载,那么对应的文件目录也会被一起清理掉。

关于外部存储中的2个空间的路径获取方法:
1.获取公共空间的存储路径调用的是Environment.getExternalStoragePublicDirectory()
2.获取应用私有空间的存储路径调用的是:getExternalFilesDir()
Android 7.0之后默认禁止访问公共存储目录。

3.2 读写文本文件

Java中常常封装一个文件工具类:

public class FileUtil{
	//保存文本文件
	public static void saveText(String path,String txt){
		try{
			FileOutPutStream fos=new FileOutPutStream(path);
			fos.write(txt.getBytes());
			fos.close();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	//读取文本文件
	public static String openText(String path){
		   String readStr="";
		   try{
		  		FileInputStream fis=new FileInputStream(path);
		  		byte[] b=new byte[fis.available()];
		  		fis.read(b);
		  		readStr=new String(b);
		  		fis.close();
		   }catch(Exception e){
		   	e.printStackTrace();
		   }
		   return readStr;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

上述Java代码,“长!”那么我们来看看Kotlin怎么处理的:
文件的写入操作:

//把文本写入文件
File(file_path).writeText(content)
  • 1
  • 2

如要往源文件追加文本,则可调用appendText()。

文件的读取操作:

  • readText:读取文本形式的文件内容。
  • readLines:按行读取文件内容。返回一个字符串List,文件有多少行,队列中就有多少个元素。
//从文件中读取全部的文本:
val content=File(file_path).readText()
  • 1
  • 2

3.3 读写图片文件

像图片等2进制格式的文件,可通过字节数组的形式写入文件,kotlin提供了writeBytes()方法用于覆盖写入字节数组,也提供了appendBytes()方法用于追加数组。
但是由于图像存储比较特殊,涉及到压缩格式与压缩质量,因此还得通过输出流来处理(Bitmap的compress()方法要求的)

//图片文件的写入代码
fun saveImage(path: String,bitmap: Bitmap){
            try{
                val file= File(path)
                //outputStream获取文件的输出流对象
                //writer获取文件的writer对象
                //printWriter获取文件的PrintWriter对象
                val fos:OutputStream =file.outputStream()
                //压缩格式未JPG图像,压缩质量为80%
                bitmap.compress(Bitmap.CompressFormat.JPEG,80,fos)
                fos.flush()
                fos.close()
            }catch (e: Exception){
                e.printStackTrace()
            }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

要想从图片文件中读取位图信息,按上面的writeBytes使用说明,应调用readBytes()方法。该办法可行,Android的BitmapFactory刚好提供了decodeByteArray()函数,用于从字节数组中解析位图:

//方式1:利用字节数组读取位图
//readBytes读取字节数组形式的文件内容
val bytes=File(file_path).readBytes()
//decodeByteArray从字节数组中解析图片
val bitmap=BitmapFactory.decodeByteArray(bytes,0,bytes.size)
  • 1
  • 2
  • 3
  • 4
  • 5

将位图保存为图片文件时,通过输出流进行处理;反过来,从文件读取位图数据也可以用输入流来实现。BitmapFactory的decodeStream()方法使得输入流解析位图变成现实:

//方式2:利用输入流读取位图
//inputStream获取文件的输入流对象
val fis=File(file_path).inputStream()
//decodeStream从输入流解析图片
val bitmap=BitmapFactory.decodeStream(fis)
fis.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上述的2种读取图片文件的方式都包含了:先从File对象获得文本内容,再利用BitmapFactory解码成位图。
大招:decodeFile。只要给出图片文件的完整路径,文件读取和 位图解析的操作都一起搞定。

//方式3:直接从文件路径获取位图
//decodeFile从指定路径解析图片
val bitmap=BitmapFactory.decodeFile(file_path)
  • 1
  • 2
  • 3

3.4 遍历文件目录

Kotlin把目录遍历重新梳理了以下,归纳为FileTreeWalk文件树,通过给文件树设置各式各样的参数与条件即可化繁为简,轻松获取文件的搜索结果。
文件树首先调用File对象的walk方法得到FileTreeWalk实例,接着依次为该实例设置具体的条件,包括遍历深度、是否匹配文件夹、文件扩展名以及最后的文件队列循环处理。

    var fileNames:MutableList<String> =mutableListOf()
    //在该目录下走一圈,得到文件目录树结构
    val fileTree:FileTreeWalk = File(mPath).walk()
    fileTree.maxDepth(1)//需遍历的目录层级为1,即无须检查子目录
            .filter{it.isFile}//只挑选文件,不处理文件夹
            //.filter{it.extension == "text"}//选择扩展名为txt的文本文件
            .filter{it.extension in listOf("png","jpg")}//选择扩展名为png和jpg的图片文件
            .forEach{fileNames.add(it.name)}//循环处理符合条件的文件
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4.Application全局变量

Application是android的又一组件,它的生命周期接连着app的整个运行过程,因此,开发者常常给自定义的Application运用单例模式,使之具备全部变量的管理功能。

4.1 Application单例化

在App运行过程中,有且仅有一个Application对象贯穿应用的整个生命周期,所以适合在Application中保存应用运行时的全局变量,而开展该工作的基础是必须获得Application对象的唯一实例,也就是Application单例化。获取一个类的单例对象需要运用程序设计中常见的单例模式,通过Java编码实现单例化:

public class MainApplication extends Application{
	private static MainApplication mApp;
	public static MainApplication  getInstance(){
		return mApp;
	}
	@Override
	public void onCreate(){
		super.onCreate();
		mApp=this;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

上述代码中,单例模式的实现过程主要有3个步骤:

  1. 在自定义的Application类内部表明一个该类的静态实例。
  2. 重写onCreate()发明合法,把自身对象赋值给第一步声明的实例。
  3. 提供一个供外部调用的静态方法getInstance(),该方法返回第一步声明的Application类实例。

上述代码同样的单例化过程通过Kotlin编码实现的话,静态属性和静态方法可利用伴生对象(理解为“人的影子”)来实现,这就形成了Kotlin单例化的第一种方式:手工声明属性单例化:

  1. 手工声明属性的单例化:
    与Java的不同之处在于,Kotlin引入了空安全机制,所以静态属性要声明为可空变量,然后获得实例时要在末尾加上“!!”表示非空,也可以事先将自身实例声明为延迟初始化属性。2种声明手段都是为了确保Application类提供给外部访问的自身实例必须是非空的。
class MainApplication : Application(){
    override fun onCreate() {
        super.onCreate()
        instance=this
    }
    
    //单例化的第一种方式:声明一个简单的application属性
    companion object {
        //情况1:声明可空的属性
//        private var instance: MainApplication? =null
//        fun instance()=instance!!
        //情况2:声明延迟初始化属性
        private lateinit var instance: MainApplication
        fun instance()= instance
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. 借助Delegates的委托属性单例化:
    方式1的单例化虽然提供了2种属性的声明手段,但只是为了保证自身实例的非空。如果仅仅是确保属性非空,Kotlin提供了一个系统工具进行自动校验———Delegate的notNull()方法。该方法返回非空校验的行为,只要将其属性的读写行为委托给这个非空校验行为,开发者就不需要手工进行非空判断。利用Delegates工具的属性代理功能就构成了Kotlin的第二种单例化方式。
//利用系统代理行为实现单例化的kotlin代码:
class MainApplication : Application(){
    override fun onCreate() {
        super.onCreate()
        instance=this
    }
    
    //单例化的第二种方式:利用系统字带的Delegates生成委托属性。
    companion object {
        private var instance: MainApplication by Delegates.notNull()
        fun instance() = instance
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上述的2种方式获取Application实例是一样的,带哦有"MainApplication.instance()"这个函数获得Application的自身实例。

  1. 自定义代理行为的单例化:
    前2种单例都只完成了非空校验,不是严格意义上的单例化。
    真正的单例化是有且仅有一次赋值操作。(前2种单例化并未实现唯一赋值功能)
    系统字带的Delegates工具没有提供校验行为。所以开发者必须自己写一个能够校验赋值次数的行为类,目的是接管委托属性的读写行为。(参考前面的Preference<T>完成自定义的委托行为编码)。
//自定义代理行为的单例化代码
class MainApplication : Application(){
    override fun onCreate() {
        super.onCreate()
        instance=this
    }

    //单例化的第三种方式:自定义一个非空且只能一次性赋值的委托属性
    companion object {
        private var instance:MainApplication by NotNullSingleValueVar()
        fun  instance() = instance
    }

    //定义一个属性管理类,进行非空和重复赋值的判断
    private class  NotNullSingleValueVar<T>() : ReadWriteProperty<Any?,T>{
        private var value: T? =null
        //非空的校验
        override fun getValue(thisRef: Any?, property: KProperty<*>): T {
            return value ?: throw IllegalArgumentException("application not initialized")
        }
		
		//重复赋值的校验
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
            this.value=if (this.value==null) value
            else throw IllegalArgumentException("application already initialized")
        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

4.2 利用Application实现全局变量

一旦有了单例的Application对象,就意味着App在运行程序过程中获取的Application实例是唯一的。因此可在该实例内部声明几个静态成员变量,从而形成所谓的全局变量。
全局的意思就是其他代码都可以引用该变量,因此,全局变量是共享数据和传递信息的好方法。
适合在Application中保存的全局变量主要由以下数据:

  • 会频繁读取的信息,例如用户名、手机号等
  • 从网络上获取的临时数据,为节约流量也为减少用户等待时间,想暂时放在内存中供下次使用,例如应用logo、商品图片等
  • 容易因频繁分配内存而导致内存泄漏的对象,例如处理器Handler、线程池ThreadPool等。

通过Applicaiton 实现全局变量的读写,需要:

  • 写一个类MainApplication继承自Application,该类要采用单例模式,内部声明自身的一个静态单例对象,在创建App时把自身赋值给这个静态实例,然后提供一个访问该静态对象的instance函数。
  • 在Activity中调用MinApplication的instance方法,获取MainApplication的一个静态对象,便可通过该对象访问MainApplication 的公共变量和公共方法。
  • 在清单文件中注册新定义的Application类名,即在application节点中增加android:name属性,“android:name=.MainApplication”
//在清单文件中的配置:
<application 
android:name=".MainApplication"(替换成自己继承Application的类的类名)
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
  • 1
  • 2
  • 3
  • 4
  • 5
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/277366
推荐阅读
相关标签
  

闽ICP备14008679号