文档章节

使用Roboguice依赖注入规划Android项目

yolinfeng
 yolinfeng
发布于 2015/06/08 20:39
字数 2246
阅读 21
收藏 1

前言

好久没写博客了,罪过啊~记事本里累积了不少东西,整理整理放上来。 

关于依赖注入 

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即可,更简单方便。
     
下载需要的包

项目创建

     创建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。
 

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        }
      

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

1、对象的生命周期如何控制:单例或 每次创建新对象?
2、框架的执行效率

3、其他可选择的框架如 dagger 

本文转载自:http://www.cnblogs.com/keyindex/p/3366666.html

yolinfeng
粉丝 12
博文 196
码字总数 11946
作品 0
珠海
架构师
私信 提问
如何成为一个偷懒又高效的Android开发人员

我敢肯定你对这个标题肯定心存疑惑,但事实就是如此,这个标题完全适合Android开发人员。据我所知, Android程序员不情愿写 findViewById()、点击事件监听等重复率较高的代码。那我们如何才能...

拉偶有所依
2015/02/06
116
0
android 的Guice RoboGuice

今天看一个是实现模糊图片的项目StackBlur的时候,看到类似依赖注入的写法,觉得有点好奇,翻开osc,果然有,原来叫RoboGuice,于是乎我去把几个包下下来试试了 其实很简单,把guice-3.0-no_...

铂金小狼
2013/09/23
545
0
使用RoboGuice @InjectFrament 时报错

我在Activity里面使用RoboGuice的@InjectFragment来初始化我自定义的Fragment,但运行出错了,代码如下: public class CrimeActivity extends RoboFragmentActivity { private static final......

张源兆
2014/08/31
687
1
xUtils是否支持类似于RoboGuice框架中配置依赖关系的功能?

最近才接触一些开源框架。感觉RoboGuice框架配置依赖关系的能力十分强大,当然还没有具体使用,只是从理论上感觉以这种方式解除依赖之后的代码能够更好的进行单元测试和自动化测试。今天了解...

桃园小七
2014/06/04
207
0
依赖注入

1.依赖 如果在 Class A 中,有 Class B 的实例,则称 Class A 对 Class B 有一个依赖。例如下面类 Human 中用到一个 Father 对象,我们就说类 Human 对类 Father 有一个依赖。 仔细看这段代码...

壮壮521
2015/06/16
5
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Boot + Mybatis-Plus 集成与使用(二)

前言: 本章节介绍MyBatis-Puls的CRUD使用。在开始之前,先简单讲解下上章节关于Spring Boot是如何自动配置MyBatis-Plus。 一、自动配置 当Spring Boot应用从主方法main()启动后,首先加载S...

伴学编程
今天
7
0
用最通俗的方法讲spring [一] ──── AOP

@[TOC](用最通俗的方法讲spring [一] ──── AOP) 写这个系列的目的(可以跳过不看) 自己写这个系列的目的,是因为自己是个比较笨的人,我曾一度怀疑自己的智商不适合干编程这个行业.因为在我...

小贼贼子
今天
6
0
Flutter系列之在 macOS 上安装和配置 Flutter 开发环境

本文为Flutter开发环境在macOS下安装全过程: 一、系统配置要求 想要安装并运行 Flutter,你的开发环境需要最低满足以下要求: 操作系统:macOS(64位) 磁盘空间:700 MB(不包含 IDE 或其余...

過愙
今天
6
0
OSChina 周六乱弹 —— 早上儿子问我他是怎么来的

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @凉小生 :#今日歌曲推荐# 少点戾气,愿你和这个世界温柔以待。中岛美嘉的单曲《僕が死のうと思ったのは (曾经我也想过一了百了)》 《僕が死の...

小小编辑
今天
2.5K
16
Excption与Error包结构,OOM 你遇到过哪些情况,SOF 你遇到过哪些情况

Throwable 是 Java 中所有错误与异常的超类,Throwable 包含两个子类,Error 与 Exception 。用于指示发生了异常情况。 Java 抛出的 Throwable 可以分成三种类型。 被检查异常(checked Exc...

Garphy
今天
42
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部