Kotlin进行数据存储
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运用了:
模板类
因为共享参数允许保存的数据类型包括整形、浮点型、字符串等,所有将Preference定义成模板类,具体的参数类型在调用的时候再指定。
除了代表模板类泛型的T,该类还有与之相似的Any和*。
T、Any、* 三者之间的区别:
1):T是抽象的泛型,再模板类中用来占位子,外部再调用模板类时才能确定T的具体类型。
2):Any是Kotlin的基本类型,所有kotlin类都从Any派生而来,相当于Java里面的Object。
3):星号* 表示一个不确定的类型,同样也是在外部调用时才能确定。但T出现在模板类的定义中,而与模板类无关,它出现在单个函数帝国一的参数列表中,因此相当于Java里面的问号?委托属性/属性代理
注意到外部利用Preference声明参数字段的时候,后面跟着表达式“by Preference(…)”,这个by表示代理的动作。所谓的属性代理,就是说该属性的类型不变,但是属性的读写行为被后面的类接管了。
接管属性的读写行为的必要性:
举例,交电费,市民需要每个月都交电费,每个月自己跑去营业厅交钱很麻烦,后来支持在网上自主缴费,但是需要用户主动上网缴费,可能出现用户忘记缴费。所以银行推行“委托代扣”的业务,用户只要跟银行签约并指定委托扣费的电力账户,那么每个月指定时间,银行会自动从用户银行卡扣费并缴纳给指定的电力账户,免去了用户的人工操作。
委托缴费场景对应到共享参数这里,开发者的人工操作是:手工的编码从SharedPreference类读取数据和保存数据。而自动操作指的是给出一个月约定:代理的属性自动通过模板类“Preference<T>”完成数据的读取和保存,也就是说,Preference<T>接管了这希望属性的读写行为,接管后的操作即为模板类的getValue和setValue方法,因此,属性被接管的行为叫做属性代理,而被代理的属性称作委托属性。
- 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类型的变量值。 - 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部分:
- 函数头语句:
头部语句位于with的圆括号内部。它先于函数体语句执行,并且头部语句返回一个对象,函数体语句语句在该对象的命名空间中运行。也就是说,体语句可以直接调用该对象的方法,而无须显式指定头部对象的实例名称。 - 函数体语句:
体语句位于常规的大括号内部。它要等头部语句处理完毕才会执行,同时体语句在头部语句返回对象的命名空间中运行。也就是说,体语句允许直接调用头部对象的方法,而无须显式指定该对象的实例名称。
2.数据库帮助类:SQLiteOpenHelper
SQLite是手机上的轻量级数据库,但与Oracle一样存在数据库的创建、变更、删除、连接等DDL操作,以及数据表的增、删、改、查等DML操作。
Android为SQLite提供了2个管理类:SQLiteDatabase、SQLiteOpenHelper类。
- 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:获取指定字段的字段类型。
- 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个步骤:
- 在自定义的Application类内部表明一个该类的静态实例。
- 重写onCreate()发明合法,把自身对象赋值给第一步声明的实例。
- 提供一个供外部调用的静态方法getInstance(),该方法返回第一步声明的Application类实例。
上述代码同样的单例化过程通过Kotlin编码实现的话,静态属性和静态方法可利用伴生对象(理解为“人的影子”)来实现,这就形成了Kotlin单例化的第一种方式:手工声明属性单例化:
- 手工声明属性的单例化:
与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}
}
- 借助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的自身实例。
- 自定义代理行为的单例化:
前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进行数据存储相关推荐
- Kotlin开发第六天,数据存储,持久化
完整代码Gitee地址:kotlin-demo: 15天Kotlin学习计划 第六天学习内容代码:Chapter6 前言 简介 知识点1:文件存储 知识点2:sharedPreferences存储 知 ...
- Kotlin中的数据存储
数据存储 1 持久化技术简介 数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或计算机关机的情况下,这些数据仍然不会丢失. 保存在内存中的数据是处于瞬时状态的,而保存在存储设备中 ...
- Android几种数据存储的对比(MMKV、DataStore、SharedPreference)
MMKV Github地址:https://github.com/Tencent/MMKV MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf ...
- 使用 Jetpack DataStore 进行数据存储
Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象.DataStore 使用 Kotlin 协程和流程以异步.一致的事务方式存储数据. 如果您当前使 ...
- 【达内课程】数据存储
文章目录 数据存储介绍 SharedPreferences:偏好设置 I/O存储/文件存储 数据存储介绍 数据存储也称之为数据持久化.表现为将程序处理过程中需要保存的数据存储到硬盘的某个文件中.在 A ...
- Android数据存储安全规范
标题理论概述 存储数据对于移动应用程序至关重要,应将尽可能少的敏感数据存储在永久性本地存储中,但是在大多数实际场景中,必须存储某种类型的用户数据.如果敏感数据没有受到应用程序适当保护,就很容易受到攻击 ...
- 7_数据存储持久化技术
持久化技术 持久化技术就是将那些在内存中的瞬时数据存储到存储设备中,使其成为持久数据 文件存储 SharedPregerences存储 数据库存储 文件存储 数据存储到文件中 Context类中提供了 ...
- 英特尔 QLC 3D NAND 数据存储
英特尔 QLC 3D NAND 数据存储 NAND是什么 由于SSD固态硬盘的普及,NAND这个词逐渐进入用户们的视线.许多厂商都在产品宣传中提到3D NAND颗粒等词汇,对于普通用户来讲,完全不知道 ...
- Android的数据存储方式
1.Shared Preferences 2.文件存储数据 3.数据库 4.Content Provider存储数据,是所有应用程序之间数据存储和检索的一个桥梁,它的作用就是使得各个应用程序之间实现数 ...
最新文章
- html css integrity,integrity 属性
- kafka入门:简介、使用场景、设计原理、主要配置及集群搭建(转)
- nginx+keepalived高可用性负载均衡
- python安装pillow图形处理库
- JAVA内存释放机制
- 系统学习NLP(十一)--命名实体识别
- 代码高亮与美化的工具
- 如何使用FTP软件进行文件传输( 本地文件传到服务器)
- GP数据库锁表如何解锁
- 8月书讯丨11本新上好书速览(计算机+经管)
- 编写代码实现简单的扫雷游戏
- 学了python还要学什么,学python先学什么
- 预训练模型(PTMs)发展史
- 淘宝/天猫API:item_videolist-按分类搜索淘宝直播接口
- 13. 模板匹配-cv2.matchTemplate()、cv2.minMaxLoc()
- 微信小程序如何实时监测网络状态变化?
- java图片压缩工具类
- Tencent后台开发Java岗二面:Java中高级核心知识全面解析
- 电子计算机显示屏维修,液晶显示器闪烁如何处理_液晶显示器维修教程
- 仿微信换头像(旋转、还原、裁剪、缩放等)
热门文章
- Python:猜数(number guess)
- 订货管理系统远不只订货,它还有这些作用...
- 【libuv高效编程】libuv学习超详细教程3——libuv事件循环
- vue 根据当前日期获取其前七天的日期
- iOS - DZNEmptyDataSet空白页
- 快考题-免费的在线考试系统软件,PC+微信+公众号多端支持
- 【PHP】如何将表单数据上传至数据库
- 基于Android的餐馆订餐点餐app
- 虫儿飞计算机音乐,虫儿飞 MIDI File Download :: MidiShow
- sharding-jdbc数据加密实践与坑