【译】使用 Dart & Henson 改进 Android Intents

声明:本文也在我的微信公众号 Android程序员(AndroidTrending) 发布。

原文链接:Better Android Intents with Dart & Henson
原文作者:Daniel Molinero Reguera
译文出自:汤涛的简书
译者:汤涛
状态:完成

最近看到这篇文章,感觉不错,就翻译了一下。文中提到的 Android Intent 的种种问题,有些也是我之前遇到的一些痛点,项目规模稍大一些后,有些问题会慢慢暴露出来,虽不是非常严重,但正是对代码的精益求精,才是我们不断进步的源泉,也是我推荐文章的重要标准。作者来自著名的团购鼻祖Groupon公司,相信这篇分享值得大家一看。

Buster Keaton — The Battling Butler.jpg

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

Groupon 正在寻找优秀的移动开发工程师,加入我们,一起构建像这样的优秀项目吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容