首先介绍一下如果将Guice 和RoboGuice 的库添加到项目中。
- 下载RoboGuice和guice-2.0-no_aop.jar(not guice-3.0),或者下载
- 创建一个新Android项目,比如GuiceDemo,目标平台Android1.5以上。
- 一般可以在该项目下添加一个lib目录,将两个jar文件拷到lib目录下,然后通过: Project > Properties > Java Build Path > Libraries > Add External JARs
添加了对应guice 和roboguice库的引用之后,就可以开始编写第一个使用roboguice 的例子。
使用roboguice 的步骤:
1. 创建一个RoboApplication 的子类GuiceApplication,GuiceApplication为Appliacation的子类,因此需要修改AndroidManifest.xml,将Application 的name 指向这个类。可以参见Android简明开发教程九:创建应用程序框架
<application android:name=”GuiceApplication”
android:icon=”@drawable/icon” android:label=”@string/app_name”>
<activity android:name=”.GuiceDemo”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
</application>
2. 在这个简单的例子中,它使用的Layout 定义如下:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:id=”@+id/hello”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”@string/hello”
/>
</LinearLayout>
我们定义了一个TextView ,它的id为hello.
假定这个应用使用一个IGreetingService ,它有一个方法getGreeting() 返回一个字符串,至于IGreetingService 如何实现,GuideDemo 不需要关心。
Dependency injection 设计模式的一个核心原则为: Separate behavior from dependency resolution. 也就说将应用需要实现的功能和其所依赖的服务或其它对象分离。 对本例来说GuiceDemo只要知道它依赖于IGreetingService 服务,至于IGreetingService有谁实现GuiceDemo并不需要知道。
在Roboguice 中使用@Inject 来表示这种依赖关系。
1 | public class GuiceDemo extends RoboActivity { |
3 |
@InjectView (R.id.hello) TextView helloLabel; |
4 |
@Inject IGreetingService greetingServce; |
7 |
public void onCreate(Bundle savedInstanceState) { |
8 |
super .onCreate(savedInstanceState); |
9 |
setContentView(R.layout.main); |
10 |
helloLabel.setText(greetingServce.getGreetings()); |
- 使用RoboGuice 的Activity需要从RoboActivity派生(RoboActivity为Activity的子类).
- 使用@Inject标注greetingServce依赖于IGreetingService服务
- 使用@InjectView表示helloLabel 依赖于R.id.hello (XML)
代码中没有创建greetingServce 对象的代码(如 new xxx()) 和为helloLabel 赋值的代码。这些值都可以Roboguice 自动创建和赋值注入(Inject)到变量中。
为了说明问题,我们在代码中添加两个对getGreetings的实现,一个为HelloWorld, 一个为HelloChina:
1 | public class HelloChina implements IGreetingService{ |
4 |
public String getGreetings() { |
10 | public class HelloWorld implements IGreetingService{ |
13 |
public String getGreetings() { |
3. 到这里,你可能有些困惑,RoboGuice怎么知道使用那个类(HelloWorld或是HelloChina)为GuiceDemo中的greetingServce 赋值呢?这是通过在Module 中定义binding 来实现的。
在项目中添加一个GreetingModule (从AbstractAndroidModule 派生)重载configure方法:
1 | public class GreetingModule extends AbstractAndroidModule{ |
4 | protected void configure() { |
5 | bind(IGreetingService. class ).to(HelloWorld. class ); |
将IGreetingService 绑定到HelloWorld 类。
然后在GuiceApplication 的addApplicationModules 添加上述模块:
1 | public class GuiceApplication extends RoboApplication { |
3 |
protected void addApplicationModules(List<Module> modules) { |
4 |
modules.add( new GreetingModule()); |
可以将GreetingModule 绑定改为HelloChina ,对比一下:
通过改变binding ,GuiceDemo 显示了不同的结果,GuiceDemo不依赖于具体的实现,可以非常方便的改变接口的实现而无需更改GuiceDemo的代码。大大降低了类于类之间的耦合性。
后面将逐个介绍Guice和RoboGuice支持的Binding类型和用法(Guice)以及与android 平台相关的Dependency injection (RoboGuice)
本例下载(含 roboguice 库)
一个应用中类于类之间的依赖关系可能非常复杂,创建于个类实例,需要先创建类所依赖的类的示例,而创建所依赖类的实例,这些类又可能依赖其它类,以此类推。因此在创建一个类实例时,你正在需要创建的是一个对象图对象(Object Graph)。
手工创建Object Graph 是一个非常繁琐而且容易出错的过程,并且很难对代码进行测试,而Guice或Roboguice可以帮助你创建Object Graph,所要做的工作是配置类和类之间的依赖关系。
模块(Modules) 是Guice 构造Object Graph 的基本构造块,Guice中构造object Graph 的工作有被称为”Injector”的类来完成。
Guice在模块为AbstractMoudule 的子类,而RoboGuice在模块为AbstractAndroidModule的子类。RoboGuice利用 Injector 来创建所依赖的对象,而Injector 为参照Module 中定义的Bindings来构造类于类之间的关系图。
打个比方,如果你熟悉make file 或是其它Build 系统(如 wix) 。你使用makefile 定义好需编译的对象所依赖的源码文件,这些源码由可能依赖其它库或头文件等。makefile 定义的这些依赖关系对应到Roboguice 中为模块中定义的bindings 。
使用make 编译某个目标程序 (Target), make 会查看makefile 中的依赖关系,依次先编译被依赖的对象直到最终编译Target。对应到Roboguide(Guice)为Injector 创建某个对象,它会根据定义的Bindings 首先创建那些被依赖的对象,直到创建所需对象。
在HelloWorld例子中,我们没有看到Injector的直接使用,这是因为RoboGuice 替我们调用了Injector来创建IGreetingService对象。
如果在某些情况下,如果你想直接使用Injector ,可以使用RoboActivity 的getInjector().
比如修改GuiceDemo,去掉@Inject IGreetingService greetingServce 而使用Injector的getInstance 来创建IGreetingService 实例。
1 | public class GuiceDemo extends RoboActivity { |
3 |
@InjectView (R.id.hello) TextView helloLabel; |
7 |
public void onCreate(Bundle savedInstanceState) { |
8 |
super .onCreate(savedInstanceState); |
9 |
setContentView(R.layout.main); |
11 |
Injector injector=getInjector(); |
12 |
IGreetingService greetingServce |
13 |
=injector.getInstance(IGreetingService. class ); |
14 |
helloLabel.setText(greetingServce.getGreetings()); |
Module中的还是绑定到HelloChina.
1 | public class GreetingModule extends AbstractAndroidModule{ |
4 |
protected void configure() { |
6 |
bind(IGreetingService. class ).to(HelloChina. class ); |
Injector 的工作就是构造Object Graph,当你调用getInstance 来构造某个类型的对象时,Injector 会自动根据类之间的依赖关系创建所需类的实例。
定义类之间的依赖关系的方法是通过扩展AbstractAndroidModule,重载其configure方法。在configure方法中定义各种Bindings。这些方法同时也做类型检测,如果使用的类型不正确,编译器将给出错误。
绑定Bindings 可以有下面几种类型:
- Linked bindings
- instance bindings
- @provider methods
- provider bindings
- constructor bindings
- untargetted bindings
- built-in bindings
- just-in-time bindings
- providers 等
后面就逐个介绍这些bindings ,这些bindings 是通用的和Android平台相关性不大,可以同时用于Java EE ,Java SE 平台,RoboGuice 提供了于Android平台相关的dependency injector ,后面也有详细介绍。
Roboguice 中最常用的一种绑定为Linked Bindings,将某个类型映射到其实现。这里我们使用引路蜂二维图形库中的类为例,引路蜂二维图形库的使用可以参见Android简明开发教程八:引路蜂二维图形绘制实例功能定义。
使用下面几个类 IShape, Rectangle, MyRectangle, MySquare, 其继承关系如下图所示:
下面代码将IShape 映射到MyRectangle
1 | public class Graphics2DModule extends AbstractAndroidModule{ |
4 |
protected void configure() { |
6 |
bind(IShape. class ).to(MyRectangle. class ); |
此时,如果使用injector.getInstance(IShape.class) 或是injector 碰到依赖于IShape地方时,它将使用MyRectangle。可以将类型映射到它任意子类或是实现了该类型接口的所有类。也可以将一个实类(非接口)映射到其子类,如
bind(MyRectangle.class).to(MySquare.class);
下面例子使用@Inject 应用IShape.
1 | public class LinkedBindingsDemo extends Graphics2DActivity{ |
5 | protected void drawImage(){ |
8 | * The semi-opaque blue color in |
9 | * the ARGB space (alpha is 0x78) |
11 | Color blueColor = new Color( 0x780000ff , true ); |
13 | * The semi-opaque yellow color in the |
14 | * ARGB space ( alpha is 0x78) |
16 | Color yellowColor = new Color( 0x78ffff00 , true ); |
21 | int dashArray[] = { 20 , 8 }; |
22 | graphics2D.clear(Color.WHITE); |
24 | Pen pen= new Pen(yellowColor, 10 ,Pen.CAP_BUTT, |
25 | Pen.JOIN_MITER,dashArray, 0 ); |
26 | SolidBrush brush= new SolidBrush(blueColor); |
27 | graphics2D.setPenAndBrush(pen,brush); |
28 | graphics2D.fill( null ,shape); |
29 | graphics2D.draw( null ,shape); |
使用bind(IShape.class).to(MyRectangle.class),为了简化问题,这里定义了MyRectangle和MySquare都带有一个不带参数的构造函数,注入具有带参数的构造函数类用法在后面有介绍。
1 | public class MyRectangle extends Rectangle{ |
6 |
public MyRectangle( int width, int height){ |
7 |
super ( 50 , 50 ,width,height); |
11 | public class MySquare extends MyRectangle { |
17 |
public MySquare( int width){ |
Linked bindings 允许链接,例如
1 | public class Graphics2DModule extends AbstractAndroidModule{ |
4 |
protected void configure() { |
5 |
bind(IShape. class ).to(MyRectangle. class ); |
6 |
bind(MyRectangle. class ).to(MySquare. class ); |
此时当需要IShape 时,Injector返回MySquare 的实例, IShape->MyRectangle->MySquare
本例下载
有些情况需要将同一类型映射到不同的类实现,还是使用绘图的例子.
IShape, Rectangle, MyRectangle, MySquare,有如下继承关系:
我们可能需要将IShape 同时映射到MyRectangle 和MySquare ,这时可以使用Binding Annotation 来实现。 这时使用类型和annotation (标注)可以唯一确定一个Binding。Type 和annotation 对称为Key(键)。
为了同时使用MyRectangle和MySequare,我们定义两个annotation,如下
1 | import com.google.inject.BindingAnnotation; |
2 | import java.lang.annotation.Target; |
3 | import java.lang.annotation.Retention; |
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; |
5 | import static java.lang.annotation.ElementType.PARAMETER; |
6 | import static java.lang.annotation.ElementType.FIELD; |
7 | import static java.lang.annotation.ElementType.METHOD; |
11 | @Target ({ FIELD, PARAMETER, METHOD }) |
13 | public @interface Rectangle { |
18 | @Target ({ FIELD, PARAMETER, METHOD }) |
20 | public @interface Square { |
定义了两个标注 @Rectangle, @Square, 至于@BindingAnnotation,@Target,@Retention你并不需要详细了解,有兴趣的可以参见Java Annotation tutorial .
简单的说明如下:
- @BindingAnnotation 通知这是一个Binding Annotation,如果将多个个标注应用到同一个元素时,Guice会报错。
- @Target({FIELD, PARAMETER, METHOD}) 表示这个标注可以应用到类成员变量,函数的参数或时方法。
- @Retention(RUNTIME) 表示这个标注在程序运行时可以使用Reflection读取。
创建一个BindingAnnotationsDemo 用来绘制两个图形:
1 | public class BindingAnnotationsDemo extends Graphics2DActivity{ |
3 |
@Inject @Rectangle IShape shape1; |
4 |
@Inject @Square IShape shape2; |
6 |
protected void drawImage(){ |
9 |
* The semi-opaque blue color in |
10 |
* the ARGB space (alpha is 0x78) |
12 |
Color blueColor = new Color( 0x780000ff , true ); |
14 |
* The semi-opaque green color in the ARGB space (alpha is 0x78) |
16 |
Color greenColor = new Color( 0x7800ff00 , true ); |
18 |
graphics2D.clear(Color.WHITE); |
21 |
SolidBrush brush= new SolidBrush(blueColor); |
23 |
graphics2D.fill(brush,shape1); |
24 |
AffineTransform at = new AffineTransform(); |
26 |
graphics2D.setAffineTransform(at); |
27 |
brush= new SolidBrush(greenColor); |
28 |
graphics2D.fill(brush,shape2); |
使用标注将shape1 绑定到MyRectangle, shape2绑定到MySquare,对应的Module 定义如下:
1 | public class Graphics2DModule extends AbstractAndroidModule{ |
4 |
protected void configure() { |
7 |
.annotatedWith(Rectangle. class ) |
8 |
.to(MyRectangle. class ); |
11 |
.annotatedWith(Square. class ) |
Inject 可以应用到Field (成员变量),Parameter (参数)或Method(方法),前面的例子都是应用到Field上,如果应用到参数可以有如下形式:
2 | public IShape getShape( @Rectangle IShape shape){ |
如果你不想自定义Annotation,可以使用Guice自带的@Name标注来解决同一类型绑定到不同实现的问题。
修改上面代码:
4 | @Inject @Named ( "Rectangle" ) IShape shape1; |
5 | @Inject @Named ( "Square" ) IShape shape2; |
修改绑定如下:
10 |
.annotatedWith(Names.named( "Rectangle" )) |
11 |
.to(MyRectangle. class ); |
13 |
.annotatedWith(Names.named( "Square" )) |
这种方法简单,但编译器无法检测字符串,比如将”Square”错写为”Sqare”,编译器无法查出这个错误,此时到运行时才可能发现 shape2 无法注入,因此建议尽量少用Named.
本例下载
我们在前面例子Android RoboGuice 使用指南(4):Linked Bindings 时为简单起见,定义MyRectangle和MySquare时为它们定义了一个不带参数的构造函数,如MyRectangle的如下:
1 | public class MyRectangle extends Rectangle{ |
5 |
public MyRectangle( int width, int height){ |
6 |
super ( 50 , 50 ,width,height); |
实际上可以不需要这个不带参数的构造函数,可以使用Instance Bindings ,Instance Bindings可以将一个类型绑定到一个特定的实例对象,通常用于一个本身不依赖其它类的类型,如各种基本类型,比如:
2 |
.annotatedWith(Names.named( "JDBC URL" )) |
3 |
.toInstance( "jdbc:mysql://localhost/pizza" ); |
5 |
.annotatedWith(Names.named( "login timeout seconds" )) |
修改MyRectangle和MySquare的定义如下:
1 | public class MySquare extends MyRectangle { |
3 |
public MySquare( @Named ( "width" ) int width){ |
8 | public class MyRectangle extends Rectangle{ |
11 |
public MyRectangle( @Named ( "width" ) int width, |
12 |
@Named ( "height" ) int height){ |
13 |
super ( 50 , 50 ,width,height); |
去掉了无参数的构造函数,可以将标注为@Named(“width”)的int 类型绑定到100,添加下面绑定:
2 |
.annotatedWith(Names.named( "width" )) |
5 |
.annotatedWith(Names.named( "height" )) |
运行这个例子,可以得到和前面例子同样的结果。此时使用Injector 构造一个MyRectangle 实例时,Injector自动选用带参数的那个构造函数,使用100,120为width和height注入参数,返回一个MyRectangle对象到需要引用的地方。
尽管可以使用Instance Bindings将一个类型映射到一个复杂类型的类实例,但RoboGuice不建议将Instance Bindings应用到复杂类型的实例,因为这样会使应用程序启动变慢。
正确的方法是使用@Provides 方法,将在下面介绍。
注:GuiceDemo 中的例子没用使用列表的方法来显示所有示例,如需运行所需示例,可以通过Run Configuration->设置Launch 的Activity: