【譯】使用 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閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容