使用Roboguice依赖注入规划Android项目
2013-10-13 15:38
489 查看
前言
好久没写博客了,罪过啊~记事本里累积了不少东西,整理整理放上来。关于依赖注入
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即可,更简单方便。 [b]下载需要的包[/b] 可参考: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 }
public class MainActivity extends Activity {
private TextView txtShowTime ;
private Button btnShow ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout. activity_main);
txtShowTime = (TextView) findViewById(R.id.txtShowTime);
btnShow = (Button) findViewById(R.id. btnShow);
btnShow.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
TimeViewModel viewModel = AndroidTimeRead. showTime();
txtShowTime.setText(viewModel.getTime());
}
});
}
} 代码很简单,也实现了我们的基本需要(如果产品到此为止的话)。但有两个明显的缺点,如果项目中大部分都是用了静态,那么面向OO的各种设计也就无法使用了。另一个问题是:当你想对MainActivity 进行单元测试,你会发现非常困难,AndroidTimeRead 必须被包含进来,如果它还引用了其他的组件(如Db 或 net),那么这些组件也必须包含入内。 静态类型因为一直在内存中,如果它引用了其他类型,则被引用的对象CG无法回收。
改进
这里我们将AndroidTimeRead 进行一些改进去掉令人讨厌的静态,将AndroidTimeRead 改成单例。[align=left] 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 }
private AndroidTimeRead(){}
public TimeViewModel showTime() {
TimeViewModel model = new TimeViewModel();
model.setTime(String. valueOf(System.currentTimeMillis ()));
return model;
}
}[/align][align=left]MainActivitry 进行对应的[/align]1 TimeViewModel viewModel = AndroidTimeRead. getInstance().showTime();调用修改
[align=left] [/align]这里去掉了静态的方式,可是却没有解除直接依赖实现的问题。
关注行为
设计程序时,我们应该更加关注行为而非数据,简单的理解是尽可能面向接口编程。在这里例子中主要的行为就是showTime.因此我们定义一个接口为MainActivity 提供所需要的行为(即提供给用户的服务)。[align=left][/align]public interface ITimeService {
TimeViewModel showTime(); 3 }
MainActivity 上的修改:[align=left][/align] 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);
setContentView(R.layout. activity_main);
txtShowTime = (TextView) findViewById(R.id.txtShowTime);
btnShow = (Button) findViewById(R.id. btnShow);
btnShow.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
TimeViewModel viewModel = timeService.showTime();
txtShowTime.setText(viewModel.getTime());
}
});
}
这里 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。[align=left] public class TimeModel {
public long CurrentTime ;
}
public interface ITimeRepository {
TimeModel query();
}[/align]
ITimeRepository 的实现:[align=left]public class TimeRepository implements ITimeRepository {public long CurrentTime ;
}
public interface ITimeRepository {
TimeModel query();
}[/align]
@Override
public TimeModel query() {
TimeModel model=new TimeModel();
model.CurrentTime=System. currentTimeMillis();
return model;
}
}[/align][align=left]
[/align][align=left]将 AndroidTimeRead 修改,让其从 ITimeRepository 中获取时间:[/align][align=left]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;
}
}[/align][align=left]
[/align][align=left]可以发现,这里AndroidTimeRead 也是依赖于 ITimeRepository接口的,并且通过构造函数,提供了注入口。[/align][align=left]
[/align][align=left]新的时间获取方式的修改,并没有要求MainActivity 函数做任何修改。如果是直接使用AndroidTimeRead,则需要变更MainActivity。[/align]
引入Roboguice 应该放在哪里?
上面的代码都是与getTime() 业务相关的,而Roboguice 却是属于系统支持类。一个真正的项目中通常会包含不少这样的组件如:日志、行为打点等等。这里组件较明显的特征是与业务的关系度不大,甚至直接移除也不会影响功能的正常使用。 对于这些组件,我通常会以一种脚手架的设计方式,将它们组织起来,并为其提供系统接入点。 命名一个Infrastructure包,将需要的基础设施放置在此。引入RoboActivity
将MainActivity 的父类修改为 RoboActivity,为View添加InjectView注入[align=left] 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 ;
//提供注入点
public void setTimeService(ITimeService timeService) {
this.timeService = timeService;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout. activity_main);
btnShow.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
TimeViewModel viewModel = timeService.showTime();
txtShowTime.setText(viewModel.getTime());
}
});
}[/align][align=left]由于 ITimeService 是我们自定义的服务,需要为其指定实现。[/align][align=left]创建RoboApplication 并继承自android 的Application同时修改AndroidManifest 里的配置。创建一个TimeModule类实现Module接口。[/align][align=left] public class RoboApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
RoboGuice. setBaseApplicationInjector(this, RoboGuice. DEFAULT_STAGE,
RoboGuice. newDefaultRoboModule(this), new TimeModule());
}
}[/align][align=left]setBaseApplicationInjector 最后一个参数是变参可以注册多个Module[/align][align=left]
[/align][align=left] 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);//单件
binder.bind(ITimeRepository. class)
.to(TimeRepository. class);
}
}[/align][align=left]
[/align][align=left]binder 用于指定接口和具体的实现的映射,[/align][align=left]这里仍旧依赖一个问题,就是 AndroidTimeRead 对 ITimeRepository 的依赖需要指定。[/align][align=left]这种复杂类型需要使用Provider来指定。[/align][align=left]可以直接在 TimeModule 添加如下方法:[/align][align=left] 1 @Provides[/align] AndroidTimeRead getAndroidTimeRead(ITimeRepository rep){
return new AndroidTimeRead(rep);
}[align=left]主要是通过@Provides。 除此以外还可以通过实现Provider<T> 接口实现。[/align][align=left] 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 );
}
}[/align][align=left]对应的在 Module添加 AndroidTimeRead的Bind[/align][align=left] 1 @Override[/align] 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 )
.to(TimeRepository. class );
binder.bind(AndroidTimeRead. class )
.toProvider(AndroidTimeReadProvider. class );
}[align=left] [/align][align=left][/align]
引入注入框架需要的思考:
1、对象的生命周期如何控制:单例或 每次创建新对象?2、框架的执行效率3、其他可选择的框架如 dagger相关文章推荐
- 使用Roboguice依赖注入规划Android项目
- 使用Roboguice依赖注入规划Android项目
- Android项目使用Dagger2进行依赖注入
- 依赖注入之Android RoboGuice简单使用
- Android项目使用Dagger2进行依赖注入
- android 使用AspectJ,代码注入到依赖项目异常
- Android依赖注入类库 Butter Knife的使用
- [Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译)
- Android 依赖注入 ButterKnife 基本使用
- .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用
- Android中的依赖注入:Dagger函数库的使用(一)
- 在githu上面开源自己的android library,让项目依赖使用攻略
- Android 开源项目android-open-project工具库解析之(一) 依赖注入,图片缓存,网络相关,数据库orm工具包,Android公共库
- Android依赖注入:Dagger、RoboGuice和ButterKnife
- 减少你的代码量--Android平台依赖注入框架RoboGuice
- Android依赖注入框架:ButterKnife 8.4.0新版本导入及使用详解
- Android快速依赖注入框架Dagger2使用1
- android开发依赖注入-RoboGuice
- [Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)
- Android ButterKnife依赖注入框架简单使用