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.使用共享参数SharedPreferences:

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

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

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

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

SharedPreferences.Editor editor=sps.edit();
editor.putString("name","尹磊");
editor.putInt("age",20);
editor.putBoolean("married",false);
editor.commit();

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

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

可以看出,共享参数的存取操作有些繁琐。因此实际开发中常将共享参数的相关操作提取到一个工具类,在新的工具类里面封装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","");

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

上述代码的运行结果和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}

可以看出,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");

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(...)
}

注:新的管理类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"/>

完成上面的权限配置后,代码里面就可以正常读写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;}
}

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

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

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

文件的读取操作:

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

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()}}

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

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

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

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

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

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

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)}//循环处理符合条件的文件

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;}@Overridepublic void onCreate(){super.onCreate();mApp=this;}
}

上述代码中,单例模式的实现过程主要有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: MainApplicationfun instance()= instance}
}
  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}
}

上述的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) valueelse throw IllegalArgumentException("application already initialized")}}

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">

Kotlin进行数据存储相关推荐

  1. Kotlin开发第六天,数据存储,持久化

    完整代码Gitee地址:kotlin-demo: 15天Kotlin学习计划 第六天学习内容代码:Chapter6 前言 简介 知识点1:文件存储 知识点2:sharedPreferences存储 知 ...

  2. Kotlin中的数据存储

    数据存储 1 持久化技术简介 数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或计算机关机的情况下,这些数据仍然不会丢失. 保存在内存中的数据是处于瞬时状态的,而保存在存储设备中 ...

  3. Android几种数据存储的对比(MMKV、DataStore、SharedPreference)

    MMKV Github地址:https://github.com/Tencent/MMKV MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf ...

  4. 使用 Jetpack DataStore 进行数据存储

    Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象.DataStore 使用 Kotlin 协程和流程以异步.一致的事务方式存储数据. 如果您当前使 ...

  5. 【达内课程】数据存储

    文章目录 数据存储介绍 SharedPreferences:偏好设置 I/O存储/文件存储 数据存储介绍 数据存储也称之为数据持久化.表现为将程序处理过程中需要保存的数据存储到硬盘的某个文件中.在 A ...

  6. Android数据存储安全规范

    标题理论概述 存储数据对于移动应用程序至关重要,应将尽可能少的敏感数据存储在永久性本地存储中,但是在大多数实际场景中,必须存储某种类型的用户数据.如果敏感数据没有受到应用程序适当保护,就很容易受到攻击 ...

  7. 7_数据存储持久化技术

    持久化技术 持久化技术就是将那些在内存中的瞬时数据存储到存储设备中,使其成为持久数据 文件存储 SharedPregerences存储 数据库存储 文件存储 数据存储到文件中 Context类中提供了 ...

  8. 英特尔 QLC 3D NAND 数据存储

    英特尔 QLC 3D NAND 数据存储 NAND是什么 由于SSD固态硬盘的普及,NAND这个词逐渐进入用户们的视线.许多厂商都在产品宣传中提到3D NAND颗粒等词汇,对于普通用户来讲,完全不知道 ...

  9. Android的数据存储方式

    1.Shared Preferences 2.文件存储数据 3.数据库 4.Content Provider存储数据,是所有应用程序之间数据存储和检索的一个桥梁,它的作用就是使得各个应用程序之间实现数 ...

最新文章

  1. html css integrity,integrity 属性
  2. kafka入门:简介、使用场景、设计原理、主要配置及集群搭建(转)
  3. nginx+keepalived高可用性负载均衡
  4. python安装pillow图形处理库
  5. JAVA内存释放机制
  6. 系统学习NLP(十一)--命名实体识别
  7. 代码高亮与美化的工具
  8. 如何使用FTP软件进行文件传输( 本地文件传到服务器)
  9. GP数据库锁表如何解锁
  10. 8月书讯丨11本新上好书速览(计算机+经管)
  11. 编写代码实现简单的扫雷游戏
  12. 学了python还要学什么,学python先学什么
  13. 预训练模型(PTMs)发展史
  14. 淘宝/天猫API:item_videolist-按分类搜索淘宝直播接口
  15. 13. 模板匹配-cv2.matchTemplate()、cv2.minMaxLoc()
  16. 微信小程序如何实时监测网络状态变化?
  17. java图片压缩工具类
  18. Tencent后台开发Java岗二面:Java中高级核心知识全面解析
  19. 电子计算机显示屏维修,液晶显示器闪烁如何处理_液晶显示器维修教程
  20. 仿微信换头像(旋转、还原、裁剪、缩放等)

热门文章

  1. Python:猜数(number guess)
  2. 订货管理系统远不只订货,它还有这些作用...
  3. 【libuv高效编程】libuv学习超详细教程3——libuv事件循环
  4. vue 根据当前日期获取其前七天的日期
  5. iOS - DZNEmptyDataSet空白页
  6. 快考题-免费的在线考试系统软件,PC+微信+公众号多端支持
  7. 【PHP】如何将表单数据上传至数据库
  8. 基于Android的餐馆订餐点餐app
  9. 虫儿飞计算机音乐,虫儿飞 MIDI File Download :: MidiShow
  10. sharding-jdbc数据加密实践与坑