使用Roboguice依赖注入规划Android项目
关于依赖注入
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提供服务。
1publicclassAndroidTimeRead{
2
3 public staticTimeViewModelshowTime(){ 4TimeViewModelmodel= newTimeViewModel(); 5model.setTime(String.valueOf(System.currentTimeMillis())); 6 returnmodel; 7} 8 9} 10 11 public classMainActivity extendsActivity{ 12 13 privateTextViewtxtShowTime; 14 privateButtonbtnShow; 15 16@Override 17 protected voidonCreate(BundlesavedInstanceState){ 18 super.onCreate(savedInstanceState); 19setContentView(R.layout.activity_main); 20 21txtShowTime=(TextView)findViewById(R.id.txtShowTime); 22btnShow=(Button)findViewById(R.id.btnShow); 23btnShow.setOnClickListener( newView.OnClickListener(){ 24 25@Override 26 public voidonClick(Viewv){ 27TimeViewModelviewModel=AndroidTimeRead.showTime(); 28txtShowTime.setText(viewModel.getTime()); 29} 30}); 31 32} 33 34}
代码很简单,也实现了我们的基本需要(如果产品到此为止的话)。但有两个明显的缺点,如果项目中大部分都是用了静态,那么面向OO的各种设计也就无法使用了。
另一个问题是:当你想对MainActivity 进行单元测试,你会发现非常困难,AndroidTimeRead 必须被包含进来,如果它还引用了其他的组件(如Db 或 net),那么这些组件也必须包含入内。静态类型因为一直在内存中,如果它引用了其他类型,则被引用的对象CG无法回收。 改进
这里我们将AndroidTimeRead 进行一些改进去掉令人讨厌的静态,
将AndroidTimeRead 改成单例。
1
public
classAndroidTimeRead{
2 3 private static classInstaceHolder{ 4 public staticAndroidTimeReadinstance= newAndroidTimeRead(); 5} 6 7 public staticAndroidTimeReadgetInstance(){ 8 returnInstaceHolder.instance; 9} 10 11 privateAndroidTimeRead(){} 12 13 publicTimeViewModelshowTime(){ 14TimeViewModelmodel= newTimeViewModel(); 15model.setTime(String.valueOf(System.currentTimeMillis())); 16 returnmodel; 17} 18 19}
MainActivitry 进行对应的
1TimeViewModelviewModel=AndroidTimeRead.getInstance().showTime();调用修改 这里去掉了静态的方式,可是却没有解除直接依赖实现的问题。 关注行为
设计程序时,我们应该更加关注行为而非数据,简单的理解是尽可能面向接口编程。在这里例子中主要的行为就是showTime.
因此我们定义一个接口
为MainActivity 提供所需要的行为(即提供给用户的服务)。
1
public
interfaceITimeService{
2TimeViewModelshowTime(); 3}
MainActivity 上的修改:
1privateITimeServicetimeService;
2
//
提供注入点
3 public voidsetTimeService(ITimeServicetimeService){ 4 this.timeService=timeService; 5} 6 7@Override 8 protected voidonCreate(BundlesavedInstanceState){ 9 super.onCreate(savedInstanceState); 10setContentView(R.layout.activity_main); 11 12txtShowTime=(TextView)findViewById(R.id.txtShowTime); 13btnShow=(Button)findViewById(R.id.btnShow); 14btnShow.setOnClickListener( newView.OnClickListener(){ 15 16@Override 17 public voidonClick(Viewv){ 18TimeViewModelviewModel=timeService.showTime(); 19txtShowTime.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。
ITimeRepository 的实现:
public
classTimeRepository
implementsITimeRepository{
@Override publicTimeModelquery(){ TimeModelmodel= newTimeModel(); model.CurrentTime=System.currentTimeMillis(); returnmodel; } }
将 AndroidTimeRead 修改,让其从 ITimeRepository 中获取时间:
public
classAndroidTimeRead
implementsITimeService{
privateITimeRepositoryrep; publicAndroidTimeRead(ITimeRepositoryrep){ this.rep=rep; } publicTimeViewModelshowTime(){ TimeViewModelmodel= newTimeViewModel(); model.setTime("现在的时间是"+String.valueOf(rep.query())); returnmodel; } }
可以发现,这里AndroidTimeRead 也是依赖于 ITimeRepository接口的,并且通过构造函数,提供了注入口。
新的时间获取方式的修改,并没有要求MainActivity 函数做任何修改。如果是直接使用AndroidTimeRead,则需要变更MainActivity。
引入Roboguice 应该放在哪里?
上面的代码都是与getTime() 业务相关的,而Roboguice 却是属于系统支持类。一个真正的项目中通常会包含不少这样的组件如:日志、行为打点等等。这里组件较明显的特征是与业务的关系度不大,甚至直接移除也不会影响功能的正常使用。
对于这些组件,我通常会以一种脚手架的设计方式,将它们组织起来,并为其提供系统接入点。
命名一个Infrastructure包,将需要的基础设施放置在此。 引入RoboActivity
将MainActivity 的父类修改为 RoboActivity,为View添加InjectView注入
1
public
classMainActivity
extendsRoboActivity{
2 3@InjectView(R.id.txtShowTime) 4 privateTextViewtxtShowTime; 5@InjectView(R.id.btnShow) 6 privateButtonbtnShow; 7 8@Inject 9 privateITimeServicetimeService; 10 // 提供注入点 11 public voidsetTimeService(ITimeServicetimeService){ 12 this.timeService=timeService; 13} 14 15@Override 16 protected voidonCreate(BundlesavedInstanceState){ 17 super.onCreate(savedInstanceState); 18setContentView(R.layout.activity_main); 19 20btnShow.setOnClickListener( newView.OnClickListener(){ 21 22@Override 23 public voidonClick(Viewv){ 24TimeViewModelviewModel=timeService.showTime(); 25txtShowTime.setText(viewModel.getTime()); 26} 27}); 28 29}
由于 ITimeService 是我们自定义的服务,需要为其指定实现。
创建RoboApplication 并继承自android 的Application同时修改AndroidManifest 里的配置。创建一个TimeModule类实现Module接口。
1
public
classRoboApplication
extendsApplication{
2 3@Override 4 public voidonCreate(){ 5 super.onCreate(); 6RoboGuice.setBaseApplicationInjector( this,RoboGuice.DEFAULT_STAGE, 7RoboGuice.newDefaultRoboModule( this),newTimeModule()); 8} 9}
setBaseApplicationInjector 最后一个参数是变参可以注册多个Module
1
public
classTimeModule
implementsModule{
2 3@Override 4 public voidconfigure(Binderbinder){ 5 // 顺序无关,在具体的Activity中被创建 6 binder 7.bind(ITimeService. class) 8.to(AndroidTimeRead. class); 9 // .in(Singleton.class); // 单件 10 11binder.bind(ITimeRepository. class) 12.to(TimeRepository. class); 13 14} 15 16}
binder 用于指定接口和具体的实现的映射,
这里仍旧依赖一个问题,就是 AndroidTimeRead 对 ITimeRepository 的依赖需要指定。
这种复杂类型需要使用Provider来指定。
可以直接在 TimeModule 添加如下方法:
1
@Provides
2AndroidTimeReadgetAndroidTimeRead(ITimeRepositoryrep){
3 return newAndroidTimeRead(rep); 4}
主要是通过@Provides。 除此以外还可以通过实现
Provider<T> 接口实现。
1
public
classAndroidTimeReadProvider
implementsProvider<AndroidTimeRead>{
2 3@Inject 4ITimeRepositoryrep; 5 6@Override 7 publicAndroidTimeReadget(){ 8 9 return newAndroidTimeRead(rep); 10} 11 12}
对应的在 Module添加 AndroidTimeRead的Bind
1
@Override
2
public
voidconfigure(Binderbinder){
3 // 顺序无关,在具体的Activity中被创建 4 binder 5.bind(ITimeService. class) 6.to(AndroidTimeRead. class); 7 // .in(Singleton.class); // 单件 8 9binder.bind(ITimeRepository. class) 10.to(TimeRepository. class); 11 12binder.bind(AndroidTimeRead. class) 13.toProvider(AndroidTimeReadProvider. class); 14 15} 引入注入框架需要的思考:
1、对象的生命周期如何控制:单例或 每次创建新对象?
2、框架的执行效率
3、其他可选择的框架如 dagger (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |