EasyBundle是开源基础组件集成库EasyAndroid中的基础组件之一。其做用是:优雅的进行Bundle数据存取java
EasyAndroid做为一款集成组件库,此库中所集成的组件,均包含如下特色,你能够放心使用~~android
得益于编码时的高内聚性
,若你只须要使用EasyBundle. 那么能够直接去copy EasyBundle源码文件到你的项目中,直接进行使用,也是没问题的。git
咱们先来与原生
使用方式进行一下对比
。以便让你们能对EasyBundle
的用法有个大概的概念github
假设咱们有如下一批数据,须要进行存储json
类型 | 值 |
---|---|
Int | age |
String | name |
val bundle = getBundle()
bundle.putInt("age", age)
bundle.putString("name", name)
复制代码
val bundle:Bundle = EasyBundle.create(getBundle())
.put("age", age)
.put("name", name)
.getBundle()
复制代码
不一样类型
, 选择不一样api
进行读取val bundle = getBundle()
val age:Int = bundle.getInt("age")
val name:String = bundle.getString("name")
复制代码
val easyBundle = EasyBundle.create(getBundle())
val age = easyBundle.get<Int>("age")
val name = easyBundle.get<String>("name")
复制代码
class ExampleActivity:Activity() {
var age:Int = 0
var name:String = ""
override fun onCreate(saveInstanceState:Bundle?) {
super.onCreate(saveInstanceState)
intent?.let{
age = it.getIntExtra("age", 0)
name = it.getStringExtra("name")
}
}
}
复制代码
class BaseActivity() {
override fun onCreate(saveInstanceState:Bundle?) {
super.onCreate(saveInstanceState)
// 在基类中直接配置注入入口,将intent中的数据注入到配置了BundleField注解的变量中去
EasyBundle.toEntity(this, intent?.extras)
}
}
class ExampleActivity:BaseActivity() {
// 在对应的字段上添加BundleField便可
@BundleField
var age:Int = 0
@BundleField
var name:String = ""
...
}
复制代码
class ExampleActivity:Activity() {
var age:Int = 0
var name:String = ""
// 原生方式。须要手动一个个的进行数据存储
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
outState?.let{
it.putInt("age", age)
it.putString("name", name)
}
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
saveInstanceState?.let {
age = it.getIntExtra("age", 0)
name = it.getStringExtra("name")
}
}
}
复制代码
// 直接在基类中进行基础注入配置便可
class BaseActivity() {
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
EasyBundle.toBundle(this, outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
EasyBundle.toEntity(this, savedInstanceState)
}
}
复制代码
以上便是EasyBundle的各类主要使用方式。但愿能让你们对EasyBundle的主要功能先有个大体了解。api
EasyBundle是对Bundle的存取操做进行封装的,那么确定咱们会须要绑定一个Bundle对应进行操做bash
val easyBundle:EasyBundle = EasyBundle.create(bundle)
复制代码
而后,经过easyBundle操做完数据后,取出操做后的bundle数据进行使用:网络
val bundle:Bundle = easyBundle.bundle
复制代码
若建立时传递进入的bundle为null
。则将新建一个空的bundle容器
进行数据存储框架
fun create(source:Bundle? = null): EasyBundle {
return EasyBundle(source?: Bundle())
}
复制代码
因此。咱们再返回去看上面的存储示例代码,就很清晰了:ide
val bundle:Bundle = EasyBundle.create(getBundle())
.put("age", age)
.put("name", name)
.getBundle()
复制代码
从上面的示例中咱们能够看得出来:相比于原生方式(须要针对不一样类型数据
使用不一样的api
进行数据存取), EasyBundle
统一了存取的api:
put(key:String, value:Any)
方法一个个进行存储:easyBundle.put(key1, value1)
.put(key2, value2)// 支持链式调用
复制代码
put(vararg params:Pair<String, Any>)
进行多数据同时存储easyBundle.put(
key1 to value1,
key2 to value2
...
)
复制代码
put(params:Map<String, Any>)
val map:Map<String, Any> = getMap()
easyBundle.put(map)
复制代码
统一了数据的存储入口。理所固然的,EasyBundle
也统一了数据的读取入口:
须要进行读取时。能够经过内联函数get<T>(key:String)
读取指定数据.
好比读取实现了Parcelable接口的User
实例:
val user = easyBundle.get<User>("user")
复制代码
而在java环境下。由于没有内联函数可用,因此你也可使用get(key:String, type:Class<*>)
方法进行读取
User user = easyBundle.get("user", User.class)
复制代码
都知道,Bundle的存取api那么复杂,主要是须要过滤掉不被系统容许的非序列化数据
。
因此常常性的。有时候咱们在开发中,忽然会须要将一个普通的实体类
传递到下一个页面。这个时候就会须要对这个类进行序列化修改。
虽然实际上对类进行实现序列化接口仍是很简单的。可是常常须要去实现,也是让人神烦的。
解决办法其实很简单,参考经典的网络通讯模型便可:使用JSON做为中转类型进行通讯
如下方的User为例:
class User() {
val name:String? = null
}
复制代码
进行存储
easyBundle.put("user", user)
复制代码
存储时,自动对user进行类型检查
,发现此类型不被bundle所支持存储
,因此会将user经过fastjson
或者gson
进行json序列化转码
后,再进行存储.
核心源码展现
fun put(name:String, value:Any?) {
...
when (value) {
// 首先,对于Bundle支持的数据类型。自动选择正确的api进行存储
is Int -> bundle.putInt(name, value)
is Long -> bundle.putLong(name, value)
...
// 对于Bundle不支持的数据类型。转换为临时中间JSON数据再进行存储
else -> bundle.putString(name, toJSON(value))
}
}
复制代码
进行读取
val user:User = easyBundle.get<User>("user")
复制代码
读取时,从bundle中取出的是json字串
。与指定类型User
不匹配。则将经过fastjson
或者gson
进行json反序列化解析
后。再进行返回:
除了此处所举例的JSON数据自动转换兼容
方案。还有一种是基本数据类型转换兼容
:
好比当前bundle中放入了数字的字符串:
easyBundle.put("number", "10086")
复制代码
虽然咱们存入的时候是String类型数据。可是内容其实是能够转为int的。那么咱们也能够直接指定接受者类型为int
来进行读取:
val number:Int = easyBundle.get<Int>("number")
复制代码
基本类型兼容
的方式。在使用路由的项目下进行使用。很是好用:
由于路由框架中,url的参数部分,大部分都是直接以String的格式进行解析、传递的
核心源码展现:
fun <T> get(key:String, type:Class<T>):T? {
var value = bundle.get(key) ?: return returnsValue(null, type) as T?
// 当取出数据类型与指定类型匹配时。直接返回
if (type.isInstance(value)) {
return value as T
}
if (value !is String) {
// 对于数据类型不为String的,先行转换为json。
value = toJSON(value)
}
// 处理两种状况下的数据自动转换:
val result = when(type.canonicalName) {
// 第一种:基本数据类型数据自动转换兼容
"byte", "java.lang.Byte" -> value.toByte()
"short", "java.lang.Short" -> value.toShort()
...
// 第二种:JSON数据自动解析兼容
else -> parseJSON(value, type)
}
return result as T
}
复制代码
关于EasyBundle中,json中转数据的说明
在EasyBundle中。并无直接依赖fastjson
与gson
解析库。而是经过在运行时进行json库匹配
。使用当前的运行环境所支持的json解析库
:
// 当前运行环境下。是否存在fastjson
private val FASTJSON by lazy { return@lazy exist("com.alibaba.fastjson.JSON") }
// 当前运行环境下,是否存在gson
private val GSON by lazy { return@lazy exist("com.google.gson.Gson") }
// 进行json库判断。优先使用gson
private fun toJSON(value:Any) = when {
GSON -> Gson().toJson(value)
FASTJSON -> JSON.toJSONString(value)
else -> throw RuntimeException("Please make sure your project support [FASTJSON] or [GSON] to be used")
}
private fun parseJSON(json:String, clazz: Class<*>) = when {
GSON -> Gson().fromJson(json, clazz)
FASTJSON -> JSON.parseObject(json, clazz)
else -> throw RuntimeException("Please make sure your project support [FASTJSON] or [GSON] to be used")
}
复制代码
因此,彻底不用担忧会引入新的不须要的库进来。并且,相信大部分的项目中也确定有fastjson
与gson
至少其中一种解析库。
EasyBundle
提供了BundleField
注解。用于提供双向数据注入
功能。
双向注入的意思便是:便可以将数据从实体类中
注入到bundle容器中
,也能够从bundle容器中
注入到实体类中
:
举个栗子,这是个普通bean类,存储着用户信息:
class User(var name:String, var arg:Int, var address:String)
复制代码
而后。正常模式下。当咱们须要将这些数据存储到bundle中去时:
val user = getUser()
bundle.putString("name", user.name)
bundle.putInt("age", user.age)
bundle.putString("address", user.address)
复制代码
或者,须要从bundle中将对应的数据取出来并赋值给user:
user.name = bundle.getString("name")
user.age = bundle.getInt("age")
user.address = bundle.getString("address")
复制代码
可是,若是你使用EasyBundle
提供的双向数据注入
功能,就很简单了:
1. 为须要进行注入的字段。添加注解:
class User(@BundleField var name:String,
@BundleField var arg:Int,
@BundleField var address:String)
复制代码
2. 将数据从User中注入到bundle中进行保存
EasyBundle.toBundle(user, bundle)
复制代码
3. 将数据从bundle中,读取并注入到User实例中去:
EasyBundle.toEntity(user, bundle)
复制代码
效果与上方的原始写法一致。且更加方便、更加简洁、更增强大
。
通常来讲。直接使用@BundleField
时。默认使用的key值是字段名
。
可是有时候,咱们会须要对key值进行重设:
class Entity(@BundleField("reset_name") var name:String)
复制代码
在进行数据存取的过程当中,很难避免不会出现存取异常。好比说。你存的是"Hello,World"
, 可是取的时候你却取成了Int
。或者存的是json。可是读取的时候,进行json解析错误时。这些状况下都会致使抛出不可期的异常
因此BundleField
提供了throwable
参数:
@BundleField(throwable = false)
var user:User
复制代码
throwable
类型为Boolean。表明当存取时发生异常时。是否将此异常向上抛出。(默认为false)
上面虽说了那么长一截,可是若是没有具体的使用场景示例的支撑。可能会有部分朋友不太理解: 你说了那么多,然而又有什么卵用?
下面我就举例一些使用场景。进行一些具体的说明:
这其实能够说是主要的使用场景。在Activity中进行使用,获取启动时传递的数据:
class UserActivity:Activity() {
@BundleField
lateinit var name:String
@BundleField
lateinit var uid:String
override fun onCreate(saveInstanceState:Bundle?) {
// 将intent中的数据。注入到当前类中
EasyBundle.toEntity(this, intent?.extras)
}
}
复制代码
固然。其实每次有个新页面。都去写一次EasyBundle.toEntity
也是挺蛋疼的
其实注入方法是能够放入基类的。作到一次基类配置,全部子类共用
class BaseActivity:Activity() {
override fun onCreate(saveInstanceState:Bundle?) {
// 将intent中的数据。注入到当前类中
EasyBundle.toEntity(this, intent?.extras)
...
}
}
复制代码
并且。使用此种方式,有个很显著的优势:好比对于上方所示的UserActivity
页面来讲。此页面须要的数据就是name
与uid
,一目了然~
照原生的方式。咱们在进行现场保护时,会须要本身去将关键状态数据
一个个的手动存入saveInstanceState
中去,须要恢复数据时,又须要一个个的去手动读取数据
.
好比像下方的页面:
class PersonalActivity:Activity() {
// 此类中含有部分的关键状态变量
lateinit var name:String
var isSelf:Boolean = false
...
// 而后须要进行现场状态保护。存储关键数据:
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
outState.putString("name", name)
outState.putBoolean("isSelf", isSelf)
}
// 页面待恢复时,将数据读取出来进行恢复
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
if (saveInstanceState == null) return
name = saveInstanceState.getString("name")
isSelf = saveInstanceState.getBoolean("isSelf")
}
}
复制代码
这只是两个变量须要保存。若是数据量较多的环境下。这块就得把人写疯。。。
而EasyBundle
的双向数据注入功能,在此处就能获得很是良好的表现:
class PersonalActivity:Activity() {
// 此类中含有部分的关键状态变量
@BundleField
lateinit var name:String
@BundleField
var isSelf:Boolean = false
...
// 而后须要进行现场状态保护。存储关键数据:
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
EasyBundle.toBundle(this, outState)
}
// 页面待恢复时,将数据读取出来进行恢复
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
EasyBundle.toEntity(this, savedInstanceState)
}
}
复制代码
固然,推荐的作法仍是将此配置到基类
. 使上层的代码更加简洁:
class BaseActivity:Activity() {
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
EasyBundle.toBundle(this, outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState)
EasyBundle.toEntity(this, savedInstanceState)
}
}
复制代码
固然,你也能够拓展到任意你须要使用到的地方。
上面说了,EasyBundle
支持了基本类型
的兼容逻辑。此兼容逻辑,主要其实就是用来出来路由参数传递的问题
好比咱们有如下一个路由跳转连接:
val url = "Haoge://page/user?name=Haoge&age=18"
复制代码
从连接能够看出来,其实咱们须要传递的参数有两个:String
类型的name
, Int
类型的age
可是路由框架可没此目测功能,因此基原本说。解析后放入intent中传递的数据,都是String
类型的name
与age
因此照正常逻辑:咱们在目标页面。对age
的取值。会须要将数据先读取出来再进行一次转码
后方可以使用
class UserActivity:BaseActivity() {
lateinit var name:String
lateinit var age:Int
override fun onCreate(saveInstanceState:Bundle?) {
// 从intent中进行读取
name = intent.getStringExtra("name")
age = intent.getStringExtra("age").toInt()// 须要再进行一次转码
}
}
复制代码
而使用注入功能,则不用考虑那么多,直接怼啊!!!
class UserActivity:BaseActivity() {
@BundleField
lateinit var name:String
@BundleField // 读取时,会进行自动转码
lateinit var age:Int
}
复制代码
@BundleField
var age:Int = 18 // 直接对变量指定默认数据便可
复制代码
由于自动注入操做使用了反射进行操做。因此若是须要对项目进行混淆的。记得添加上如下混淆规则:
-keep class com.haoge.easyandroid.easy.BundleField
-keepclasseswithmembernames class * {
@com.haoge.easyandroid.easy.BundleField <fields>;
}
复制代码
更多使用场景。期待你的发掘~~~