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