使用Roboguice依赖注入规划Android项目
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
前言
好久沒寫博客了,罪過啊~記事本里累積了不少東西,整理整理放上來。?
關(guān)于依賴注入?
Dependency Injection( 依賴注入)可以很好的幫助我們分離模塊,降低耦合、提高可測試性。(PS:Roboguice 只是一個工具,依賴注入更多的是一種思想)通常博主開發(fā)項目時喜歡以Activity 、Service 等組件作為頂級層入口,輔以各類接口作為業(yè)務(wù)服務(wù)。Activity 主要負(fù)責(zé)維護(hù)界面相關(guān)的東西,及提供功能所需要的上下文環(huán)境,引入功能實現(xiàn)需要的接口。
這些接口的實例通過Roboguice進(jìn)行注入。(當(dāng)然你也可以完全不使用Roboguice,但還是建議保留接口注入的設(shè)計)。
關(guān)于Roboguice
Roboguice 是基于guice-noaop 的android注入框架,項目地址:https://github.com/roboguice/roboguice?.利用Roboguice可以較輕松的注入各種服務(wù),它默認(rèn)提供了各種android相關(guān)的注入如: injectView ?,injectResource 等。
遺憾的是這里將不對Roboguice的使用詳細(xì)講解。想了解 Roboguice 的讀者可以查看官網(wǎng)的Wiki 或參考:http://www.imobilebbs.com/wordpress/archives/2480
需要注意的是Roboguice 分為 1.1 版和2.0及以上版本,這兩個版本并不兼容,一般使用2.0即可,更簡單方便。下載需要的包
可參考: https://github.com/roboguice/roboguice/wiki/InstallationNonMaven項目創(chuàng)建
創(chuàng)建android項目命名為:RoboguicePractice ,并添加Roboguice 相關(guān)包。基本功能?
項目僅包含一個Activity,界面上包含一個TextView和Button.點(diǎn)擊Button 可查看當(dāng)前時間。
?
為了使Demo更具代表性, Activity 需要引用 ?ITimeService 的接口來獲取時間。ITimeService 接口的具體實現(xiàn)類AndroidTimeReand則依賴于ITimeRepository(數(shù)據(jù)源),這樣就從邏輯上劃分出一個基本的三層。通常我喜歡把數(shù)據(jù)相關(guān)的模塊(db、sharepreferene、net、cache等)歸類到Repository中,對上層而言就形成一個數(shù)據(jù)來源接口。
注意:沒有哪一種設(shè)計是萬能,需要根據(jù)最實際的情況,不斷的進(jìn)行權(quán)衡,最終選擇較合適的系統(tǒng)設(shè)計,并且要做好睡著系統(tǒng)的成長需要變更設(shè)計的準(zhǔn)備。
例如有的android程序比較簡單,就完全不需要 IService 服務(wù)層。
項目包結(jié)構(gòu)
?
這里創(chuàng)建一個ViewModel 用于輔助界面展示使用靜態(tài)類的實現(xiàn)方式
在正式開始項目前讓我們看看一種常見的實現(xiàn)——通過靜態(tài)的方式為 Activity提供服務(wù)。??1?public?class?AndroidTimeRead?{
?2??3????????? public? static?TimeViewModel?showTime()?{
?4???????????????TimeViewModel?model?=? new?TimeViewModel();
?5???????????????model.setTime(String.?valueOf(System.currentTimeMillis?()));
?6???????????????? return?model;
?7????????}
?8?
?9?}
10?
11? public? class?MainActivity? extends?Activity?{
12?
13????????? private?TextView?txtShowTime?;
14????????? private?Button?btnShow?;
15?
16?????????@Override
17????????? protected? void?onCreate(Bundle?savedInstanceState)?{
18???????????????? super.onCreate(savedInstanceState);
19???????????????setContentView(R.layout.?activity_main);
20?
21????????????????txtShowTime?=?(TextView)?findViewById(R.id.txtShowTime);
22????????????????btnShow?=?(Button)?findViewById(R.id.?btnShow);
23????????????????btnShow.setOnClickListener(? new?View.OnClickListener()?{
24?
25???????????????????????@Override
26??????????????????????? public? void?onClick(View?v)?{
27????????????????????????????TimeViewModel?viewModel?=?AndroidTimeRead.?showTime();
28?????????????????????????????txtShowTime.setText(viewModel.getTime());
29??????????????????????}
30???????????????});
31?
32????????}
33?
34?} 代碼很簡單,也實現(xiàn)了我們的基本需要(如果產(chǎn)品到此為止的話)。但有兩個明顯的缺點(diǎn),如果項目中大部分都是用了靜態(tài),那么面向OO的各種設(shè)計也就無法使用了。
另一個問題是:當(dāng)你想對MainActivity 進(jìn)行單元測試,你會發(fā)現(xiàn)非常困難,AndroidTimeRead 必須被包含進(jìn)來,如果它還引用了其他的組件(如Db 或 net),那么這些組件也必須包含入內(nèi)。?靜態(tài)類型因為一直在內(nèi)存中,如果它引用了其他類型,則被引用的對象CG無法回收。
改進(jìn)
這里我們將AndroidTimeRead 進(jìn)行一些改進(jìn)去掉令人討厭的靜態(tài), 將AndroidTimeRead 改成單例。 ?1? public? class?AndroidTimeRead?{?2?
?3????????? private? static? class?InstaceHolder{
?4???????????????? public? static?AndroidTimeRead?instance=? new?AndroidTimeRead();
?5????????}
?6????????
?7????????? public? static?AndroidTimeRead?getInstance(){
?8???????????????? return?InstaceHolder.instance;
?9????????}
10????????
11????????? private?AndroidTimeRead(){}
12????????
13????????? public?TimeViewModel?showTime()?{
14???????????????TimeViewModel?model?=? new?TimeViewModel();
15???????????????model.setTime(String.?valueOf(System.currentTimeMillis?()));
16???????????????? return?model;
17????????}
18?
19?} MainActivitry 進(jìn)行對應(yīng)的
1????????TimeViewModel?viewModel?=?AndroidTimeRead.?getInstance().showTime();調(diào)用修改
這里去掉了靜態(tài)的方式,可是卻沒有解除直接依賴實現(xiàn)的問題。
關(guān)注行為
?設(shè)計程序時,我們應(yīng)該更加關(guān)注行為而非數(shù)據(jù),簡單的理解是盡可能面向接口編程。在這里例子中主要的行為就是showTime. 因此我們定義一個接口 為MainActivity 提供所需要的行為(即提供給用戶的服務(wù))。?
1? public? interface?ITimeService?{2????????TimeViewModel?showTime(); ?
3?} ? ???
?
MainActivity 上的修改:??1?private?ITimeService?timeService?;
?2????????? // 提供注入點(diǎn)?3? ???????? public? void?setTimeService(ITimeService?timeService)?{
?4???????????????? this.timeService?=?timeService;
?5????????}
?6????????
?7?????????@Override
?8????????? protected? void?onCreate(Bundle?savedInstanceState)?{
?9???????????????? super.onCreate(savedInstanceState);
10???????????????setContentView(R.layout.?activity_main);
11?
12????????????????txtShowTime?=?(TextView)?findViewById(R.id.txtShowTime);
13????????????????btnShow?=?(Button)?findViewById(R.id.?btnShow);
14????????????????btnShow.setOnClickListener(? new?View.OnClickListener()?{
15?
16???????????????????????@Override
17??????????????????????? public? void?onClick(View?v)?{
18????????????????????????????TimeViewModel?viewModel?=?timeService.showTime();
19?????????????????????????????txtShowTime.setText(viewModel.getTime());
20??????????????????????}
21???????????????});
22?
23????????}
?
?
這里 MainActivity 引用了 ITimeService,并通過 ?setTimeService 的方式提供注入點(diǎn)(重要)。?
到此一個基本的結(jié)構(gòu)已經(jīng)形成,當(dāng)我們需要對MainActivity進(jìn)行測試時,可以通過 ?Mock<ITimeService> 方式,并使用setTimeService 注入到MainActivity 中,解除了與具體實現(xiàn)的依賴。遺憾的是上面的程序不能正常運(yùn)行,ITimeService 沒有實例化。我們雖然提供了注入點(diǎn),但是Activity 的生命周期由系統(tǒng)接管,我們無法直接使用。
? ? ?聰明的讀者可能已經(jīng)想到,我們可以通過實現(xiàn)一個BaseActivity(繼承至Activity),然后在BaseActivity里提供IService 的實現(xiàn),如 ?getService(class<?>) ,再讓MainActivity 繼承自BaseActivity。?
事實上當(dāng)你使用Roboguice 時也是需要繼承自其提供的RoboActivity。
?
完成業(yè)務(wù)代碼
在引入Roboguice 前先完成Demo的結(jié)構(gòu)。添加ITimeRepository 和對應(yīng)的實現(xiàn),并讓AndroidTimeRead ?依賴 ITimeRepository。
?
2????? public? long?CurrentTime?;
3?}
4? public? interface?ITimeRepository?{
5????????TimeModel?query();
6?} ITimeRepository 的實現(xiàn): public? class?TimeRepository? implements?ITimeRepository?{
????????@Override
???????? public?TimeModel?query()?{
???????????????TimeModel?model= new?TimeModel();
???????????????model.CurrentTime=System.?currentTimeMillis();
??????????????
??????????????
??????????????? return?model;
???????}
}
將 AndroidTimeRead 修改,讓其從 ITimeRepository 中獲取時間: public? class?AndroidTimeRead? implements?ITimeService?{
???????? private?ITimeRepository?rep?;
???????? public?AndroidTimeRead(ITimeRepository?rep)?{
??????????????? this.rep?=?rep;
???????}
???????? public?TimeViewModel?showTime()?{
??????????????TimeViewModel?model?=? new?TimeViewModel();
??????????????model.setTime(?"現(xiàn)在的時間是"?+?String.valueOf(?rep.query()));
??????????????? return?model;
???????}
}
可以發(fā)現(xiàn),這里AndroidTimeRead 也是依賴于 ITimeRepository接口的,并且通過構(gòu)造函數(shù),提供了注入口。
新的時間獲取方式的修改,并沒有要求MainActivity 函數(shù)做任何修改。如果是直接使用AndroidTimeRead,則需要變更MainActivity。
引入Roboguice ?應(yīng)該放在哪里?
? ? ?上面的代碼都是與getTime() 業(yè)務(wù)相關(guān)的,而Roboguice 卻是屬于系統(tǒng)支持類。一個真正的項目中通常會包含不少這樣的組件如:日志、行為打點(diǎn)等等。這里組件較明顯的特征是與業(yè)務(wù)的關(guān)系度不大,甚至直接移除也不會影響功能的正常使用。 ? 對于這些組件,我通常會以一種腳手架的設(shè)計方式,將它們組織起來,并為其提供系統(tǒng)接入點(diǎn)。命名一個Infrastructure包,將需要的基礎(chǔ)設(shè)施放置在此。
引入RoboActivity
將MainActivity 的父類修改為 RoboActivity,為View添加InjectView注入 ?1? public? class?MainActivity? extends?RoboActivity?{?2?
?3?????????@InjectView(R.id.txtShowTime?)
?4????????? private?TextView?txtShowTime?;
?5?????????@InjectView(R.id.btnShow?)
?6????????? private?Button?btnShow?;
?7?
?8?????????@Inject
?9????????? private?ITimeService?timeService?;
10????????? // 提供注入點(diǎn)
11? ???????? public? void?setTimeService(ITimeService?timeService)?{
12???????????????? this.timeService?=?timeService;
13????????}
14????????
15?????????@Override
16????????? protected? void?onCreate(Bundle?savedInstanceState)?{
17???????????????? super.onCreate(savedInstanceState);
18???????????????setContentView(R.layout.?activity_main);
19?
20????????????????btnShow.setOnClickListener(? new?View.OnClickListener()?{
21?
22???????????????????????@Override
23??????????????????????? public? void?onClick(View?v)?{
24????????????????????????????TimeViewModel?viewModel?=?timeService.showTime();
25?????????????????????????????txtShowTime.setText(viewModel.getTime());
26??????????????????????}
27???????????????});
28?
29????????} 由于 ITimeService 是我們自定義的服務(wù),需要為其指定實現(xiàn)。 創(chuàng)建RoboApplication 并繼承自android 的Application同時修改AndroidManifest 里的配置。創(chuàng)建一個TimeModule類實現(xiàn)Module接口。 1? public? class?RoboApplication? extends?Application?{
2?
3?????????@Override
4????????? public? void?onCreate()?{
5???????????????? super.onCreate();
6???????????????RoboGuice.?setBaseApplicationInjector( this,?RoboGuice.?DEFAULT_STAGE,
7????????????????????????????RoboGuice.?newDefaultRoboModule( this),? new?TimeModule());
8????????}
9?} setBaseApplicationInjector 最后一個參數(shù)是變參可以注冊多個Module
?1? public? class?TimeModule? implements?Module?{
?2?
?3?????????@Override
?4????????? public? void?configure(Binder?binder)?{
?5???????????????? // 順序無關(guān),在具體的Activity中?被創(chuàng)建
?6? ???????????????binder
?7??????????.bind(ITimeService.? class)
?8??????????.to(AndroidTimeRead.? class);
?9??????????? // .in(Singleton.class); // 單件
10? ????????
11??????????binder.bind(ITimeRepository.? class)
12??????????.to(TimeRepository.? class);
13?
14????????}
15?
16?}
binder 用于指定接口和具體的實現(xiàn)的映射, 這里仍舊依賴一個問題,就是 ?AndroidTimeRead 對 ITimeRepository 的依賴需要指定。 這種復(fù)雜類型需要使用Provider來指定。 可以直接在 TimeModule 添加如下方法: 1 ???????????????@Provides 2????????AndroidTimeRead?getAndroidTimeRead(ITimeRepository?rep){
3???????????????? return? new?AndroidTimeRead(rep);
4????????} 主要是通過@Provides。 ?除此以外還可以通過實現(xiàn) Provider<T> 接口實現(xiàn)。 ?1? public? class?AndroidTimeReadProvider? implements?Provider<AndroidTimeRead>?{
?2?
?3?????????@Inject
?4????????ITimeRepository?rep;
?5?
?6?????????@Override
?7????????? public?AndroidTimeRead?get()?{
?8?
?9???????????????? return? new?AndroidTimeRead(?rep?);
10????????}
11?
12?} 對應(yīng)的在 Module添加 AndroidTimeRead的Bind ? ? ? ?1 ????@Override ?2????????? public? void?configure(Binder?binder)?{
?3???????????????? // 順序無關(guān),在具體的Activity中?被創(chuàng)建
?4? ???????????????binder
?5??????????.bind(ITimeService.? class?)
?6??????????.to(AndroidTimeRead.? class?);
?7??????????? // .in(Singleton.class); // 單件
?8? ????????
?9??????????binder.bind(ITimeRepository.? class?)
10??????????.to(TimeRepository.? class?);
11?????????
12??????????binder.bind(AndroidTimeRead.? class?)
13??????????.toProvider(AndroidTimeReadProvider.? class?);
14?
15????????} ? ? ??
引入注入框架需要的思考:
1、對象的生命周期如何控制:單例或 每次創(chuàng)建新對象? 2、框架的執(zhí)行效率3、其他可選擇的框架如 dagger?
轉(zhuǎn)載于:https://my.oschina.net/yolinfeng/blog/464323
總結(jié)
以上是生活随笔為你收集整理的使用Roboguice依赖注入规划Android项目的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: maven学习(上)- 基本入门用法
- 下一篇: Android典型界面设计(3)——访网