聲明:本文也在我的微信公眾號(hào) Android程序員(AndroidTrending) 發(fā)布别凤。
原文鏈接:Better Android Intents with Dart & Henson
原文作者:Daniel Molinero Reguera
譯文出自:湯濤的簡書
譯者:湯濤
狀態(tài):完成
最近看到這篇文章,感覺不錯(cuò),就翻譯了一下低剔。文中提到的 Android Intent 的種種問題,有些也是我之前遇到的一些痛點(diǎn)向臀,項(xiàng)目規(guī)模稍大一些后工三,有些問題會(huì)慢慢暴露出來,雖不是非常嚴(yán)重讲冠,但正是對(duì)代碼的精益求精瓜客,才是我們不斷進(jìn)步的源泉,也是我推薦文章的重要標(biāo)準(zhǔn)竿开。作者來自著名的團(tuán)購鼻祖Groupon公司谱仪,相信這篇分享值得大家一看。
Intent 是 Android 生態(tài)系統(tǒng)的重要組成部分否彩。他們用來表達(dá)一個(gè)執(zhí)行動(dòng)作疯攒,可分為隱式和顯式 Intent。在應(yīng)用程序內(nèi)部列荔,所有的 Intent 以一種抽象的方式敬尺,一起定義了一個(gè)信息傳遞層。在本文中贴浙,我們將解釋為什么 Android 創(chuàng)建顯式 Intent 的方式容易出錯(cuò)砂吞,也給大家展示一些有問題的應(yīng)對(duì)方案。最后崎溃,我們將介紹一個(gè)生成這種信息傳遞層的庫:Dart & Henson蜻直,它使用簡單,能方便、快捷與健壯地在你的 Activity 和 Service 之間傳遞信息袭蝗。
顯示 Intent 需要明確指定組件唤殴,常用于在應(yīng)用內(nèi)的 Activity 或 Intent 之間傳遞信息,額外的信息通過 extras 提供給目標(biāo)組件到腥,與 Intent 一起傳遞朵逝。比如下面的代碼,創(chuàng)建了一個(gè)顯示 Intent 來啟動(dòng) Activity:
Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra(EXTRA_ITEM_ID, selectedItem.id);
intent.putExtra(EXTRA_SHOW_MAP, true);
startActivity(intent);
被啟動(dòng)的 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");
}
...
}
...
}
這種機(jī)制很好地處理了組件的創(chuàng)建與通信乡范,但仍然有一些問題需要注意:
- 目標(biāo)組件作為一個(gè)實(shí)體配名,對(duì)輸入沒有任何控制權(quán)。在我們的例子里晋辆,itemId 是必需的渠脉,但如果沒有傳遞它,DetailActivity 最好的處理方式也只能是拋出異常瓶佳。
- Intent 的創(chuàng)建(完全)不夠健壯芋膘,并沒有對(duì) extra 中的 key 或 value 做任何檢查。
一分預(yù)防勝過十分治療
有問題的解決方案
解決這些問題的一個(gè)可能的方案是 Intent 工廠模式霸饲。它主要由一些工廠方法組成为朋,包含了應(yīng)用程序里用到的各種 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;
}
...
}
然而厚脉,這種解決方案有一些局限习寸,并不是一個(gè)很好的辦法。
- Intent 工廠是一個(gè)集中類傻工,這個(gè)類可能會(huì)變得很大且復(fù)雜霞溪。
- 它違背了開放/閉合原則。對(duì)修改并沒有關(guān)閉中捆,我們將總是需要給每個(gè)新的 Activity 添加一個(gè)新方法鸯匹。
- 目標(biāo)組件應(yīng)該是唯一知曉參數(shù)細(xì)節(jié)與邏輯的地方。
- 可選參數(shù)處理泄伪。同一個(gè)組件有不同的需求殴蓬,是否應(yīng)該寫不同的方法?還是寫一個(gè)方法并使用默認(rèn)值臂容?
- 它會(huì)誘使后續(xù)的開發(fā)人員模仿,進(jìn)而產(chǎn)生其他的 Intent 工廠根蟹,最終演變成大泥球模式脓杉,使得代碼越來越糟。
有一個(gè)類似的策略可以分散這些工廠方法到各自的目標(biāo)組件简逮。也就是指球散,每個(gè)組件可以包含一個(gè)(或多個(gè))靜態(tài)方法,用于生成這些啟動(dòng)它自身的 Intent散庶。這個(gè)辦法可以解決開放/閉合原則的問題蕉堰,分解 Intent 工廠凌净,也許還可以避免大泥球模式。盡管如此屋讶,關(guān)于可選參數(shù)的問題依然存在冰寻。有人說 builder 模式可以?我們自己實(shí)現(xiàn)它皿渗?...
我選擇用懶惰的人做困難的工作斩芭,因?yàn)橐粋€(gè)懶惰的人會(huì)找到簡單的方法完成它。比爾蓋茨
Dart 2 & Henson
Dart 是一個(gè) Android 開源庫乐疆。它綁定 Activity 字段到 Intent extra划乖,Butter Knife 也是用類似的方案,關(guān)聯(lián) 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 注解聲明了一個(gè)同名的 extra key,默認(rèn)情況下仰美,所有的注解字段都是必需的迷殿,如果 extra 沒有提供,會(huì)拋出異常筒占。如果想使其可選贪庙,需要加上 @Nullable 注解。接下來翰苫,只需要調(diào)用 Dart.inject 即可自動(dòng)生成相關(guān)代碼止邮。
在 Groupon,我們意識(shí)到注解里的那些信息奏窑,已經(jīng)足夠創(chuàng)建我們一直想要的builder模式导披。因此,我們決定在 Dart 基礎(chǔ)上再進(jìn)一步:我們做了一個(gè)注解處理器埃唯,用于生成 Intent builders撩匕,這個(gè)新模塊叫做 Henson,它集成在 Dart 2 中墨叛。
在 DetailActivity 這個(gè)例子里止毕,Henson 生成了一個(gè)小型的領(lǐng)域特定語言 (DSL),來使得跳轉(zhuǎn)到 DetailActivity 變得非常容易:
Intent intent = Henson.with(context)
.gotoDetailActivity()
.itemId(selectedItem.id)
.shouldShowMap(true)
.build();
startActivity(intent);
首先是通過 Henson.with(context).gotoXXX() 獲取目標(biāo) Activity 或 Service 的 builder漠趁。然后扁凛,使用自動(dòng)生成的方法設(shè)置必需的 extras, 比如 itemId 是使用 itemId(String str)。之后闯传,用同樣的方式設(shè)置可選參數(shù)谨朝。最后調(diào)用 build,你就可以得到一個(gè)有效的 Intent,用于啟動(dòng)你的組件字币。
這段領(lǐng)域特定語言(DSL)會(huì)為所有@InjectExtra 注解標(biāo)記的字段生成相關(guān)類则披。這相當(dāng)于一個(gè)信息傳遞層,解決了我們創(chuàng)建 Intent 時(shí)碰到的那些問題:
- 通過注解洗出,目標(biāo)組件對(duì) extras 擁有完全的控制權(quán)士复。
- DSL 定義在組件內(nèi)的一處,如果它有修改共苛,產(chǎn)生的問題都可以在編譯時(shí)被發(fā)現(xiàn)判没。
- 沒有違反開放/閉合原則,實(shí)際上隅茎,我們什么都不需要寫澄峰,一切都是自動(dòng)生成。
- 因?yàn)槭褂昧?builder 模式辟犀,可選參數(shù)很容易實(shí)現(xiàn)俏竞。
- 還可以自動(dòng)補(bǔ)全代碼!
完整的示例代碼在這里堂竟。
總結(jié)
Henson 創(chuàng)建了一個(gè)小型的領(lǐng)域特定語言(DSL)魂毁,可以更加健壯地構(gòu)建啟動(dòng) Activity 與 Service 的 Intent,它允許缺失必需的extra出嘹,支持靈活的可選參數(shù)席楚,最棒的是,使用 Dart 2 與 Henson税稼,你一行代碼也不必寫了烦秩。??
還不趕緊試試?
f2prateek/dart---Extras "injection" Library for Android
Groupon 正在尋找優(yōu)秀的移動(dòng)開發(fā)工程師郎仆,加入我們只祠,一起構(gòu)建像這樣的優(yōu)秀項(xiàng)目吧。