EasyAndroid basic integrated component library: EasyBundle best Bundle access practice

EasyAndroid basic integrated component library: EasyBundle best Bundle access practice

What is EasyBundle

EasyBundle is one of the basic components in the open source basic component integration library EasyAndroid . Its role is: to elegantly access Bundle data

EasyAndroid is an integrated component library. The integrated components in this library all contain the following features, so you can use it with confidence~~

  1. Streamlined : As an integrated library, I don't want to have that kind of large components, and try to control the size of the integrated library as much as possible. No redundant code
  2. Cohesion : Minimize or even avoid a single component's dependence on other modules. Achieve independence between components.

Thanks to the coding , if you only need to use EasyBundle. Then you can directly copy the EasyBundle source code file to your project and use it directly, it is no problem.

characteristic

  1. Unified access api
  2. Supports storage of any type of data, breaking the Bundle data limit
  3. Automatic type conversion. Read as you like
  4. Two-way data injection between Bundle and entity class

usage

Usage overview

Let's take a look at how to use it first . So that everyone can EasyBundlehave a general idea of the usage

Suppose we have the following batch of data, which needs to be stored

Types of value
Int age
String name
  • Native storage : Different apis need to be selected according to different storage types
val bundle = getBundle()
bundle.putInt("age", age)
bundle.putString("name", name)
 
  • Use EasyBundle for storage : unified storage api. Direct storage
val bundle:Bundle = EasyBundle.create(getBundle())
	.put("age", age)
	.put("name", name)
	.getBundle()
 
  • Native reading : you need to choose apito read according to the container
val bundle = getBundle()
val age:Int = bundle.getInt("age")
val name:String = bundle.getString("name")
 
  • Use EasyBundle to read : unified read api. Read directly
val easyBundle = EasyBundle.create(getBundle())
val age = easyBundle.get<Int>("age")
val name = easyBundle.get<String>("name")
 
  • Native page value
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")
		}
	}
}
 
  • Use EasyBundle for page value
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 = ""
	...
}
 
  • Native method for field protection
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")
		}
	}
}
 
  • Use EasyBundle for field protection configuration
// 
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)
	}
}
 

The above is the main usage of EasyBundle. I hope everyone can get a general understanding of the main functions of EasyBundle.

EasyBundle instance creation instructions

EasyBundle encapsulates the access operation of Bundle, then we will definitely need to bind a Bundle to operate accordingly

val easyBundle:EasyBundle = EasyBundle.create(bundle)
 

Then, after the data is manipulated through easyBundle, the bundle data after the operation is taken out for use:

val bundle:Bundle = easyBundle.bundle
 

If the bundle passed during creation is null. Will create a new one bundle for data storage

fun create(source:Bundle? = null): EasyBundle {
    return EasyBundle(source?: Bundle())
}
 

and so. Let's go back and look at the storage example code above, it is very clear:

val bundle:Bundle = EasyBundle.create(getBundle())
	.put("age", age)
	.put("name", name)
	.getBundle()
 

Unified access api

From the above example, we can see that: Compared with the native method (which requires data access for use api), EasyBundlethe access api is unified:

3.ways of unified storage

  1. Use the put(key:String, value:Any)method directly to store one by one:
easyBundle.put(key1, value1)
	.put(key2, value2)// 
 
  1. put(vararg params:Pair<String, Any>)Simultaneous storage of multiple data through the provided method with variable parameters
easyBundle.put(
	key1 to value1,
	key2 to value2
	...
)
 
  1. Directly store the map data passed by othersput(params:Map<String, Any>)
val map:Map<String, Any> = getMap()
easyBundle.put(map)
 

Unified read

Unified data storage entry. Of course, EasyBundlethe data reading entry is also unified:

When you need to read. The get<T>(key:String)specified data can be read through inline functions .

For example, read the Parcelable Userinstance:

val user = easyBundle.get<User>("user")
 

And in the java environment. Because there is no inline function available, you can also use the get(key:String, type:Class<*>)method to read

User user = easyBundle.get("user", User.class)
 

Break the Bundle storage data limit

As we all know, the Bundle's access api is so complicated, it mainly needs to be filtered out .

So often. Sometimes when we are developing, we suddenly need to pass one to the next page. At this time, you will need to serialize and modify this class.

Although it is actually very simple to implement the serialization interface for the class. But it often needs to be realized, which is also annoying.

The solution is actually very simple, refer to the classic network communication model: use JSON as the transit type for communication

Take the following User as an example:

class User() {
	val name:String? = null
}
 

Make storage

easyBundle.put("user", user)
 

When stored for user automatically find this type bundle , the user will be by fastjsonor gsonbe json carried out after the storage.

Core source code display

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))
	}
}
 

Read

val user:User = easyBundle.get<User>("user")
 

When reading, it is taken out of the bundle json . UserDoes not match the specified type . Then it will pass fastjsonor gsonproceed json later. Go back again:

In addition to the JSON schemes illustrated here . The other is :

For example, a string of numbers is placed in the current bundle:

easyBundle.put("number", "10086")
 

Although we store data in String type. But the content can actually be converted to int. Then we can also intread directly :

val number:Int = easyBundle.get<Int>("number")
 

The way. Use it under items that use routing. perfectly worked:

Because in the routing framework, most of the parameter part of the url is directly parsed and passed in the format of String

Core source code display:

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
}
 

Description of the json transfer data in EasyBundle

In EasyBundle. There is no direct dependency fastjsonand gsonparsing library. But by doing it at runtime json . Use what is supported by the current operating environment 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")
}
 

Therefore, there is no need to worry about introducing new unneeded libraries. Moreover, I believe that most of the projects are certainly fastjsonwith gsonat least one parsing library.

Two-way data injection

EasyBundleProvide BundleFieldcomments. Used to provide functionality.

Bi-injection means: either data can be injected into bundle , or it can be bundle injected into :

For example, this is an ordinary bean class that stores user information:

class User(var name:String, var arg:Int, var address:String)
 

then. In normal mode. When we need to store these data in the bundle:

val user = getUser()
bundle.putString("name", user.name)
bundle.putInt("age", user.age)
bundle.putString("address", user.address)
 

Or, you need to fetch the corresponding data from the bundle and assign it to the user:

user.name = bundle.getString("name")
user.age = bundle.getInt("age")
user.address = bundle.getString("address")
 

However, if you use the EasyBundleprovided function, it's very simple:

1. For the fields that need to be injected. Add a comment:

class User(@BundleField var name:String, 
	@BundleField var arg:Int, 
	@BundleField var address:String)
 

2. Inject the data from the User into the bundle for saving

EasyBundle.toBundle(user, bundle)
 

3. Read the data from the bundle and inject it into the User instance:

EasyBundle.toEntity(user, bundle)
 

The effect is consistent with the original writing above. And .

Re-specify the key value

Generally speaking. When used directly @BundleField. The key value used by default is .

But sometimes, we will need to reset the key value:

class Entity(@BundleField("reset_name") var name:String)
 

Anti-crash switch

In the process of data access, it is difficult to avoid access exceptions. For example. You saved it "Hello,World", but when you got it, you got it Int. Or save it as json. But when reading, json parsing error. These situations will cause unexpected exceptions to be thrown

So BundleFieldthe throwableparameters are provided:

@BundleField(throwable = false)
var user:User
 

throwableThe type is Boolean. Represents when an exception occurs during access. Whether to throw this exception upward. (Default is false)

Use scenarios for data injection

Although the above is a long section, if there is no support for specific usage scenarios. Some friends may not understand: You have said so much, but what is the use of eggs?

Below I will give some examples of usage scenarios. Make some specific instructions:

1. Page jump Intent pass value

This can actually be said to be the main usage scenario. Use it in Activity to get the data passed at startup:

class UserActivity:Activity() {
	@BundleField
	lateinit var name:String
	@BundleField
	lateinit var uid:String
	
	override fun onCreate(saveInstanceState:Bundle?) {
		// intent 
		EasyBundle.toEntity(this, intent?.extras)
	}
}	
 

of course. In fact, there is a new page every time. It EasyBundle.toEntityhurts to write it all once

In fact, the injection method can be put into the base class. Do it

class BaseActivity:Activity() {
	override fun onCreate(saveInstanceState:Bundle?) {
		// intent 
		EasyBundle.toEntity(this, intent?.extras)
		...
	}
}
 

and. Using this method has a very significant advantage: for example, for the UserActivitypage shown above . The data required for this page is the namesame uid, at a glance~

2. On-site status protection

According to the original way. When we are doing on-site protection, we will need to go one by one saveInstanceState, and when we need to restore data, we need to go one by one. .

For example, like the following page:

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")
	}
}
 

These are just two variables that need to be saved. If there is a lot of data in an environment. This piece has to write people crazy. . .

And EasyBundlethe two-way data injection function can get very good performance here:

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)
	}
}
 

Of course, the recommended approach is to make the upper code more concise:

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)
    }
}
 

Of course, you can also expand to any place you need to use it.

3. Compatible with routing jump parameter transfer

As mentioned above, the compatible logic is EasyBundlesupported . This compatible logic is mainly used to issue routing parameters transmission

For example, we have the following route jump link:

val url = "Haoge://page/user?name=Haoge&age=18"
 

As can be seen from the link, there are actually two parameters we need to pass: Stringtype nameand Inttypeage

But the routing framework does not have this visual inspection function, so basically. The data passed in the intent after parsing is of Stringtype nameandage

So follow the normal logic: we are on the target page. The right agevalue. You will need to read out the data before you can use it

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()// 
	}
}
 

When using the injection function, you don't have to think about it so much, it's straightforward! ! !

class UserActivity:BaseActivity() {
	@BundleField
	lateinit var name:String
	@BundleField// 
	lateinit var age:Int
}
 

4. Specify default values

@BundleField
var age:Int = 18// 
 

Obfuscated configuration

Because the automatic injection operation uses reflection to operate. So if you need to confuse the project. Remember to add the following obfuscation rules:

-keep class com.haoge.easyandroid.easy.BundleField
-keepclasseswithmembernames class * {
    @com.haoge.easyandroid.easy.BundleField <fields>;
}
 

More usage scenarios. Looking forward to your discovery~~~