Dependency Injection( 依賴注入)能夠很好的幫助咱們分離模塊,下降耦合、提升可測試性。(PS:Roboguice 只是一個工具,依賴注入更多的是一種思想)
一般博主開發項目時喜歡以Activity 、Service 等組件做爲頂級層入口,輔以各種接口做爲業務服務。Activity 主要負責維護界面相關的東西,及提供功能所須要的上下文環境,引入功能實現須要的接口。git
這些接口的實例經過Roboguice進行注入。(固然你也能夠徹底不使用Roboguice,但仍是建議保留接口注入的設計)。github
關於Roboguice
Roboguice 是基於guice-noaop 的android注入框架,
項目地址:https://github.com/roboguice/roboguice .利用Roboguice能夠較輕鬆的注入各類服務,它默認提供了各類android相關的注入如: injectView ,injectResource 等。web
遺憾的是這裏將不對Roboguice的使用詳細講解。想了解 Roboguice 的讀者能夠查看官網的Wiki 或參考:http://www.imobilebbs.com/wordpress/archives/2480編程
須要注意的是Roboguice 分爲 1.1 版和2.0及以上版本,這兩個版本並不兼容,通常使用2.0便可,更簡單方便。
下載須要的包
項目建立
建立android項目命名爲:RoboguicePractice ,並添加Roboguice 相關包。
基本功能
項目僅包含一個Activity,界面上包含一個TextView和Button.點擊Button 可查看當前時間。框架
爲了使Demo更具表明性, Activity 須要引用 ITimeService 的接口來獲取時間。ITimeService 接口的具體實現類AndroidTimeReand則依賴於ITimeRepository(數據源),這樣就從邏輯上劃分出一個基本的三層。
一般我喜歡把數據相關的模塊(db、sharepreferene、net、cache等)歸類到Repository中,對上層而言就造成一個數據來源接口。ide
注意:沒有哪種設計是萬能,須要根據最實際的狀況,不斷的進行權衡,最終選擇較合適的系統設計,而且要作好睡着系統的成長鬚要變動設計的準備。wordpress
例若有的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。
1
public
class TimeModel {
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 }
引入注入框架須要的思考:
一、對象的生命週期如何控制:單例或 每次建立新對象?
二、框架的執行效率