什么是依赖注入?
依赖是指一个对象持有其他对象的引用。依赖注入则是将这些依赖对象传递给被依赖对象,而不是被依赖对象自己创建这些对象。
public class MyClass{ private AnotherClass mAnotherObject; public MyClass(){ mAnotherObject = new AnotherClass(); } }
通过传递对象的方式,所传递对象的更改不会影响代码。
public class MyClass{ private MyInterface mAnotherObject; public MyClass(MyInterface anotherObject){ mAnotherObject = anotherObject; } }
依赖注入可以简化代码编写,并提供一个可适配的环境,方便进行单元测试以及功能模块的配置。
开发中可能会遇到这样的麻烦。
我们将通过一个例子来理解依赖注入的应用场景:某Android应用需要一个列表来显示用户的好友。
public class FriendListFragment{ private FriendListAPI mFriendListAPI; ...... public FriendListFragment(){ mFriendListAPI = new FriendListAPI(); } private void getFriendList(){ mFriendListAPI.getFriendList(new Callback(){ public void onSuccess(List<User> list){ ...... } ...... }); } } public class FriendListAPI{ private OkHttpClient mHttpClient; public FriendListAPI(){ mHttpClient= new OkHttpClient(); //接下来各种Http配置 ...... } }
代码写好了,运行程序试试。可是,后台API没有准备好或者没有数据怎么办?自己添点测试数据试试吧。在FriendListFragment里面添加一个生成测试数据的方方法buildTestData(),并替换getFriendList()方法。等后台API准备好后再改回来。
我们想测试网络有延迟或错误的时候,程序是否会出现异常。这需要通过配置OkHttpClient参数来实现测试场景,于是又要更改FriendListAPI中相关HttpClient配置代码,测试完后再修改回来。
这样对代码进行多次修改,很容易出错。因此,对于多次使用的模块,我们可以通过注入的方式,将引用传入需要使用的类中,而不是自己创建。通过编写两个API,一个是直接请求后台数据,另一个则只是一些静态测试数据。需要测试的时候注入可生成测试数据的API,测试完后则切换为正式API。
public class FriendListFragment{ private FriendListAPI mFriendListAPI; ...... public FriendListFragment(FriendListAPI friendListAPI){ mFriendListAPI = friendListAPI; } } public class FriendListAPI{ private OkHttpClient mHttpClient; public FriendListAPI(HttpClient okHttpClient){ mHttpClient= okHttpClient; ...... } }
现在引入一个稍微复杂的场景,更多的Fragment需要使用FriendListAPI,我们需要在两个不同的地方进行注入,因此产生了许多重复代码。
因此,我们需要一个容器,它知道什么地方需要注入,注入什么样的对象。
Dagger解决方案。
这里简单的展示轻量级依赖注入库Dagger实现的注入。
首先定义模块:
public class MyModule{ @Provides @Singleton OkHttpClient provideOkHttpClient(){ //这里可进行各种Http配置 return new OkHttpClient(); } @Provides @Singleton FriendListAPI provideFriendListAPI(){ return newFriendListAPI(); } }
初始化模块以及依赖对象图。
public class MyApplication extends Application{ private ObjectGraph graph; @Override public void onCreate() { super.onCreate(); graph = ObjectGraph.create(getModules().toArray()); } protected List<Object> getModules() { return Arrays.asList( new MyModule(this)); } public void inject(Object object) { graph.inject(object); } }
最后添加注入点并进行注入。
public abstract class BaseActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((MyApplication) getApplication()).inject(this); } } public class FriendListFragment{ @Inject FriendListAPI mFriendListAPI; ...... } public class FriendListAPI{ @Inject OkHttpClient mHttpClient; ...... }
如需进行单元测试,或使用可生成测试数据的模拟API,则再编写一个模块,在初始模块和依赖对象图时替换即可。
现有的依赖注入性能?
依赖注入虽能简化代码编写,方便单元测试,可是由于当前基于反射的依赖注入框架(Guice、RoboGuice)性能并不好。原因是他们会在程序运行的时候需要扫描代码中的注解,并需要花费内存映射到内存中。
这里推荐使用Dagger是因为它使用了编译时注解,也就是说在编译代码的时候,Dagger就已经完成传统依赖注入框架在运行时所执行的任务。
什么时候需要依赖注入?
当你需要将配置数据注入到一个或多个模块时。在开发过程中前端访问后台服务器地址会分为测试服务器和正式服务器,以及各种第三方分享key和ID,依赖注入都是非常好的选择。
当需要将同一依赖注入到多个模块时。如加载图片以及图片存储管理组件(Picasso, Android-Universal-Image-Loader)。
当需要对同一依赖注入不同的实现时。为方便开发和单元测试,后台API可有正式API和模拟API,通过依赖注入方便切换环境。
当同一模块需要注入不同的配置时。
参考资料:
http://square.github.io/dagger/