Dagger2 在 Android 项目的正确使用方式【完整篇】
Dagger2的入门难度极大,我们直奔主题,先使用起来 再去思考原理。网上几乎都是Java的用法,谨慎参考。
当你看到没有使用dagger.android这个库的讲解,都是Java用的,Android如果那样用人都会累死的。。
Java用法是Android的基础,是最原始的用法,Android所有的库都是对Java用法做了优化,我们先从最基础的来讲。
很多人开发Android都不看Google的文档,总用Java那套,还真把Android手机当服务器用了,你的App不卡谁的卡??
官网说明,其实Java这种用法在Android端大有问题!查阅官网的Android用法。 如果熟悉dagger2基础,可以直接跳到后面。
赠送源码:https://github.com/yugu88/MagicWX。
《最完整的Android逆向知识体系》
集成 Dagger2 依赖
Android studio 3.2.1 如下Gradle方式引用,Java工程仅需引用如下两个库,Android也可以这么用。
// dagger 2.ximplementation 'com.google.dagger:dagger:2.17'annotationProcessor 'com.google.dagger:dagger-compiler:2.17'
(推荐) 建议使用谷歌为Android提供的 dagger.android 依赖库,需要引用完整,如下。可查阅Dagger2 在GitHub官网集成方法
// dagger 2.x // implementation 'com.google.dagger:dagger:2.17' // android部分接口也会需要这个库 annotationProcessor 'com.google.dagger:dagger-compiler:2.17'// dagger2针对Android的库,如果你使用Android的开发方式,仅依赖这三个库。 implementation 'com.google.dagger:dagger-android:2.17' implementation 'com.google.dagger:dagger-android-support:2.17' annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'// 本文前部分demo中使用了dagger 2.x的基本用法,需要引入dagger 2.x的两个Java库// 后半部分dagger只使用Android方式开发,实际的Android开发中你只需要引用Android库即可。
如果你使用的是2.2以下的低版本gradle,可以使用apt的方式引用。(Dagger2 现在都不用 apt 方式了)
最新的AndroidStudio在使用apt插件的时候已经会报warn了,但是还能用,如果你看到集成方式还是apt的,就没必要看下去了。
apt插件使用Dagger2方法如下(不建议用,已过时):
Project的 build.gradle中添加如下代码
dependencies { classpath 'com.android.tools.build:gradle:3.2.1' //添加apt插件 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' }
App的 build.gradle中添加如下代码
apply plugin: 'com.android.application'apply plugin: 'com.neenbedankt.android-apt' ...// 网上这么写的都是Java用法,Android不要使用这种apt方式,会发警告dependencies { implementation 'com.google.dagger:dagger:2.17' apt 'com.google.dagger:dagger-compiler:2.17' implementation 'org.glassfish:javax.annotation:10.0-b28'}
开始使用 Dagger2
从最简单方式 @Inject 注解开始,看代码来体会 创建对象的过程。(基础 仅此而已)
/** * (核心功能):json解析的Javabean类(注意必须public修饰) * * @author qihao * @date on 2018/12/18 17:07 */public class User { private String name; // 这个 @Inject 表示可以提供User类型的对象实例 @Inject public User() { this.name = "我的名字…User…"; } public String getName() { return name; }}
public class LoginActivity extends AppCompatActivity { @Inject User user; // 必须 public class User @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); ButterKnife.bind(this); /*** * 添加依赖关系 */ //第一种方式 DaggerLoginComponent.create().inject(this); //第二种方式 DaggerLoginComponent.builder().build().inject(this); } @OnClick(R.id.login_button) public void onViewClicked() { Logger.i(user.getName()); }}
@Component()public interface LoginComponent { /** * 必须让Component知道需要往哪个类中注入 * 这个方法名可以是其它的,但是推荐用inject * 目标类LoginActivity必须精确,不能用它的父类 */ void inject(LoginActivity activity);}
需要Make Project之后才会在build目录下生成 DaggerLoginComponent 类,Dagger开头的一个编译类。
LoginActivity 中使用 User 中的方法,需要一个注入--Component类,通过 @Component()把双方关联。
LoginActivity 中需要初始化 DaggerTestComponent.builder().build().inject(this);
要理解记忆,要理解记忆,要理解记忆……这三个类之间的调用必须先理解了,@Inject注解和@Component()两处,仅此而已。
现在就用上 Dagger2 了( 配合MVP的结构使用,接下来讲,先理解了这个调用过程。)建议新手停留片刻思考一会再往下看。
接下来我们要考虑该如何调用第三方库中的方法呢??总不能改源码去加@Inject吧,稍作修改,如下。
使用@Module @Provides 标签,稍稍改一处。
(目前不要去想MVP结构,只是演示我们在Activity中调用一个类,不使用 new 的过程,思路简单些,仅此而已。)
@Component(modules = ActivityModule.class)public interface LoginComponent { void inject(LoginActivity activity);}
@Modulepublic class ActivityModule { // 使用Provider 注解 提供实例化对象 @Provides User providerUser() { return new User(); }}
我们只添加了一个Module。此处User() 只是一个普通类,并没有使用@Inject。
public class User { private String name; public User() { this.name = "我的名字…User…"; } public String getName() { return name; }}
最初我们 LoginActivity --> 通过 Component类 --> 得到并使用User对象
现在我们 LoginActivity --> 通过 Component类 --> 查找Module中的Provides提供的类 --> 得到并使用User对象
(好多人入门时的困惑是Dagger2比普通写法代码量还要大,这是真的,因为他的目的是方便解耦。丢掉疑虑看下去)
Module其实是一个简单工厂模式,Module里面的函数基本都是创建类实例的方法。
什么时候使用@Provide,比如你使用的第三方库,或者你引用的类,它的构造方法没有@Inject时。
Component首先搜索User类中用Inject注解标注的属性,没找到,Component就会去Module中查找Provides注解标注的位置,这样就可以解决第三方类库用dagger2实现依赖注入了。。
接下来按照真实项目的使用场景,开始一步一步的优化,优化到最后我们会把Dagger2中的注解标签都用上。最后封装成完美的MVP开发框架,源码会提供GitHub地址,请耐心看完全篇。接下来我们继续优化……
简单总结:
- @Inject 主要是用来标注目标类的依赖和依赖的构造函数。
- @Module Module和Provides是为解决第三方类库而生的。
- @Component 它是一个桥梁,一端是目标类,另一端是目标类所依赖类的实例。
接下来我们再稍微改一点,用@Named玩一玩。。
public interface BasePresenter { void postHttplogin();}
public class HomePresenter implements BasePresenter { @Override public void postHttplogin(){ Logger.i("Activity调用了home P层代码"); }}
public class LoginPresenter implements BasePresenter { @Override public void postHttplogin(){ Logger.i("Activity调用了login P层代码"); }}
随便写了几个类,我们可以通过@Named改一个参数就可以调用不同的类,原理如此,怎么玩都行。
@Modulepublic class ActivityModule { // 使用Provider 注解 实例化对象 @Provides User providerUser() { return new User(); } @Provides @Named("login") BasePresenter getLoginP(){ return new LoginPresenter(); } @Provides @Named("home") BasePresenter getHomeP(){ return new HomePresenter(); }}
@Component(modules = ActivityModule.class)public interface LoginComponent { void inject(LoginActivity activity);}
public class User { private String name; public User() { this.name = "我的名字…User…"; } public String getName() { return name; }}
public class LoginActivity extends AppCompatActivity { @Inject User user; @Inject @Named("home") BasePresenter hoemPresenter; @Inject @Named("login") BasePresenter loginPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); ButterKnife.bind(this); DaggerLoginComponent.create().inject(this); } @OnClick(R.id.login_button) public void onViewClicked() { hoemPresenter.postHttplogin(); Logger.i(user.getName()); loginPresenter.postHttplogin(); }}
这次贴的代码有点多,把完整的所有代码都贴上了,以免有些地方疑惑。。
相信看一遍代码心里都清楚这个@Named("login")是什么意思了,用法就是利用Module提供不同的对象。
在这其实我想举很多例子,其实就是利用Module提供对象的过程。。使用场景和思路千变万化。
classA中存在对classB的依赖该怎么使用呢??如下场景。
public class User { private String name = "我的名字…User…"; @Inject WorkInfo workInfo; public String getName() { DaggerLoginComponent.create().inject(this); return name + " <==> " + workInfo.getInfo(); }}
我们再user中使用 @Inject WorkInfo workInfo; 需要注入,否则是个空对象。
注意这一行:DaggerLoginComponent.create().inject(this);
@Component(modules = ActivityModule.class)public interface LoginComponent { void inject(LoginActivity activity); void inject(User user);}
public class WorkInfo { private String job = "财务"; private int jobYear = 5; private String jobSite = "北京"; public String getInfo(){ return "工作:"+job+", 工龄:"+jobYear+", 工作地点:"+jobSite; }}
LoginActivity依然是打印user.getName(),代码和之前一样……
我们只是加了一个Component,通过这个例子可以进一步理解Component的作用,哪里用就要哪里注入。
一个工程几千几万的类,要是全这么用肯定不爽……于是谷歌又发布了dagger.android 依赖库。。
现在回到顶部依赖处,看看自己是否集成dagger.android 依赖库,纯用Dagger2多累啊。
单利是我们最常用的设计模式,dagger2构建的对象怎么保证单利呢??
@Singleton注解 轻轻松松解决单例。我们稍微改一下user的引用。加个注解即可。
@Modulepublic class ActivityModule { @Singleton @Provides User providerUser() { return new User(); }}
注意: 第一个坑!!! 如果 module所依赖的Component 中有被单利的对象,那么Conponnent也必须是单利的
@Singleton@Component(modules = ActivityModule.class)public interface LoginComponent { void inject(LoginActivity activity);}
接下来我们创建两个对象打印一下:
public class LoginActivity extends AppCompatActivity { @Inject User user; @Inject User user2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); DaggerLoginComponent.create().inject(this); Logger.i(user.toString()); Logger.i(user2.toString()); }}
注意: 第二个坑,单利对象只能在同一个Activity中有效。不同的Activity 持有的对象不同
没什么办法么,我就想全局只要一个实例化对象啊?
自定义Scoped就可以解决,实现全局单例模式。
下面是@Singleton的源码
@Scope@Documented@Retention(RUNTIME)public @interface Singleton{}
可以看到定义一个Scope注解,必须添加以下三部分:
- @Scope :注明是Scope
- @Documented :标记在文档
- @Retention(RUNTIME) :运行时级别
Dagger2 Scope 如何实现全局单例,开始纯Android用法。
Dagger2的Android库支持support-v4包,导包时请记得使用support下的Dagger相关类
举例:import dagger.android.support.DaggerApplication; 先讲完全局单例我们再玩这个类。一点点感受生命周期。
最基础的引用对象我们已经讲完,接下来就是正题,Android如何使用Dagger2,并绑定生命周期,并且按照mvp的模式拆分。
@Scope@Documented@Retention(RUNTIME)public @interface ActivityScoped {}
@ActivityScoped@Component(modules = {ActivityModule.class}, dependencies = AppComponent.class)public interface LoginComponent {// 依赖了AppComponent作父类 void inject(LoginActivity activity); void inject(HomeActivity activity);}
@Singleton@Component(modules = AppModules.class)public interface AppComponent { // 如果不暴露,外面是调不到的。 User providerUser();}
@Modulepublic class AppModules { @Singleton @Provides User providerUser() { return new User(); }}
public class ComponentHolder { private static AppComponent myAppComponent; public static void setAppComponent(AppComponent component) { myAppComponent = component; } public static AppComponent getAppComponent() { return myAppComponent; }}
public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); inject(); } private void inject() { AppComponent appComponent = DaggerAppComponent.builder() .appModules(new AppModules(this)) .build(); ComponentHolder.setAppComponent(appComponent); }}
@Modulepublic class ActivityModule extends AppModules {}
我们只是对基础的代码做了简单的修改,讲一下再继续写:(Activity中的代码没多少变动)
- 第一,我们的注入类 LoginComponent 继承了 AppComponent,这个类是在Application中初始化,并且@Singleton标记了单例,绑定了全局,来保证单例,我们使用 Component时就是依赖它,dependencies = AppComponent.class 。
- 第二,我们使用 Component时无法使用@Singleton标记,因为父类已经用过了,再用会报错。。所以我们自己实现了一个ActivityScoped,和Singleton源码一样的,这就是子类权限低于父类的问题。
Modules和我们上面使用单例一样@Singleton标记,没变化。
全局单例的区别就在于Component初始化放在了Application中。
我写了一个AppModules只是留个父类而已,实际开发中我们会有很多的类,总要提取一个基类出来。
public class LoginActivity extends AppCompatActivity { @Inject User user; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); ButterKnife.bind(this); DaggerLoginComponent.builder() .activityModule(new ActivityModule(MyApp.getInstance())) .appComponent(ComponentHolder.getAppComponent()) .build() .inject(this); } @OnClick(R.id.login_button) public void onViewClicked() { Logger.i("login="+user.toString()); startActivity(new Intent(this,HomeActivity.class)); }}
public class HomeActivity extends AppCompatActivity { @Inject User user; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); DaggerLoginComponent.builder() .activityModule(new ActivityModule(MyApp.getInstance())) .appComponent(ComponentHolder.getAppComponent()) .build() .inject(this); Logger.i("home="+user.toString()); }}
dagger.android的使用,MVP模式的设计。请保证依赖了Android库。
DaggerLoginComponent.builder() .activityModule(new ActivityModule(MyApp.getInstance())) .appComponent(ComponentHolder.getAppComponent()) .build() .inject(this);
我们发现了这段代码会到处都是……而且这段代码重复率很高。
官网说明,其实Java这种用法在Android端大有问题!查阅官网的Android用法。
- 第一点,代码重复度过高:我们要在几乎每一个需要注入依赖项的 Activity 和 Fragment 里面来一套这样的代码。这是因为我们在 Android 编程中要用到的 Activity、Fragment 等类是继承自系统的,同时生命周期都由系统管理,所以使用 Dagger 2 的时候就不得不进行手动的处理,也就只有在纯粹自己写的 Java 类中使用 Dagger 2 才感觉更舒服一些。
- 第二点,违反了控制反转原则:原有的用法使得被注入的目标类必须要了解注入管理工具的详细信息,才能让注入工作顺利进行。即使可以通过接口使得实际代码中不必书写太多的实际类名,但这仍然造成了严重的目标类和管理类的紧耦合。
// dagger2针对Android的库,如果你使用Android的开发方式,仅依赖这几个库。implementation 'com.google.dagger:dagger-android:2.17'implementation 'com.google.dagger:dagger-android-support:2.17'annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'annotationProcessor 'com.google.dagger:dagger-compiler:2.17'
下面我们看一下Android的正确使用方式,
这次也不循序渐进了,直接上最终的优化版。官网用法很详细了,我就不从官网的demo一点点讲优化了,太累,写了太多了……
我们尽量简洁,整体理解,application下初始化,和activity初始化。然后关联,全局使用。
@Singleton@Component( modules = {AppModule.class, BuildersModule.class, ConfigModule.class, AndroidSupportInjectionModule.class})public interface AppComponent extends AndroidInjector { @Component.Builder interface Builder { @BindsInstance Builder application(Application application); AppComponent build(); }}
@Modulepublic class AppModule { @Singleton @Provides Context provideContext(Application application) { return application; }}
public class MyApp extends DaggerApplication { @Override protected AndroidInjector<? extends DaggerApplication> applicationInjector() { return DaggerAppComponent.builder().application(this).build(); }}
@Modulepublic abstract class BuildersModule { @ActivityScope @ContributesAndroidInjector abstract LoginActivity loginActivityInject(); @ActivityScope @ContributesAndroidInjector abstract SplashActivity splashActivityInject();}
@Modulepublic abstract class ConfigModule { @Provides @Singleton static Student provideStudent() { return new Student(); }}
@Documented@Scope@Retention(RetentionPolicy.RUNTIME)public @interface ActivityScope {}
public class LoginActivity extends BaseActivity implements MainContract.View { @Inject Student student; @Inject LoginPresenter presenter; @BindView(R.id.login_button) Button login; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); ButterKnife.bind(this); } @OnClick(R.id.login_button) public void onViewClicked() { Logger.i("student:"+student.getName()); Logger.i("对象:"+student.toString()); presenter.requestHttp(); startActivity(new Intent(this, SplashActivity.class)); } public void showMessage(String message) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); }}
public class SplashActivity extends BaseActivity { @Inject Student student; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); Logger.i("对象2:"+student.toString()); }}
public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
public interface MainContract { interface View{} interface Presenter {} interface Model {}}
public class MainModel implements MainContract.Model { @Inject public MainModel() { } public String returnMessage() { return "qi_hao"; }}
public class LoginPresenter extends BasePresenter implements MainContract.Presenter { private final LoginActivity view; private final MainModel model; @Inject public LoginPresenter(LoginActivity view, MainModel model) { this.view = view; this.model = model; } public void requestHttp() { view.showMessage(model.returnMessage()); }}
App--> AppCompat --->AppModule-->调用类
Activity--> ActCompat --->ActModule-->调用类
拆开看其实就这几条思路,在看代码的时候寻找就可以了,我们说一下Android的正确用法的注意点。。
先回顾前面的基础,现在全局没有了那么多ActCompat,现在只有一个Compat类,所有的Module都关联到这个Compat类。
到这一步资料就很多了,看一眼代码基本上也能理解。
注意点:Activity不可以继承DaggerActivity,看源码可知,必须使用源码中的Fragment才可以继承此类。
所以老老实实在BaseActivity中写 AndroidInjection.inject(this); 就一行初始化而已,也不麻烦。。。
Fragment可以继承DaggerFragment。
Application可以继承DaggerApplication。
如果使用V4 V7包的Fragment或者Activity,就要导入support包下的类,例如: dagger.android.support.DaggerApplication;
源码下载GitHub:已简单封装了MVP,会持续不断的完善为便捷开发框架。
赠送源码:https://github.com/yugu88/MagicWX。《最完整的Android逆向知识体系》
更多相关文章
- Unity调用Android原生Java代码以及Unity打开Android原生Activity
- 告别Dagger2模板代码:DaggerAndroid原理解析
- 获取Android的Java源代码并在Eclipse中关联查看
- 最简单的android底部导航栏 + Fragment的实现方式
- EditText实现输入限制和校验功能实例代码
- android关机重启流程代码