项目背景
在app开发中经常遇到的一个问题是Activity的重用。对于很多页面布局相同,但是业务逻辑上有少量不同的页面。我们希望能够重复使用一个Activity来减少工作量。一种实现方式是,通过Intent传一个值给Activity,并在Activity中通过switch语句判断该运行哪个具体的业务逻辑。如下:
val intent = Intent(context, DemoActivity::java.class)
intent.putExtra(KEY_MODEL, MODEL_A)
intent.putExtra(KEY_PARAMS_A, "abc")
startActivity(intent)
val intent = Intent(context, DemoActivity::java.class)
intent.putExtra(KEY_MODEL, MODEL_B)
intent.putExtra(KEY_PARAMS_B0, "hello word")
intent.putExtra(KEY_PARAMS_B1, 10)
startActivity(intent)
class DemoActivity : AppCompactActivity {
onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
val model = intent.getIntExtra(KEY_MODEL)
when(model) {
MODEL_A->{
val paramsA = intent.getStringExtra(KEY_PARAMS_A)
}//执行A逻辑
MODEL_B->{
val paramsB0 = intent.getStringExtra(KEY_PARAMS_B0)
val paramsB1 = intent.getIntExtra(KEY_PARAMS_B1, -1)
}//执行B逻辑
MODEL_C->{}//执行C逻辑
}
}
}
上述方法在逻辑不复杂时,实现方便快捷。但是该方法存在如下问题:
1)违反开闭原则:目前项目中存在A、B、C三种业务逻辑,如果后续需求迭代,需要添加新的业务逻辑D。则需要对DemoActivity页面进行修改才能满足要求
2)每个业务逻辑都有可能有自己特定的输入参数,上述实现思路在调用时,需要在调用时根据不同的业务逻辑传递不同的参数,稍不谨慎可能会造成参数漏传
3)多种业务逻辑的代码存在于同一个页面中,给后续代码维护带来相当大的麻烦,特别是在多种复杂业务逻辑的情况下。
还有一种思路,是在onActivityResult方法中返回DemoActivity的通用结果,然后在onActivityResult中处理。这样实现仍然存在局限性。假设一个项目中有多个地方需要调用该DemoActivity,并执行A逻辑。那么就需要在多处实现onActivityResult并重复对A逻辑代码块的调用。给代码维护带来不便。
设计思路
解决多个业务逻辑分离的问题,我们很容易联想到策略模式。为每一种业务逻辑定义一个策略类,分别实现各个业务逻辑。又由于是Android开发,将一个自定义的策略类通过Intent发送给定义好的Activity,我们可以将策略类继承自Parcelable。
撸代码
策略类基类
@UiThread
class BaseAction() : Parcelable {
open fun onCreate(activity : ActionActivity) {}
open fun run(activity : ActionActivity, data : Any?)
open fun onDestroy(activity : ActionActivity) {}
constructor(parcel : Parcel)
override fun writeToParcel(parcel: Parcel, flags: Int) {}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<BaseAction> {
override fun createFromParcel(parcel: Parcel): BaseAction{
return BaseAction(parcel)
}
override fun newArray(size: Int): Array<BaseAction?> {
return arrayOfNulls(size)
}
}
}
onCreate、onDestroy方法分别于Activity的onDestroy方法和onDestroy方法绑定。使得Action可以获取Activity的生命周期。当然也可以实现onStart、onStop、onPause、onResum等Activity生命周期方法的绑定
run方法的data参数由Activity传递进来。根据具体的情况可以定义成需要的数据类型,这里定义为Any?
Activity实现
class ActionActivity : AppCompatActivity {
private var action : BaseAction?
onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
...
action = intent.getParcelableExtra(KEY_ACTION)
action?.onCreate(this)
}
onStart() {
super.onStart()
doAction()
}
onDestroy() {
super.onDestroy()
action?.onDestroy()
}
private fun doAction() {
action?.run(this, null)
}
}
doAction方法根据具体的情况触发,例如用户点击某个按钮,或者其他操作后执行。这里为了说明方便,在Activity#onStart方法中触发
具体业务A的Action
class AModelAction(val value : Int) : BaseAction() {
override fun run(activity : ActionActivity, data : Any?) {
Log.i("action", "run in action A, value= $value")
}
constructor(parcel : Parcel) : this(parcel.readInt())
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeInt(this.value)
}
companion object CREATOR : Parcelable.Creator<AModelAction> {
override fun createFromParcel(parcel: Parcel): AModelAction{
return AModelAction(parcel)
}
override fun newArray(size: Int): Array<AModelAction?> {
return arrayOfNulls(size)
}
}
}
这里AModelAction有一个参数value,在实现parcelable时,需要实现该参数的读写过程。在run方法种调用打印一行log。注意CRATOR对象的方法一定要指定具体的Action类型,否则intent.getParcelableExtra方法返回的类型会与intent.putParcelableExtra的不一致
同样可以实现BModelAction
class BModelAction(val value0 : String, val value1 : Int) : BaseAction() {
override fun run(activity : ActionActivity, data : Any?) {
Log.i("action", "run in action B, value= $value0, $value1")
}
constructor(parcel : Parcel) : this(parcel.readString(), parcel.readInt())
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeString(this.value0)
parcel.writeInt(this.value1)
}
companion object CREATOR : Parcelable.Creator<BModelAction> {
override fun createFromParcel(parcel: Parcel): BModelAction{
return BModelAction(parcel)
}
override fun newArray(size: Int): Array<BModelAction?> {
return arrayOfNulls(size)
}
}
}
在BModelAction中传递一个String参数和一个Int参数。同样可以定义更多的BaseAction子类
启动ActionActivity并执行AModelAction
val intent(context, ActionActivity::java.class)
intent.putExtra(KEY_ACTION, AModelAction(10))
startActivity(intent)
可以看大日志输出:
run in action A, value= 10
启动ActionActivity并执行BModelAction
val intent(context, ActionActivity::java.class)
intent.putExtra(KEY_ACTION, BModelAction("hello word", 12))
startActivity(intent)
调用后的日志输出:
run in action B, value= hello word, 12
可以看出,该实现方案,在启动页面和指定具体业务逻辑时非常的清晰。并且不同业务逻辑都分开定义在具体的策略类中。现在在Activity内仅提供一个触发条件的控制。我们也可以在Action的onCreate方法中修改触发条件,实现更加灵活的业务场景,例如:
class CModelAction() : BaseAction() {
override fun onCreate(activity : ActionActivity) {
activity.findViewById<Button>(R.id.btDoAction).setOnClickListener{
activity.doAction()
}
}
override fun run(activity : ActionActivity, data : Any?) {
Log.i("action", "run in action C= $data")
}
constructor(parcel : Parcel) : this(parcel.readString())
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeString(this.value)
}
companion object CREATOR : Parcelable.Creator<CModelAction> {
override fun createFromParcel(parcel: Parcel): CModelAction{
return CModelAction(parcel)
}
override fun newArray(size: Int): Array<CModelAction?> {
return arrayOfNulls(size)
}
}
}
策略C添加了一个通过点击按钮的触发条件。
我们在项目开发中,总是在不断的提炼自己的代码,使得代码越来越精简和可靠。针对上述的问题,小伙伴们是否还能够给出更加理想的解决方案呢,欢迎留言。