|
| 1 | +# EasyBundle |
| 2 | + |
| 3 | +EasyBundle顾名思义,主要是对Bundle的封装。作用在于使Bundle的存取操作变得`灵活、方便、简洁` |
| 4 | + |
| 5 | +[Sample Activity](../app/src/main/java/com/haoge/sample/easyandroid/activities/EasyBundleActivity.kt) |
| 6 | + |
| 7 | +## 特性 |
| 8 | + |
| 9 | +1. 简化Bundle数据存取api: |
| 10 | +> 不需要再根据待存取的数据类型。进行`putXXX/getXXX`的方法选择了。统一为`put/get` |
| 11 | +2. 打破Bundle数据格式限制。支持对非可序列化对象进行存取。 |
| 12 | +> 非可序列化对象将会转换为json后进行存储 |
| 13 | +3. 支持注入操作。在进行页面跳转传值时。将会非常好用。 |
| 14 | + |
| 15 | +## 用法 |
| 16 | + |
| 17 | +### 创建EasyBundle实例 |
| 18 | + |
| 19 | +``` |
| 20 | +// 传入具体的Bundle实例进行处理。当传入为null时,将默认创建一个空的Bundle实例提供使用 |
| 21 | +val easyBundle = EasyBundle.create(bundle) |
| 22 | +
|
| 23 | +... // 具体操作区 |
| 24 | +
|
| 25 | +// 操作完成后,获取操作后的bundle实例进行使用 |
| 26 | +val bundle = easyBundle.bundle |
| 27 | +``` |
| 28 | + |
| 29 | +### 统一存取api |
| 30 | + |
| 31 | +EasyBundle简化了存取api。 **不用像原生Bundle一样,需要根据指定数据类型选择使用不同的api进行使用:** |
| 32 | + |
| 33 | +以存取`String、Parcelable、Serializable`实例为例: |
| 34 | + |
| 35 | +``` |
| 36 | +val string = "Hello world" |
| 37 | +val parcelable = ParcelableSubclass() |
| 38 | +val serializable = SerializableSubclass() |
| 39 | +``` |
| 40 | + |
| 41 | +#### 统一存储 |
| 42 | + |
| 43 | +EasyBundle提供了三种重载方法进行数据存储 |
| 44 | + |
| 45 | +1. 统一使用put方法进行存储, 且支持链式调用 |
| 46 | + |
| 47 | +``` |
| 48 | +easybundle.put(key1, string) |
| 49 | + .put(key2, parcelable) |
| 50 | + .put(key3, serializable) |
| 51 | +``` |
| 52 | + |
| 53 | +2. 或者,你也可以使用提供的带可变参数的方法进行多数据存储 |
| 54 | + |
| 55 | +``` |
| 56 | +easyBundle.put(key1 to String, |
| 57 | + key2 to parcelable, |
| 58 | + key3 to serializable) |
| 59 | +``` |
| 60 | + |
| 61 | +3. 当然,你也可以传入一个存在的map实例 |
| 62 | + |
| 63 | +``` |
| 64 | +easyBundle.put(mapOf<String, Any>( |
| 65 | + key1 to string, |
| 66 | + key2 to parcelable, |
| 67 | + key3 to serializable)) |
| 68 | +``` |
| 69 | + |
| 70 | +#### 统一读取 |
| 71 | + |
| 72 | +1. 通过内联函数指定`数据泛型`进行读取 |
| 73 | + |
| 74 | +``` |
| 75 | +val string = easyBundle.get<String>(key1) |
| 76 | +val parcelable = easyBundle.get<ParcelableSubclass>(key2) |
| 77 | +val serializable = easyBundle.get<SerializableSubclass>(key3) |
| 78 | +``` |
| 79 | + |
| 80 | +2. 或者。直接通过`指定class`进行读取 |
| 81 | + |
| 82 | +``` |
| 83 | +val String = easyBundle.get(key1, String::class.java) |
| 84 | +val parcelable = easyBundle.get(key2, ParcelableSubclass::class.java) |
| 85 | +val serializable = easyBundle.get(key3, SerializableSubclass::class.java) |
| 86 | +``` |
| 87 | + |
| 88 | +### 打破bundle数据存储限制 |
| 89 | + |
| 90 | +EasyBundle破除了存储的入口限制,所以也理所应当的,**破除了Bundle的数据存储限制** |
| 91 | + |
| 92 | +意思即是:**EasyBundle允许你向Bundle内部存储任意的数据类型实例** |
| 93 | + |
| 94 | +以下方的`非可序列化类`为例: |
| 95 | + |
| 96 | +``` |
| 97 | +class Info { |
| 98 | + val name:String? = "Info's name" |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +- 进行存储: |
| 103 | + |
| 104 | +``` |
| 105 | +val info = Info() |
| 106 | +easyBundle.put("info", info) |
| 107 | +``` |
| 108 | + |
| 109 | +- 进行读取 |
| 110 | + |
| 111 | +``` |
| 112 | +val info = easyBundle.get<Info>("info") |
| 113 | +``` |
| 114 | + |
| 115 | +可以看到:Info本身并未实现序列化接口。但是也是可以通过EasyBundle直接进行存取操作的。 |
| 116 | + |
| 117 | +这是因为`EasyBundle`采用的是`JSON`作为数据中转格式: |
| 118 | +- 在进行存储时:将不能被Bundle直接存储的`(非可序列化对象)`转为`JSON`数据,再进行存储 |
| 119 | +- 在进行读取时:取出的数据与实际要求的类型不匹配。通过'JSON'数据作为中转,并解析出要求的数据对象返回 |
| 120 | + |
| 121 | +我们来通过部分`核心代码`来进行说明: |
| 122 | + |
| 123 | +- 存储时: |
| 124 | + |
| 125 | +``` |
| 126 | +fun put(name:String, value:Any?):EasyBundle { |
| 127 | + when (value) { |
| 128 | + // 对于bundle支持的数据格式,直接使用对应的api进行存储 |
| 129 | + is Int -> bundle.putInt(name, value) |
| 130 | + ... |
| 131 | + // 对于不满足条件的,进行json转码后再进行存放 |
| 132 | + else -> bundle.putString(name, toJSON(value)) |
| 133 | + } |
| 134 | +
|
| 135 | + return this |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +- 读取时: |
| 140 | + |
| 141 | +``` |
| 142 | +fun <T> get(key:String, type:Class<T>):T? { |
| 143 | + var value = bundle.get(key) ?: return returnsValue(null, type) as T? |
| 144 | + // 当取出数据类型与指定类型匹配时。直接返回 |
| 145 | + if (type.isInstance(value)) { |
| 146 | + return value as T |
| 147 | + } |
| 148 | +
|
| 149 | + if (value !is String) { |
| 150 | + // 不匹配类型,使用json作为数据中转站 |
| 151 | + value = toJSON(value) |
| 152 | + } |
| 153 | + value = value as String |
| 154 | +
|
| 155 | + // 处理两种情况下的数据自动转换: |
| 156 | + val result = when(type.canonicalName) { |
| 157 | + // 兼容基本数据类型 |
| 158 | + "byte", "java.lang.Byte" -> value.toByte() |
| 159 | + ... |
| 160 | + // 对不匹配类型数据。使用json进行反序列化解析。 |
| 161 | + else -> parseJSON(value, type) |
| 162 | + } |
| 163 | + return result as T |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +源码很简单。相信很容易看懂。 |
| 168 | + |
| 169 | +而在读取时,也对基本数据类型做判断兼容的好处是:可以做到很好的兼容市面上的路由框架。 |
| 170 | + |
| 171 | +#### 路由传参的兼容方案 |
| 172 | + |
| 173 | +我们都知道。路由的传参,有相当一部分的数据是通过`url`自带的`params`进行的数据传递。 |
| 174 | +而这些`params`解析后放入intent的数据。基本上都是`String`类型。所以在数据接收页, |
| 175 | +普遍的还会需要自己去进行数据解析,这样很容易导致可维护性降低。 |
| 176 | + |
| 177 | +所以EasyBundle自带的读取时解析数据。在这种场景下就能得到很好的应用: |
| 178 | + |
| 179 | +以下方所示的链接为例: |
| 180 | + |
| 181 | +``` |
| 182 | +val uri = Uri.parse("haoge://page/example").buildUpon() |
| 183 | + .appendQueryParameter("int", "12") |
| 184 | + .appendQueryParameter("user", JSON.toJSONString(User("Haoge"))) |
| 185 | + .appendQueryParameter("name", "Haoge") |
| 186 | + .build() |
| 187 | +``` |
| 188 | + |
| 189 | +为了便于展示说明。这里采用builder的方式进行了url的创建。传递一个`基本数据类型`一个`JSON`数据, |
| 190 | + |
| 191 | +所以。在解析url时,这部分的参数将会被解析后存入intent中进行传递: |
| 192 | + |
| 193 | +``` |
| 194 | +val intent = getIntent() |
| 195 | +intent.putExtra("int", uri.getQueryParameter("int")) |
| 196 | +intent.putExtra("user", uri.getQueryParameter("user")) |
| 197 | +intent.putExtra("name", uri.getQueryParameter("name")) |
| 198 | +``` |
| 199 | + |
| 200 | +然后在参数接收页。按照常规做法。我们应该是要先自己从intent中读取数据。然后自己转换成对应数据后再进行使用: |
| 201 | + |
| 202 | +但是使用EasyBundle即可以不用那么麻烦: |
| 203 | + |
| 204 | +``` |
| 205 | +val easyBundle = EasyBundle.create(intent.extras) |
| 206 | +val int = easyBundle.get<Int>("int") |
| 207 | +val user = easyBundle.get<User>("user") |
| 208 | +val name = easyBundle.get<String>("name") |
| 209 | +``` |
| 210 | + |
| 211 | +### 使用BundleField做自动数据注入 |
| 212 | + |
| 213 | +`EasyBundle`提供`BundleField`注解作自动数据注入 |
| 214 | + |
| 215 | +类似于ButterKnife。EasyBundle可以很方便的,从`bundle`容器中,将数据自动注入到实体类中的`对应成员变量`中去。 |
| 216 | + |
| 217 | +最经典的用法是进行页面传参时进行使用:仍以上方路由传参的几个参数作为说明: |
| 218 | + |
| 219 | +``` |
| 220 | +class InjectorActivity:Activity() { |
| 221 | + // 配置可注入的参数. 添加BundleField注解即可 |
| 222 | + @BundleField("name") |
| 223 | + var name:String? = null |
| 224 | + @BundleField("int") |
| 225 | + var int:Int = 0 |
| 226 | + @BundleField("user") |
| 227 | + var user:User? = null |
| 228 | +
|
| 229 | + override fun onCreate(saveInstanceState:Bundle?) { |
| 230 | + super.onCreate(saveInstanceState) |
| 231 | + // 执行注入操作 |
| 232 | + EasyBundle.toEntity(this, intent?.extras) |
| 233 | + } |
| 234 | +} |
| 235 | +``` |
| 236 | + |
| 237 | +这样的操作可以大大的提高代码的可读性。不用再去自己单独手动进行读取了。 |
| 238 | + |
| 239 | +当然,每个类都去手动调用`toEntity`方法,也是很蛋疼的。所以`EasyBundle`也支持将注入api入口配置到基类中去。 |
| 240 | + |
| 241 | +而且,结合`EasyBundle`的另一个`反向注入`api:`toBundle`。能够达到很方便的`现场数据保存`的效果 |
| 242 | + |
| 243 | +``` |
| 244 | +abstract class BaseActivity:Activity() { |
| 245 | +
|
| 246 | + final override fun onCreate(savedInstanceState: Bundle?) { |
| 247 | + super.onCreate(savedInstanceState) |
| 248 | + // 自动触发注入操作 |
| 249 | + EasyBundle.toEntity(this, intent?.extras) |
| 250 | + } |
| 251 | +
|
| 252 | + // ==== 自动进行现场保护. 可选配置 |
| 253 | + override fun onSaveInstanceState(outState: Bundle?) { |
| 254 | + super.onSaveInstanceState(outState) |
| 255 | + // 将当前类中的被BundleField注解的变量。注入到outState中进行保存 |
| 256 | + EasyBundle.toBundle(this, outState) |
| 257 | + } |
| 258 | +
|
| 259 | + override fun onRestoreInstanceState(savedInstanceState: Bundle?) { |
| 260 | + super.onRestoreInstanceState(savedInstanceState) |
| 261 | + // 将savedInstanceState中的数据注入到当前类中被BundleField注解的成员变量中 |
| 262 | + EasyBundle.toEntity(this, savedInstanceState) |
| 263 | + } |
| 264 | +} |
| 265 | +``` |
| 266 | + |
| 267 | +#### BundleField参数说明 |
| 268 | + |
| 269 | +BundleField提供两个参数: |
| 270 | + |
| 271 | +``` |
| 272 | +annotation class BundleField(val value:String = "", val throwable:Boolean = true) |
| 273 | +``` |
| 274 | + |
| 275 | +**1. value**: 参数的key值,当为空时,代表使用成员变量的变量名作为key值使用 |
| 276 | +**2. throwable**: 在进行数据注入时,当出现异常时,是否允许抛出异常。 |
| 277 | + |
| 278 | +#### 指定参数默认值 |
| 279 | + |
| 280 | +很多时候我们会需要为某个参数指定默认值。可以通过`直接为变量配置默认值`的方式进行配置: |
| 281 | + |
| 282 | +``` |
| 283 | +@BundleField |
| 284 | +var name:String = "this is default name" |
| 285 | +``` |
0 commit comments