声明:本文也在我的微信公众号 Android程序员(AndroidTrending) 发布。
原文链接:Better Android Intents with Dart & Henson
原文作者:Daniel Molinero Reguera
译文出自:汤涛的简书
译者:汤涛
状态:完成
Buster Keaton — The Battling Butler.jpg最近看到这篇文章,感觉不错,就翻译了一下。文中提到的 Android Intent 的种种问题,有些也是我之前遇到的一些痛点,项目规模稍大一些后,有些问题会慢慢暴露出来,虽不是非常严重,但正是对代码的精益求精,才是我们不断进步的源泉,也是我推荐文章的重要标准。作者来自著名的团购鼻祖Groupon公司,相信这篇分享值得大家一看。
Intent 是 Android 生态系统的重要组成部分。他们用来表达一个执行动作,可分为隐式和显式 Intent。在应用程序内部,所有的 Intent 以一种抽象的方式,一起定义了一个信息传递层。在本文中,我们将解释为什么 Android 创建显式 Intent 的方式容易出错,也给大家展示一些有问题的应对方案。最后,我们将介绍一个生成这种信息传递层的库:Dart & Henson,它使用简单,能方便、快捷与健壮地在你的 Activity 和 Service 之间传递信息。
显示 Intent 需要明确指定组件,常用于在应用内的 Activity 或 Intent 之间传递信息,额外的信息通过 extras 提供给目标组件,与 Intent 一起传递。比如下面的代码,创建了一个显示 Intent 来启动 Activity:
Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra(EXTRA_ITEM_ID, selectedItem.id);
intent.putExtra(EXTRA_SHOW_MAP, true);
startActivity(intent);
被启动的 Activity 代码可能是这样的:
public class DetailActivity extends Activity {
public static final String EXTRA_ITEM_ID = "extra.item_id";
public static final String EXTRA_SHOW_MAP = "extra.show_map";
private String itemId;
private boolean shouldShowMap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
itemId = getIntent().getStringExtra(EXTRA_ITEM_ID);
shouldShowMap = getIntent().getBooleanExtra(EXTRA_SHOW_MAP, false);
if (itemId == null) {
throw new IllegalArgumentException("Item Id is required");
}
...
}
...
}
这种机制很好地处理了组件的创建与通信,但仍然有一些问题需要注意:
- 目标组件作为一个实体,对输入没有任何控制权。在我们的例子里,itemId 是必需的,但如果没有传递它,DetailActivity 最好的处理方式也只能是抛出异常。
- Intent 的创建(完全)不够健壮,并没有对 extra 中的 key 或 value 做任何检查。
一分预防胜过十分治疗
有问题的解决方案
解决这些问题的一个可能的方案是 Intent 工厂模式。它主要由一些工厂方法组成,包含了应用程序里用到的各种 Intent。比如像下面这样的 Intent 工厂:
public class IntentFactory {
public Intent newDetailActivityIntent(Context context, String itemId, boolean showMap) {
Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra(EXTRA_ITEM_ID, itemId);
intent.putExtra(EXTRA_SHOW_MAP, showMap);
return intent;
}
...
}
然而,这种解决方案有一些局限,并不是一个很好的办法。
- Intent 工厂是一个集中类,这个类可能会变得很大且复杂。
- 它违背了开放/闭合原则。对修改并没有关闭,我们将总是需要给每个新的 Activity 添加一个新方法。
- 目标组件应该是唯一知晓参数细节与逻辑的地方。
- 可选参数处理。同一个组件有不同的需求,是否应该写不同的方法?还是写一个方法并使用默认值?
- 它会诱使后续的开发人员模仿,进而产生其他的 Intent 工厂,最终演变成大泥球模式,使得代码越来越糟。
有一个类似的策略可以分散这些工厂方法到各自的目标组件。也就是指,每个组件可以包含一个(或多个)静态方法,用于生成这些启动它自身的 Intent。这个办法可以解决开放/闭合原则的问题,分解 Intent 工厂,也许还可以避免大泥球模式。尽管如此,关于可选参数的问题依然存在。有人说 builder 模式可以?我们自己实现它?...
我选择用懒惰的人做困难的工作,因为一个懒惰的人会找到简单的方法完成它。比尔盖茨
Dart 2 & Henson
Dart 是一个 Android 开源库。它绑定 Activity 字段到 Intent extra,Butter Knife 也是用类似的方案,关联 Activity 与 XML 布局中的View。在我们的例子里,它看起来是这样:
public class DetailActivity extends Activity {
@InjectExtra String itemId;
@Nullable @InjectExtra boolean shouldShowMap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Dart.inject(this);
...
}
...
}
@InjectExtra 注解声明了一个同名的 extra key,默认情况下,所有的注解字段都是必需的,如果 extra 没有提供,会抛出异常。如果想使其可选,需要加上 @Nullable 注解。接下来,只需要调用 Dart.inject 即可自动生成相关代码。
在 Groupon,我们意识到注解里的那些信息,已经足够创建我们一直想要的builder模式。因此,我们决定在 Dart 基础上再进一步:我们做了一个注解处理器,用于生成 Intent builders,这个新模块叫做 Henson,它集成在 Dart 2 中。
在 DetailActivity 这个例子里,Henson 生成了一个小型的领域特定语言 (DSL),来使得跳转到 DetailActivity 变得非常容易:
Intent intent = Henson.with(context)
.gotoDetailActivity()
.itemId(selectedItem.id)
.shouldShowMap(true)
.build();
startActivity(intent);
首先是通过 Henson.with(context).gotoXXX() 获取目标 Activity 或 Service 的 builder。然后,使用自动生成的方法设置必需的 extras, 比如 itemId 是使用 itemId(String str)。之后,用同样的方式设置可选参数。最后调用 build,你就可以得到一个有效的 Intent,用于启动你的组件。
这段领域特定语言(DSL)会为所有@InjectExtra 注解标记的字段生成相关类。这相当于一个信息传递层,解决了我们创建 Intent 时碰到的那些问题:
- 通过注解,目标组件对 extras 拥有完全的控制权。
- DSL 定义在组件内的一处,如果它有修改,产生的问题都可以在编译时被发现。
- 没有违反开放/闭合原则,实际上,我们什么都不需要写,一切都是自动生成。
- 因为使用了 builder 模式,可选参数很容易实现。
- 还可以自动补全代码!
完整的示例代码在这里。
总结
Henson 创建了一个小型的领域特定语言(DSL),可以更加健壮地构建启动 Activity 与 Service 的 Intent,它允许缺失必需的extra,支持灵活的可选参数,最棒的是,使用 Dart 2 与 Henson,你一行代码也不必写了。😊
还不赶紧试试?
f2prateek/dart---Extras "injection" Library for Android
网友评论