依赖注入(DI)是一种设计模式,允许在运行时或编译时移除或改变硬编码的依赖性。使用依赖注入库可以减少编码量、把精力专注在更有价值的地方、降低维护成本。
Android程序通常使用注解(Annotation,例如
@Click
)实现“声明式编程”和依赖注入。注:“声明式编程”告诉机器在什么地方做什么事(Where to do What),让机器去决定如何干。而传统的“命令式编程”则需要告诉机器如何干(How to do What)。 声明式编程可以让程序员从具体的编码实现中解脱,减少编码量,把精力专注在更有价值的地方。
常见的依赖注入库
Dagger:实现了类构造注入(POJO注入),不支持函数注入。功能太简单,不建议使用。
ButterKnife:实现了视图注入、资源注入、事件绑定,使用自动生成的内部类实现注入。
可在 Activity、Fragment 或 Adapter 类中注入视图:使用
@Bind
注解将每个视图字段与其资源ID关联,不用写findViewById和类型转换代码。 例如:@Bind(R.id.title) TextView title;
支持视图数组绑定,例如:
@Bind({ R.id.first_name, R.id.middle_name, R.id.last_name }) List<EditText> nameViews;
自动绑定事件处理函数:在事件处理函数上添加注解(
@OnClick
、@OnItemSelected
等),不用写事件处理的匿名侦听类和事件绑定代码。例如:@OnClick(R.id.submit) public void submit(View view) { // TODO submit data to server... }
资源注入:使用
@BindColor
、@BindDimen
、@BindDrawable
、@BindString
等注解将资源ID与对象绑定。 例如:@BindDrawable(R.drawable.graphic) Drawable graphic;
需要调用 ButterKnife.inject(activity) 在运行时动态绑定。 具体绑定关系和事件处理匿名侦听类是在预编译阶段自动生成的匿名类中实现的。
所要绑定的视图字段的可访问性为package或public级别。
在Eclipse中只需要一个jar包,用于自动生成依赖注入类。运行期不需要额外的jar包。
在 Android Studio 开发环境中有自动添加注解的插件(Zelezny),可自动插入所有视图注解和视图字段。
RoboGuice:实现了视图注入、资源注入、系统服务注入、POJO注入、测试用例支持,基于 Google Guice 库实现。
视图注入、资源注入、系统服务注入类似于 ButterKnife。例如:
@InjectView(R.id.textView1) TextView textView1;
@InjectResource(R.string.app_name) String name;
@Inject LayoutInflater inflater;
POJO注入(类构造注入):省略构造函数,自动创建成员对象。例如
@Inject Foo foo;
没有事件绑定功能。如需要可使用 ButterKnife 或 AndroidAnnotations。
继承限制:你得在 RoboActivity 和 RoboFragment 中继承其中一个才能在Activity事件或Fragment中使用 RoboGuice 。
安装:需要 roboguice、guice、jsr 等三个jar包。
AndroidAnnotations:实现了依赖注入(视图注入、事件绑定、资源注入、系统服务注入、值传递注入)、简化的线程模型、REST客户端。
依赖注入和事件绑定:与 ButterKnife 类似,但需要在 Activity 等相关类前使用
@EActivitie
等注解以便使用依赖注入功能, 不需要像 ButterKnife 那样写额外的 inject() 绑定语句。下面的例子使用了
@EActivity
、@ViewById
、@Click
、@AfterViews
等注解,简化了以往 onCreate 的初始化代码。@EActivity(R.layout.activity_main) // 代替setContentView,@EActivity生成子类 public class MainActivity extends Activity { private IViewHelper mHelper = ViewFactory.createHelper(); @ViewById(R.id.lineWidthBar) SeekBar mLineWidthBar; // 视图注入 @Click void line_btn() { // 事件绑定,不指定ID就用名称查找资源 mHelper.setCommand("line"); } @Click(R.id.rect_btn) void onRectClick() { // 指定了ID就可以用别的函数名 mHelper.setCommand("rect"); } @SeekBarProgressChange(R.id.lineWidthBar) void onProgressChanged(SeekBar seekBar, int progress) { mHelper.setStrokeWidth(progress); } @SeekBarTouchStart(R.id.lineWidthBar) void onStartTrackingTouch(SeekBar seekBar) { mHelper.setContextEditing(true); } @SeekBarTouchStop(R.id.lineWidthBar) void onStopTrackingTouch(SeekBar seekBar) { mHelper.setContextEditing(false); } @AfterViews // 视图已注入后 void init() { // Do more after views injected } }
简化的线程模型:用简单函数调用的方式使用UI线程和后台线程。例如:
@Background // 开启新线程后台运行,注意不要引用UI控件,而且返回值类型一定是void void someBackgroundWork(String name, long timeToDoSomeLongComputation) { try { TimeUnit.SECONDS.sleep(timeToDoSomeLongComputation); } catch (InterruptedException e) { } updateUi(String.format(helloFormat, name), androidColor); // 直接调用就切到UI线程了 showNotificationsDelayed(); } @UiThread // UI线程,可以指定多个参数 void updateUi(String message, int color) { setProgressBarIndeterminateVisibility(false); textView.setText(message); // UI线程中可访问UI控件 textView.setTextColor(color); } @UiThread(delay=2000) // 可以设置延时毫秒 void showNotificationsDelayed() { }
使用自动生成的子类实现注入,子类名为原类名加上一个下划线。例如在 AndroidManifest.xml 中使用的Activity改为
android:name=".MainActivity_"
。在Eclipse中需要一个编译期jar包(androidannotations-X.Y.Z.jar),用于预编译阶段自动生成依赖注入的子类。 运行期需要另一个jar包(androidannotations-api-X.Y.Z.jar),约120KB。
因为 AndroidAnnotations (简称AA)功能较多,可大幅减少代码量,易于理解和维护,所以本文推荐使用AA。
本文接下来介绍在 ADT Bundle(Eclipse)开发环境下使用 AA 的方法。
插件安装和工程配置
安装 Eclipse Java Development Tools 插件,以便启用 Annotation Processing
下载并解压 AA的jar包
androidannotations-X.Y.Z.jar 用于预编译自动生成依赖注入代码文件,需要复制到项目中的某个目录(例如新建的 compile-libs 目录中,外部目录也行),不能是 libs 目录。
androidannotations-api-X.Y.Z.jar 用于注解和程序运行,需要放到项目的 libs 目录。
为了查看源码,建议也放到 compile-libs 目录并 Add to Build Path,然后设置其 Java Source Attachment 为 androidannotations-api-X.Y.Z-sources.jar。 该jar文件根据自己的需要放在项目的 compile-libs 目录或外部目录,对于多人团队建议放在项目内。
设置程序工程的属性,启用 Annotation Processing
选中程序工程,打开 Project | Properties。
查看 Java Compiler ,确定 Compiler compliance level 必须是 1.6 。
查看 Java Compiler \ Annontation Processing ,选中 Enabled annontation processing 。 在Mac电脑上建议将自动生成的代码目录建议改为 gen,以避免 Eclipse 找不到代码目录(以点号开头的目录自动隐藏)。
查看 Java Compiler \ Annontation Process \ Factory Path ,点击 Add JARs,把 androidannotations-X.Y.Z.jar 加进来。
点击确定后将弹出个对话框,提示 annotation 设置变更,要求 rebuild project ,确定即可重建项目。
在工程中使用 AA 的注解
上面介绍AA时已经举例,这里不再重复,进一步阅读可参考官方Wiki文档。
Cookbook 页面中的“Enhanced components”(例如
@EActivity
)实质上会在预编译时生成对应的子类,在子类中实现注解注入功能。注意:子类的名称就是在原来的类之后加了一个下划线,例如
MyActivity_
, 需要在AndroidManifest.xml中改变原来的Activity类名,使用相关类时也要做相应替换。如果不方便在线浏览官方Wiki文档,可以下载Wiki文档:
git clone https://github.com/excilys/androidannotations.wiki.git
注:本文首发于新博客(rhcad.com)