EasyBundle是开源基础组件集成库 EasyAndroid 中的基础组件之一。其作用是: 优雅的进行Bundle数据存取
EasyAndroid 作为一款集成组件库,此库中所集成的组件,均包含以下特点,你可以放心使用~~
得益于编码时的 高内聚性
,若你只需要使用EasyBundle. 那么可以直接去copy EasyBundle源码文件
到你的项目中,直接进行使用,也是没问题的。
既然是对Bundle的存取操作进行封装的,那么肯定我们会需要绑定一个Bundle对应进行操作
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()) }
这一条也是当初写这个组件的主要原因。只要是个Android开发都知道,这特么原生bundle操作简直不要太蛋疼。我存个 String
。需要用 putString
; 我存个 int
,需要用 putInt
, 我存个 parcelable
,需要用 putParcelable
。虽然能理解系统这样设计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 ... )
当然。你甚至可以直接存储别人传过来的map数据 put(params:Map<String, Any>)
easyBundle.put(mapof<String, Any>(){ key1 to value1, key2 to value2 ... })
统一了数据的存储入口。理所当然的, EasyBundle
也统一了数据的读取入口:
需要进行读取时。可以通过 get(key:String, type:Class<*>)
读取指定数据.
比如读取 实现了Parcelable接口的User
实例:
val user = easyBundle.get("user", User.class)
如果你在kotlin环境下使用,也可以通过内联函数 指定数据泛型
进行读取:
val user = easyBundle.get<User>("user")
都知道,Bundle的存取api那么复杂,主要是需要过滤掉 不被系统允许的非序列化数据
。
所以经常性的。有时候我们在开发中,突然会需要将一个 普通的实体类
传递到下一个页面。这个时候就会需要对这个类进行序列化修改。
虽然实际上对类进行实现序列化接口还是很简单的。但是经常需要去实现,也是让人神烦的。
解决办法其实很简单,参考经典的网络通信模型即可: 使用JSON作为中转类型进行通信
以下方的User为例:
class User() { val name:String? = null }
easyBundle.put("user", user)
存储时,自动对user进行 类型检查
,发现此类型 不被bundle所支持存储
,所以会将user通过 fastjson
或者 gson
进行 json序列化转码
后,再进行存储。
val user:User = easyBundle.get<User>("user")
读取时,从bundle中取出的是 json字串
。与指定类型 User
不匹配。则将通过 fastjson
或者 gson
进行 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
至少其中一种解析库。
在进行读取时。不止有对 json中转数据
的兼容逻辑。也有 基本类型
的兼容逻辑:
比如当前bundle中放入了数字的字符串:
easyBundle.put("number", "10086")
虽然我们存入的时候是String类型数据。但是内容实际上是可以转为int的。那么我们也可以直接 指定接受者类型为int
来进行读取:
val number:Int = easyBundle.get<Int>("number")
基本类型兼容
的方式。在使用路由的项目下进行使用。非常好用:
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
提供的 双向数据注入
功能,就很简单了:
class User(@BundleField var name:String, @BundleField var arg:Int, @BundleField var address:String)
EasyBundle.toBundle(user, bundle)
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
的取值。会需要将数据先读取出来再 进行一次转码
后方可使用
@RouteRule("Haoge://page/user") 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()// 需要再进行一次转码 } }
而使用注入功能,则不用考虑那么多,直接怼啊!!!
@RouteRule("Haoge://page/user") class UserActivity:BaseActivity() { @BundleField lateinit var name:String @BundleField // 读取时,会进行自动转码 lateinit var age:Int }