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>;
}
複製代碼
更多使用場景。期待你的發掘~~~