热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

关于依赖注入 DependencyInjection(依赖注入)可以很好的帮助我们分离模块,降低耦合、提高可测试性。(PS:Roboguice只是一个工具,依赖注入更多的是一种
关于依赖注入 
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提供服务。

  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 方式,并使用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 接口实现。
,
 1 public class AndroidTimeReadProvider implements Provider {
 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

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

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


推荐阅读
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 解决VS写C#项目导入MySQL数据源报错“You have a usable connection already”问题的正确方法
    本文介绍了在VS写C#项目导入MySQL数据源时出现报错“You have a usable connection already”的问题,并给出了正确的解决方法。详细描述了问题的出现情况和报错信息,并提供了解决该问题的步骤和注意事项。 ... [详细]
  • 本文详细介绍了MySQL表分区的创建、增加和删除方法,包括查看分区数据量和全库数据量的方法。欢迎大家阅读并给予点评。 ... [详细]
author-avatar
king_her灬o1
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有