一個(gè)Android項(xiàng)目搞定所有主流架構(gòu)
相信大部分人都聽過(guò)這個(gè)框架,或者已經(jīng)使用過(guò)。
了解和簡(jiǎn)單運(yùn)用的過(guò)程中大家一定會(huì)有這樣幾個(gè)問(wèn)題或者痛點(diǎn):
MVP有什么好處,為什么要用它?
MVP結(jié)構(gòu)代碼怎么寫?
為什么MVP結(jié)構(gòu)利于單元測(cè)試?而且我為什么要寫測(cè)試代碼呢?
好了你說(shuō)服我了,但是我不會(huì)寫單元測(cè)試啊!
MVP多了好多類,還要寫測(cè)試代碼,寫起來(lái)好累啊!老娘不想這么麻煩啊!
這里班門弄斧的分享下我的經(jīng)驗(yàn),挨個(gè)解決這幾個(gè)問(wèn)題。
MVP有什么好處,為什么要用它?網(wǎng)上文章一大堆,總結(jié)下來(lái)主要有下面幾個(gè)優(yōu)點(diǎn):
代碼解耦、結(jié)構(gòu)更清晰更好的拓展性可復(fù)用性利于單元測(cè)試優(yōu)點(diǎn)其實(shí)主要是相對(duì)傳統(tǒng)MVC結(jié)構(gòu)而言的,簡(jiǎn)單對(duì)比下:
MVC(Model-View-Controller)傳統(tǒng)MVC結(jié)構(gòu)中,C承擔(dān)著一個(gè)總控制器的作用,處理Model數(shù)據(jù),再控制View的顯示。大部分時(shí)候Activity類就是這個(gè)角色,我們?cè)贏ctivity中調(diào)用接口,接口返回?cái)?shù)據(jù)后各種setText setImage顯示到UI上。MVP(Model-View-Presenter)重點(diǎn)在于Presenter,它其實(shí)是將Model和View分開了,在其中起到一個(gè)中轉(zhuǎn)站的角色。把Model數(shù)據(jù)拿來(lái)一通處理,然后丟給View讓它自己去解決具體的UI顯示。打個(gè)比方如果處理Model處理業(yè)務(wù)邏輯就是加工食材做菜。把菜送到客戶手里呈現(xiàn)給客戶就是View的展示。那MVC就是大排檔。C就是獨(dú)自運(yùn)營(yíng)的老板,自己炒菜,做完再自己送到小桌子上的客戶面前,一條龍。MVP就是正規(guī)大餐廳,P則是后廚中心,海綿寶寶做好蟹黃堡后放到窗口處,叮一下通知前臺(tái)好了可以送餐了,不用關(guān)心菜是怎么送到客戶手里的。然后由服務(wù)員章魚哥在窗口處取了餐,再或跑或跳或踩著轱轆鞋最后送到客戶手里,合作完成。
所以這里也可以看出來(lái),MVP最重要的特點(diǎn)就是:
將 Model業(yè)務(wù)邏輯處理 和 View頁(yè)面處理 分開!!!
MVP的良好拓展性、解耦、利于單元測(cè)試等優(yōu)點(diǎn)基本都是來(lái)源于此。
純語(yǔ)言描述大家可能還是不好理解,下面上實(shí)戰(zhàn)項(xiàng)目。
MVP結(jié)構(gòu)代碼怎么寫?示例項(xiàng)目中的MVP結(jié)構(gòu)參考了 谷歌官方MVP示例項(xiàng)目 中的寫法。每個(gè)功能模塊都包含以下幾部分:
Contact協(xié)議類
這個(gè)Contact協(xié)議類不是MVP中的任何一個(gè)模塊,是把所有View和Presenter的方法都提取成了接口放在這里,作為一個(gè)總的規(guī)則、協(xié)議,方便統(tǒng)一管理。
比如下面的代碼,就是示例項(xiàng)目中意見反饋?lái)?yè)面的Contact協(xié)議類,提供了View和Presenter的接口。
其中BaseView和BasePresenter是提供了一些基礎(chǔ)方法,比如顯示進(jìn)度showProgress等,自己可以按需添加。
public interface FeedBackContract { interface View extends BaseView<Presenter> { void addFeedbackSuccess(); } interface Presenter extends BasePresenter { void addFeedback(String content, String email); }}
Model
數(shù)據(jù)層,和MVC結(jié)構(gòu)中的無(wú)區(qū)別,沒(méi)啥好說(shuō)的。
Presenter
負(fù)責(zé)處理業(yè)務(wù)邏輯代碼,處理Model數(shù)據(jù),然后分發(fā)給View層的抽象接口。
注意,這里是將處理好的數(shù)據(jù)派發(fā)給View的抽象接口,是一個(gè)簡(jiǎn)單的中轉(zhuǎn)分發(fā)出去,并不負(fù)責(zé)具體展示
public class FeedBackPresenter implements FeedBackContract.Presenter { private final FeedBackContract.View view; private final HttpRequest.ApiService api; public FeedBackPresenter(FeedBackContract.View view, HttpRequest.ApiService api) { this.view = view; this.api = api; this.view.setPresenter(this); } @Override public void addFeedback(String content, String email) { // 開始驗(yàn)證輸入內(nèi)容 if (StringUtils.isEmpty(content)) { view.showTip('反饋內(nèi)容不能為空'); return; } if (StringUtils.isEmpty(email)) { view.showTip('請(qǐng)輸入郵箱地址,方便我們對(duì)您的意見進(jìn)行及時(shí)回復(fù)'); return; } view.showProgress(); // 使用自定義對(duì)象存至云平臺(tái),作為簡(jiǎn)易版的反饋意見收集 FeedBack fb = new FeedBack(); fb.setContent(content); fb.setEmail(email); Observable<BaseEntity> observable = ObservableDecorator.decorate(api.addFeedBack(fb)); observable.subscribe(new Subscriber<BaseEntity>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { if (!view.isActive()) { return; } view.dismissProgress(); view.showTip('反饋提交失敗'); } @Override public void onNext(BaseEntity entity) { if (!view.isActive()) { return; } view.dismissProgress(); view.addFeedbackSuccess(); } }); }}
View
負(fù)責(zé)UI具體實(shí)現(xiàn)展現(xiàn)。比如Presenter派發(fā)過(guò)來(lái)一個(gè)動(dòng)作是showProgress顯示進(jìn)度命令,那由我這個(gè)View負(fù)責(zé)實(shí)現(xiàn)具體UI,是顯示進(jìn)度框還是顯示一個(gè)下拉刷新圈圈等,都是View這里自行控制。
Google的例子中,每個(gè)Activity中都會(huì)添加一個(gè)Fragment作為View實(shí)現(xiàn),Activity僅僅作為一個(gè)容器,包含一個(gè)Fragment在其中顯示各種控件。我覺得其實(shí)也可以直接將Activity作為View。本示例代碼中兩種方式都有,可以根據(jù)需要自行選擇方式~
public class FeedBackActivity extends BaseActivity implements FeedBackContract.View { private FeedBackContract.Presenter presenter; private EditText et_content; private EditText et_email; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_feed_back); initView(); } private void initView() { presenter = new FeedBackPresenter(this, HttpRequest.getInstance().service); initBackTitle('意見反饋') .setRightText('提交') .setRightOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { submit(); } }); et_content = (EditText) findViewById(R.id.et_content); et_email = (EditText) findViewById(R.id.et_email); } private void submit() { // 開始驗(yàn)證輸入內(nèi)容 String content = et_content.getText().toString().trim(); String email = et_email.getText().toString().trim(); presenter.addFeedback(content, email); } @Override public void addFeedbackSuccess() { showToast('反饋成功'); finish(); } @Override public void setPresenter(FeedBackContract.Presenter presenter) { this.presenter = presenter; } @Override public boolean isActive() { return isActive; } @Override public void showProgress() { showProgressDialog(); } @Override public void dismissProgress() { dismissProgressDialog(); } @Override public void showTip(String message) { showToast(message); }}
注意,這里BaseView中會(huì)有一個(gè)isActivite方法,用于判斷視圖是否被銷毀。我在BaseActivity中會(huì)統(tǒng)一處理,添加一個(gè)isActivite變量,onStart時(shí)設(shè)為true,onStop時(shí)設(shè)為false。
然后在presenter里的接口返回?cái)?shù)據(jù)后,判斷view是否被銷毀然后再控制顯示,因?yàn)榻涌谑钱惒降模苑祷財(cái)?shù)據(jù)后視圖可能已經(jīng)銷毀,那就沒(méi)必要更新了,更新反而還會(huì)崩潰報(bào)錯(cuò)。
好了,現(xiàn)在再回頭看看MVP的幾個(gè)優(yōu)點(diǎn),可能就有更好的理解了(當(dāng)然,還是要自己擼過(guò)一遍最好)。
更好的拓展性。某天頁(yè)面需要加功能了,協(xié)議類中先寫好對(duì)應(yīng)的P邏輯方法、V頁(yè)面方法,然后在實(shí)現(xiàn)類中分別編寫具體代碼即可。某天突然改功能了,說(shuō)所有錯(cuò)誤提示我們不用Toast,用Dialog吧,那直接在showTip處修改即可。某天產(chǎn)品突然告訴你說(shuō)意見反饋,失敗我們也讓用戶覺得成功,那直接在Error回調(diào)里調(diào)用view抽象方法即可。解耦、更好的代碼結(jié)構(gòu)。業(yè)務(wù)邏輯 和 頁(yè)面UI 代碼分開,不揉在一起,改邏輯的時(shí)候不用關(guān)心UI,反之亦然。想了解某個(gè)模塊功能時(shí),直接在協(xié)議類中看一個(gè)個(gè)抽象方法,不用關(guān)心代碼,清晰明了。還有代碼可以分工合作,核心業(yè)務(wù)邏輯你在P中自己寫,UI的具體實(shí)現(xiàn)直接給其他人合作寫。可復(fù)用性。比如本項(xiàng)目中的注冊(cè)功能,注冊(cè)步驟1和步驟2頁(yè)面中都有發(fā)送驗(yàn)證碼功能,那就可以使用同一個(gè)P了,在其中調(diào)用獲取驗(yàn)證碼接口。然后各自實(shí)現(xiàn)具體View顯示,步驟1頁(yè)面獲取驗(yàn)證碼成功后跳轉(zhuǎn)到頁(yè)面2,頁(yè)面2獲取成功后開始數(shù)字倒計(jì)時(shí)。為什么MVP結(jié)構(gòu)利于單元測(cè)試?之前提到過(guò),MVP結(jié)構(gòu)最大的特點(diǎn)是,P將邏輯和UI分開了。即P 中沒(méi)有任何Android相關(guān)的代碼,比如Toast啊、setText等等。這意味著~ 你可以針對(duì)Presenter寫junit測(cè)試了。只對(duì)java代碼的測(cè)試,不用涉及任何UI!!!不用運(yùn)行模擬器的測(cè)試!!!!!速度起飛的測(cè)試!!!!!!!!
說(shuō)的這么熱鬧,那么
我為什么要寫測(cè)試代碼呢?不是浪費(fèi)時(shí)間嗎?測(cè)試其實(shí)除了檢測(cè)bug驗(yàn)證邏輯之外,還有最重要的一個(gè)功能是 提高開發(fā)速度!
你沒(méi)有看錯(cuò),雖然寫了更多的代碼,但實(shí)際效率是提升的,尤其對(duì)越龐大越復(fù)雜的應(yīng)用來(lái)說(shuō)。
可能我這樣說(shuō)不夠權(quán)威,可以看下經(jīng)典書籍《重構(gòu)》然后自己嘗試一下,可能就會(huì)有感受了。
怎么寫測(cè)試代碼呢?我們先介紹下Android中的兩種測(cè)試
UI測(cè)試(本項(xiàng)目中使用框架Espresso)
UI測(cè)試其實(shí)就是模擬機(jī)器上的操作行為,讓它自動(dòng)進(jìn)行的“點(diǎn)擊某個(gè)位置”、“輸入某些字符串”等行為。
是依賴安卓設(shè)備的,測(cè)試的時(shí)候可以在手機(jī)或模擬器屏幕上看到頁(yè)面被各種點(diǎn)點(diǎn)點(diǎn),輸輸輸,跳來(lái)跳去。
這個(gè)其實(shí)和MVP結(jié)構(gòu)關(guān)系不大,MVC,MVP,或者M(jìn)VABCDEFG都可以進(jìn)行UI測(cè)試,所以這里暫時(shí)不多做介紹,可以直接參考示例項(xiàng)目中的代碼。UI測(cè)試部分的內(nèi)容其實(shí)也很多,以后單獨(dú)拿出來(lái)再詳細(xì)展開。
項(xiàng)目中androidTest文件夾里的就是UI測(cè)試代碼,而test文件夾才是Junit部分的單元測(cè)試代碼。
對(duì)Presenter進(jìn)行Junit單元測(cè)試(本項(xiàng)目中使用框架Mockito)
UI測(cè)試雖然接近真實(shí)場(chǎng)景,但是有個(gè)缺點(diǎn)是要運(yùn)行應(yīng)用到模擬器上,所以速度就會(huì)有影響,慢~
而且開發(fā)中也會(huì)常有這樣一個(gè)需要,調(diào)試接口時(shí),我不想點(diǎn)點(diǎn)點(diǎn)跳轉(zhuǎn)到那個(gè)頁(yè)面再輸入東西再點(diǎn)按鈕,費(fèi)時(shí)間啊~而用postman啥的工具也麻煩,header還要重新寫,如果有參數(shù)加密就更蛋疼了。
所以,這個(gè)時(shí)候你就需要Junit單元測(cè)試了,最大的特點(diǎn)就是 不用運(yùn)行安卓設(shè)備,直接run代碼,速度飛快!
單元測(cè)試代碼示例
正式開始介紹怎么寫之前,先感受下單元測(cè)試是什么樣的,如下圖
意見反饋Presenter代碼截圖
這里針對(duì)示例項(xiàng)目中意見反饋Presenter分別測(cè)試了幾個(gè)場(chǎng)景
真實(shí)接口提交成功模擬接口提交成功模擬接口提交失敗三個(gè)Test方法,針對(duì)三個(gè)測(cè)試場(chǎng)景。
突破左下角運(yùn)行情況可以看到,一共用了852ms,1秒不到!!!
第一個(gè)測(cè)試方法因?yàn)槭钦鎸?shí)調(diào)用接口數(shù)據(jù),所以稍微耗費(fèi)點(diǎn)時(shí)間。
右下角也可以看到3個(gè)用例全測(cè)試成功通過(guò),也打印了真實(shí)調(diào)用數(shù)據(jù)的接口日志。
完美~
如何寫單元測(cè)試代碼編寫步驟按照以下進(jìn)行
1. 新建Presenter的測(cè)試類
右鍵Presenter類 -> Go To -> Test -> create new test
彈出一個(gè)創(chuàng)建測(cè)試類對(duì)話框,然后勾選需要測(cè)試的方法(當(dāng)然也可以自己手動(dòng)創(chuàng)建方法)。
然后OK,選擇test文件夾完成測(cè)試類創(chuàng)建。
2. 測(cè)試類的初始化
代碼如下(mockito的gradle配置等參考項(xiàng)目中build.gradle)
// 用于測(cè)試真實(shí)接口返回?cái)?shù)據(jù) private FeedBackPresenter presenter; // 用于測(cè)試模擬接口返回?cái)?shù)據(jù) private FeedBackPresenter mockPresenter; @Mock private FeedBackContract.View view; @Mock private HttpRequest.ApiService api; @Before public void setupMocksAndView() { // 使用Mock標(biāo)簽等需要先init初始化一下 MockitoAnnotations.initMocks(this); // 當(dāng)view調(diào)用isActive方法時(shí),就返回true表示UI已激活。方便測(cè)試接口返回?cái)?shù)據(jù)后測(cè)試view的方法 when(view.isActive()).thenReturn(true); // 設(shè)置單元測(cè)試標(biāo)識(shí) BoreConstants.isUnitTest = true; // 用真實(shí)接口創(chuàng)建反饋 Presenter presenter = new FeedBackPresenter(view, HttpRequest.getInstance().service); // 用mock模擬接口創(chuàng)建反饋 Presenter mockPresenter = new FeedBackPresenter(view, api);}
這里用到了一個(gè)很重要的框架 Mockito。
Mockito框架介紹
Mockito框架是干什么的?
mockito框架是用來(lái)模擬數(shù)據(jù)和情景的,方便我們的測(cè)試工作進(jìn)行。為什么要用Mockito框架?
比如我們MVP結(jié)構(gòu)中P的測(cè)試,有個(gè)問(wèn)題是:創(chuàng)建Presenter對(duì)象的時(shí)候這個(gè)View怎么辦?傳入null會(huì)空指針啊。還有很多接口調(diào)用等邏輯,很多奇怪的失敗情況怎么測(cè)試?這個(gè)時(shí)候就可以用mockito了~ 直接模擬一個(gè)view接口對(duì)象,不用關(guān)心它的具體實(shí)現(xiàn);失敗情況直接用when方法搞定;此外還提供了其他一系列方便測(cè)試的方法,比如verify用于判斷某對(duì)象是否執(zhí)行了某個(gè)方法等。后面會(huì)根據(jù)例子挨個(gè)介紹。網(wǎng)上很多例子其實(shí)是純mock模擬測(cè)試,也就是接口api也是模擬的,模擬接口調(diào)用,模擬接口返回?cái)?shù)據(jù)。
雖然這樣速度快且方便模擬各種錯(cuò)誤情況,但是有時(shí)候也會(huì)想要測(cè)試真實(shí)的接口返回情況,因此本項(xiàng)目示例中提供了兩種模擬和真實(shí)接口的寫法和處理。參考上面代碼里的presenter和mockPresenter對(duì)象。
注意,mock相關(guān)方法比如verify、when等使用者也都必須是mock對(duì)象,所以使用presenter的時(shí)候不能用when什么的方法模擬接口返回。
@Before標(biāo)簽的方法,是每個(gè)測(cè)試方法調(diào)用前都會(huì)走一遍的方法,因此在里面放了一系列的初始化操作,每個(gè)操作都添加了注釋。其中需要單獨(dú)解釋的是when方法。
when(view.isActive()).thenReturn(true);
這個(gè)是mockito框架提供的一個(gè)方法,看英文基本就能了解什么意思了,當(dāng)xx方法調(diào)用時(shí)就返回xx
因?yàn)槲覀兊膙iew的模擬的,所以沒(méi)有實(shí)現(xiàn)isActive方法,則p中數(shù)據(jù)返回后就無(wú)法繼續(xù)走下去了,因此這里when處理一下。只要調(diào)用這個(gè)方法就返回true。
3. 測(cè)試方法編寫
通常Presenter中的一個(gè)業(yè)務(wù)方法會(huì)對(duì)應(yīng)至少一個(gè)測(cè)試方法。
比如這里的意見反饋業(yè)務(wù),就分別對(duì)應(yīng)意見提交成功、失敗兩種情景。
方法名字可以隨便定,有個(gè)@Test標(biāo)簽即可,推薦方法取名為:test+待測(cè)方法原名+測(cè)試場(chǎng)景
測(cè)試場(chǎng)景一共有哪些呢?這個(gè)最好問(wèn)測(cè)試要個(gè)測(cè)試用例按照待測(cè)功能對(duì)應(yīng)的所有情景挨個(gè)來(lái)。
我這里寫的單元測(cè)試代碼,對(duì)于接口又分了兩種: 模擬接口 和 真實(shí)接口
直接全部用真實(shí)接口測(cè)不很好嗎,為什么要mock模擬測(cè)試呢?
好吧,比如我們這個(gè)意見反饋,不像登錄還有密碼錯(cuò)誤的情況,很少有場(chǎng)景能失敗。怎么辦?
所以對(duì)于難以模擬的情景,還是需要用mockito框架模擬的,模擬個(gè)失敗,然后驗(yàn)證失敗后的一系列邏輯~
下面挨個(gè)介紹測(cè)試方法,模擬成功和失敗差不多就只介紹失敗了。
模擬接口測(cè)試方法示例 - 模擬提交失敗
@Test public void testAddFeedback_Mock_Error() throws Exception { // 模擬數(shù)據(jù),當(dāng)api調(diào)用addFeedBack接口傳入任意值時(shí),就拋出錯(cuò)誤error when(api.addFeedBack(any(FeedBack.class))) .thenReturn(Observable.<BaseEntity>error(new Exception('孫賊你說(shuō)誰(shuí)辣雞呢?'))); String content = '這個(gè)App真是辣雞!'; String email = '120@qq.com'; mockPresenter.addFeedback(content, email); verify(view).showProgress(); verify(view).dismissProgress(); verify(view).showTip('反饋提交失敗');}
這里重點(diǎn)是when的運(yùn)用,當(dāng)模擬的api調(diào)用addFeedBack時(shí),就返回error結(jié)果。
然后調(diào)用mockPresenter的意見反饋業(yè)務(wù)方法,最后驗(yàn)證結(jié)果。
注意,這個(gè)verify方法也是特別常用的一個(gè)mockito方法,用于驗(yàn)證某個(gè)對(duì)象是否執(zhí)行了某個(gè)方法。
最后運(yùn)行測(cè)試,成功,完美~
真實(shí)接口測(cè)試方法示例 - 提交成功
@Test public void testAddFeedback_Success() throws Exception { // 真實(shí)數(shù)據(jù),調(diào)用實(shí)際接口 String content = '這個(gè)App真是好!'; String email = '110@qq.com'; presenter.addFeedback(content, email); verify(view).showProgress(); verify(view).dismissProgress(); verify(view).addFeedbackSuccess();}
這里用了真實(shí)接口對(duì)應(yīng)的presenter對(duì)象,調(diào)用接口,然后驗(yàn)證成功結(jié)果。
運(yùn)行測(cè)試,成功,完美
再次強(qiáng)調(diào),mockito的方法都是針對(duì)模擬對(duì)象的,所以調(diào)用真實(shí)請(qǐng)求api時(shí),你也想用when去處理,那就會(huì)報(bào)錯(cuò)~
注意,真實(shí)接口由于是異步的,所以如果不做任何處理是無(wú)法測(cè)試通過(guò)的,接口數(shù)據(jù)還沒(méi)返回就運(yùn)行下面的驗(yàn)證了,自然失敗。因此需要對(duì)回調(diào)做一個(gè)處理,將其修改為同步請(qǐng)求,這樣就能一條線下來(lái)了,運(yùn)行完接口再進(jìn)行驗(yàn)證。項(xiàng)目是基于Retrofit框架的,使用RxJava處理回調(diào),我這里所有的回調(diào)都會(huì)用一個(gè)ObservableDecorator處理一下,而在其中我會(huì)判斷,如果當(dāng)前是測(cè)試狀態(tài)(也就是Before中的那個(gè)isUnitTest 參數(shù)),就將回調(diào)設(shè)置為同步,具體代碼參考項(xiàng)目中。
4. 運(yùn)行單元測(cè)試用例
右鍵方法,run 測(cè)試單個(gè)用例方法右鍵類,run 測(cè)試該類中包含的全部用例方法最后控制臺(tái)看結(jié)果
參考最上面單元測(cè)試代碼示例中的截圖,下面控制臺(tái)會(huì)顯示測(cè)試了哪些方法,測(cè)試成功通過(guò)了幾個(gè)方法,然后打印相應(yīng)日志,如果不通過(guò)還會(huì)打印對(duì)應(yīng)錯(cuò)誤信息。
好了,寫法介紹完畢~
更多例子請(qǐng)去項(xiàng)目中查看,這里篇幅有限就不太詳細(xì)的展開了,簡(jiǎn)單列舉幾個(gè)例子讓大家感受下。
MVP多了好多類,還要寫測(cè)試代碼,寫起來(lái)好累啊!老娘不想這么麻煩啊!這一點(diǎn)估計(jì)是最重要的原因把絕大部分人阻擋在門外。
畢竟平常普通的擼就那么累了,還要這么麻煩,沒(méi)時(shí)間啊沒(méi)精力啊!!!
不一定所有功能都用MVP
就像之前例子舉得那樣,大排檔和正規(guī)餐廳。你在一個(gè)超級(jí)偏遠(yuǎn)沒(méi)人流量生意差到爆的地方還整個(gè)后廚中心,就過(guò)了。同理,如果你有的功能業(yè)務(wù)邏輯比較簡(jiǎn)單,自然就沒(méi)必要MVP了,簡(jiǎn)單的關(guān)于頁(yè)面你也一頓MVP可能就有點(diǎn)猛了,所以不一定所有功能都使用MVP。
單元測(cè)試?yán)陂_發(fā)
代碼結(jié)構(gòu)啥的就不說(shuō)了,單元測(cè)試這個(gè)有時(shí)候真的很方便,尤其是運(yùn)行快。相信大部分人都有經(jīng)驗(yàn),遇到個(gè)不靠譜后臺(tái)的時(shí)候,經(jīng)常要陪他們調(diào)接口,再遇到那種特別深的頁(yè)面簡(jiǎn)直是浪費(fèi)人生。單元測(cè)試代碼,run,唰~秒搞定。自測(cè)某些邏輯功能時(shí)也很有用,這一點(diǎn)上看來(lái)絕對(duì)是節(jié)省時(shí)間的。
LiveTemplate(干貨!!!一鍵生成模板代碼,模板可自定義!!!)
我通常擼的時(shí)候特別特別注重速度效率。之前也開發(fā)過(guò)很多插件工作,比如已經(jīng)發(fā)布的自動(dòng)生成代碼布局的開源AndroidStudio插件。 https://github.com/boredream/BorePlugin
然后就尋思,寫這種特別有規(guī)律的MVP各種類,還有測(cè)試類等的時(shí)候,要不也弄個(gè)插件生成下?
但是想了下覺得插件生成模板代碼的話,模板怎么寫呢?尤其MVP這種不同的人寫法也不同啊。
最后突然想起來(lái)了AndroidStudio里自帶的LiveTemplate這東西,是AS中自帶的一個(gè)模板代碼系統(tǒng)。
使用LiveTemplate模板
先展示下該功能的強(qiáng)大,這里我以前提前寫好過(guò)幾個(gè)模板了。拿協(xié)議類舉例。
右鍵需要生成的位置 -> New -> 選擇模板(如下圖的MvpContract)那么模板哪里來(lái)的呢~下面介紹
編輯/創(chuàng)建LiveTemplate模板
編輯已有模板 : New -> 選擇模板的時(shí)候,模板底部有個(gè)Edit File Template,點(diǎn)擊之。參見上面使用步驟1的圖。創(chuàng)建新的模板 :打開你希望生成模板的文件,選擇工具欄中的Tool -> Save File as Template步驟1、 2都會(huì)打開下面這樣一個(gè)編輯頁(yè)面,區(qū)別在于創(chuàng)建比編輯少個(gè)左側(cè)的已有模板列表給模板起個(gè)名字,然后在內(nèi)容頁(yè)面里根據(jù)需要?jiǎng)h刪改改即可,模板里所有${NAME}的地方都會(huì)替換成你創(chuàng)建模板時(shí)候輸入的文件名,其他的${XXX}的作用可參考下面Description里的描述。最后OK保存模板。LiveTemplate雖然無(wú)法替你搞定絕大部分代碼,但是這樣一個(gè)快捷的模板,可以靈活的隨時(shí)編輯還是很方便的,還是能節(jié)省相當(dāng)代碼量的。
和本期主題無(wú)關(guān)的插個(gè)話,LiveTemplate是個(gè)很神奇的東西,很多地方都可以用,不光有文件的模板,代碼也是。比如輸入sout+回車就會(huì)自動(dòng)生成System.out.print()代碼,輸入Toast+回車就會(huì)自動(dòng)生成Toast.make blablabl的代碼,超級(jí)方便。比如你們項(xiàng)目有BaseActivity,需要復(fù)寫幾個(gè)方法,那就可以自定義創(chuàng)建個(gè)頁(yè)面類文件模板里面處理好繼承和方法,就不用每次新建完Activity都去寫一下繼承了。更多用法期待你滴挖掘~
結(jié)語(yǔ)好了,之前提的所有問(wèn)題和痛點(diǎn)都挨個(gè)解答過(guò)了,尤其最后的LiveTemplate,對(duì)于還不知道的同學(xué),即使最后你還是不愿意用MVP和寫單元測(cè)試,那這部分你也算賺到了哈哈。
因?yàn)橐榻B的內(nèi)容比較多,MVP啊~測(cè)試啊~Junit單元測(cè)試啊~LiveTemplate啊~ 所以介紹的比較精簡(jiǎn),主旨在拋磚引玉,希望大家對(duì)這幾個(gè)東西能有個(gè)了解,感興趣后再深入研究,也希望與我多多交流大家共同進(jìn)步。
本項(xiàng)目里Junit測(cè)試模塊其實(shí)還是有幾個(gè)問(wèn)題的,比如Presenter我是將接口Api作為構(gòu)造函數(shù)參數(shù)依賴注入的,所以其實(shí)還可以再加入Dagger2改進(jìn)一番,下一個(gè)框架就會(huì)在MVP的結(jié)構(gòu)上加入Dagger2。
谷歌例子中RxJava是單獨(dú)拎出來(lái)說(shuō)的,我這里Retrofit2+RxJava是作為所有例子通用框架的,用法可以給大家作為一個(gè)參考,這里就不掃盲Retrofit用法了。
來(lái)自:http://www.jianshu.com/p/aa948c640433
相關(guān)文章:
1. 得到XML文檔大小的方法2. asp.net web api2設(shè)置默認(rèn)啟動(dòng)登錄頁(yè)面的方法3. 解決ajax異步請(qǐng)求返回的是字符串問(wèn)題4. Ajax原理與應(yīng)用案例快速入門教程5. .NET 實(shí)現(xiàn)啟動(dòng)時(shí)重定向程序運(yùn)行路徑及 Windows 服務(wù)運(yùn)行模式部署的方法6. Ajax常用封裝庫(kù)——Axios的使用7. 如何降低node版本,怎樣實(shí)現(xiàn)降低node版本8. SQL+HTML+PHP 一個(gè)簡(jiǎn)單論壇網(wǎng)站的綜合開發(fā)案例(注冊(cè)、登錄、注銷、修改信息、留言等)9. ASP.NET MVC使用Quartz.NET執(zhí)行定時(shí)任務(wù)10. 在 XSL/XSLT 中實(shí)現(xiàn)隨機(jī)排序
