使用Roboguice依賴注入規劃Android項目

前言

很久沒寫博客了,罪過啊~記事本里累積了很多東西,整理整理放上來。 android

關於依賴注入 

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        }
      

引入注入框架須要的思考:

一、對象的生命週期如何控制:單例或 每次建立新對象?
二、框架的執行效率

三、其餘可選擇的框架如 dagger 

相關文章
相關標籤/搜索