参考资料
– 深入浅出Dagger2 : 从入门到爱不释手
https://www.jianshu.com/p/626b2087e2b1
Android_Dagger2篇——从小白最易上手的角度 + 最新dagger.android
https://www.jianshu.com/p/22c397354997
概述
使用过spring mvc、ButterKnife或者ARouter的人一定对@AutoWired、@BindView这样注入标签爱不释手,这极大的减少了用来初始化的代码。
那么在MVP架构下,有没有什么标签能够让Presenter
也来实现自动装载的效果了,其实是有的,使用Dagger
来实现组件注入,然后就可以使用@Inject
注入标签自动装载Presenter
对象了。
Dagger2是什么
Dagger2是Dagger的升级版,是一个依赖注入框架,第一代由大名鼎鼎的Square公司共享出来,第二代则是由谷歌接手后推出的,现在由Google接手维护.
依赖注入是面向对象编程的一种设计原则,其目的是为了降低程序耦合,这个耦合就是类之间的依赖引起的.
举个例子:
public class ClassA{
private ClassB b
public ClassA(ClassB b){
this.b = b
}
}
这里ClassA的构造函数里传了一个参数ClassB,随着后续业务增加也许又需要传入ClassC,ClassD.试想一下如果一个工程中有5个文件使用了ClassA那是不是要改5个文件?
这既不符合开闭原则, 也太不软工了.这个时候大杀器Dagger2就该出场了.
public class ClassA{
@inject
private ClassB b
public ClassA(){
}
}
环境配置
在app
下的build.gradle
文件中添加如下配置
implementation 'com.google.dagger:dagger:2.5'
annotationProcessor "com.google.dagger:dagger-compiler:2.5"
项目中还使用到了ButterKnife
, 所以还需要添加如下配置
classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1'
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
无参无module注入示例
创建Presenter
public class PayPresenter {
@Inject
public PayPresenter(){}
public boolean isSupportAlipay(){
return true;
}
}
创建Compenont接口
@Component
public interface AliPayComponent {
void inject(AliPayActivity aliPayActivity);
}
进行注入
public class AliPayActivity extends AppCompatActivity {
@Inject
PayPresenter payPresenter;
@BindViews({R.id.aliPayButton})
List<Button> buttonList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alipay);
//将当前的Activity注入到组件中
DaggerAliPayComponent.builder().build().inject(this);
}
@OnClick({R.id.aliPayButton})
public void onViewClick(View view){
switch (view.getId()){
case R.id.aliPayButton:
if(payPresenter.isSupportAlipay()){
Toast.makeText(AliPayActivity.this,
"本应用支持支付宝支付",
Toast.LENGTH_LONG).show();
}
break;
}
}
}
注入编写过程
- 第一步,在需要注入的类的构造函数上使用
@Inject
注解来进行标记,告诉dagger
这个类是需要执行注入操作的。
- 第二步,创建一个
Component
接口类,在接口类上使用@Component
注解来标记,告诉dagger
这接口类是一个组件容器,是需要使用apt
进行自动生成代码的。然后在接口类的内部提供了一个inject
方法来指明主要在哪里进行注入操作。
- 第三步,在目标类
AlipayActivity
上使用@Inject
注解来标记要实现自动装载效果的PayPresenter payPresenter;
成员变量。
- 第四步,build一下项目,build成功后,可以在以下路径
app\build\generated\source\apt\debug\cheery\mvp\demo\module
看到新生成的java类DaggerLoginComponent.java
- 第五步,在目标类
AlipayActivity
上使用DaggerAliPayComponent.builder().build().inject(this);
来进行注入容器操作。注入完成后,就自动实现了payPresenter的自动装载效果。
实现流程分析
@Inject
关键字起作用的过程一共分为以下几步:
- 第一步, 对
DaggerLoginComponent
进行了构建,返回了一个空的Builder
对象。
DaggerAliPayComponent.Builder builder =
DaggerAliPayComponent.builder();
- 第二步, 实现LoginCompent接口对象的实例化
AliPayComponent alipayComponent = builder.build();
通过查看DaggerAliPayComponent.java
的源代码可以看到实现过程
public final class DaggerAliPayComponent implements AliPayComponent{
private DaggerAliPayComponent (Builder builder) {}
public static final class Builder {
private LoginModule loginModule;
public AliPayComponent build() {
return new DaggerAliPayComponent (this);
}
}
}
- 第三步, 将Activity注入到组件中
alipayComponent.inject(this);
通过查看DaggerAliPayComponent.java
的源代码可以看到实现过程
public final class DaggerAliPayComponent implements AliPayComponent {
@Override
public void inject(AliPayActivity aliPayActivity) {
injectAliPayActivity(aliPayActivity);
}
private AliPayActivity
injectAliPayActivity(AliPayActivity instance) {
AliPayActivity_MembersInjector.injectPayPresenter(
instance, new PayPresenter());
return instance;
}
}
首先这里先调用了inject
方法进行注入操作,接着调用了injectAliPayActivity
方法来实现真正的注入操作。
其次,在injectAliPayActivity
方法中用到了另外一个新的源码类AliPayActivity_MembersInjector
调用injectPayPresenter
方法来实现赋值操作,将参数中的PayPresenter对象
赋值给AliPayActivity
对象的payPresenter
属性对象。
public static void injectPayPresenter(AliPayActivity instance,
PayPresenter payPresenter) {
instance.payPresenter = payPresenter;
}
完成赋值操作后,就可以在AliPayActivity
直接使用payPresenter
属性来进行业务操作了。
有参数注入示例
无Module
注入实现代码
public class BankCard {
@Inject
public BankCard(){}
}
public class BankCardFactory {
BankCard bankCard;
@Inject
public BankCardFactory(BankCard bankCard){
this.bankCard = bankCard;
}
//进行支付操作
public boolean pay(double money){
return true;
}
}
@Component
public interface BankCardComponent {
void inject(AliPayActivity aliPayActivity);
}
使用注入类
public class AliPayActivity extends AppCompatActivity {
@Inject
BankCardFactory bankCardFactory;
@BindViews({R.id.aliPayButton})
List<Button> buttonList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alipay);
//将当前的Activity注入到组件中
DaggerBankCardComponent.create().inject(this);
}
@OnClick({R.id.aliPayButton})
public void onViewClick(View view){
switch (view.getId()){
case R.id.aliPayButton:
if(bankCardFactory.pay(1000)) {
Toast.makeText(AliPayActivity.this,
"支付成功",
Toast.LENGTH_LONG).show();
}
break;
}
}
}
实现过程分析
和上面无参构建注入的实现过程不同的是,在
BankCardFactory
构造函数中传入了BankCard
类型的参数并且使用了@Inject
,BankCard
类的构造函数也使用了@Inject
函数。
在Component
实现组件注解操作完成后,开始调用DaggerBankCardComponent.java
中的getBankCardFactory()
方法类进行自动创建BankCard
和BankCardFactory
对象,然后将其绑定到AlipayActivity
对象的bankCardFactory
属性上完成自动构造。
private BankCardFactory getBankCardFactory() {
return new BankCardFactory(new BankCard());
}
@Override
public void inject(AliPayActivity aliPayActivity) {
injectAliPayActivity(aliPayActivity);
}
private AliPayActivity injectAliPayActivity(
AliPayActivity instance) {
AliPayActivity_MembersInjector.
injectBankCardFactory(instance, getBankCardFactory());
return instance;
}
有Module
创建IView接口
/**
* 视图基本操作接口
*
* */
public interface IBaseView<T> {
/**
* 是否已经添加到父视图中
* */
public boolean isAddedToParentView();
/**
* 获取当前的上下文
* */
public Context getCurrentContext();
}
创建Module类
//使用 模块注解 自动注解一个模块类
@(android)Module
public class LoginModule {
private IBaseView baseView;
@Inject
public LoginModule(IBaseView iBaseView)
{
baseView = iBaseView;
}
@Provides
public IBaseView provideIBaseView()
{
return this.baseView;
}
}
创建Component类
// 使用 组件@Component 注解 自动注解一个 组件类
@Component(modules = LoginModule.class)
public interface LoginComponent {
void inject(LoginActivity loginActivity);
}
build一下项目,LoginComponent 组件在经过dagger的注解编译后,通过
apt
技术生成了一个新的JAVA类DaggerLoginComponent,这个新生成的类将是我们实现注解的关键, 类的内容你是可以点击进行查看的但是不支持编辑。
创建Presenter类
public class LoginPresenter implements IBasePresenter {
private IBaseView mView;
@Inject
public LoginPresenter(IBaseView view){
mView = view;
}
public void login() {
if(null == mView){
Log.i("Debug", "mView is null");
return;
}
Context context = mView.getCurrentContext();
Toast.makeText(context, "登录成功!!",
Toast.LENGTH_LONG).show();
}
}
创建Activity类
public class LoginActivity extends AppCompatActivity implements IBaseView {
@BindView(R.id.loginButton)
Button loginButton;
@Inject
LoginPresenter loginPresenter;
Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
unbinder = ButterKnife.bind(this);
DaggerLoginComponent.builder()
.loginModule(new LoginModule(this)).
build().inject(this);
}
@OnClick(R.id.loginButton)
public void onViewClicked() {
loginPresenter.login();
}
@Override
public void onDestroy(){
super.onDestroy();
if(null != unbinder){
unbinder.unbind();
}
}
@Override
public boolean isAddedToParentView() {
return true;
}
@Override
public Context getCurrentContext() {
return getBaseContext();
}
}
至此,我们就可以使用
@Inject
来实现LoginPresenter的自动装载
了, 点击登录按钮,发现确实正常调用了LoginPresenter中的login
方法,说明我们的LoginPresenter
确实实现了自动装载,自动初始化的效果。
实现流程分析
那么问题来了,LoginPresenter的构造函数中还包含有一个
IBaseView
接口参数,这个接口参数是怎么传递给LoginPresenter的?
上面这幅图概况的描述了dagger工作的整个流程,我们这里没有使用Scope
作用域关键字,所以可以先忽略它。在Actvitiy中我们通过以下代码实现了 组件的注入操作
DaggerLoginComponent.builder()
.loginModule(new LoginModule(this)).
build().inject(this)
这行链式编程代码太长了,看起来不容易明白它到底做了什么工作 。
我们将上面的代码分为4句,然后来看它们每一步都做了什么工作。
流程分析
我们根据上面的调用过程原理图知道,@Inject
关键字起作用的过程一共分为以下几步:
- 第一步, 对DaggerLoginComponent进行了构建,返回了一个空的Builder对象。
DaggerLoginComponent.Builder builder = DaggerLoginComponent.builder();
- 第二步, 将
LoginModule
对象存储在builder
对象中。
DaggerLoginComponent.Builder builder1 =
builder.loginModule(new LoginModule(this));
可以通过查看DaggerLoginComponent.java
的源代码看到实现过程
public final class DaggerLoginComponent implements LoginComponent {
public static final class Builder {
private LoginModule loginModule;
private Builder() {}
public Builder loginModule(LoginModule loginModule) {
this.loginModule = Preconditions.checkNotNull(loginModule);
return this;
}
}
}
这行代码,在空的Builder中加载了一个LoginModule对象,这个地方是关键的地方,这里传入了IBaseView
接口的实现对象this,也就是LoginActivity对象。
- 第三步, 实现LoginCompent接口对象的实例化
LoginComponent loginComponent = builder1.build();
可以通过查看DaggerLoginComponent.java
的源代码可以看到实现过程
public final class DaggerLoginComponent implements LoginComponent {
private LoginModule loginModule;
private DaggerLoginComponent(Builder builder) {
initialize(builder);
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.loginModule = builder.loginModule;
}
public static final class Builder {
private LoginModule loginModule;
public LoginComponent build() {
if (loginModule == null) {
throw new IllegalStateException(
LoginModule.class.getCanonicalName()
+ " must be set");
}
return new DaggerLoginComponent(this);
}
}
}
- 第四步, 将Activity注入到组件中
loginComponent.inject(this);
可以通过查看DaggerLoginComponent.java
的源代码可以看到实现过程
public final class DaggerLoginComponent implements LoginComponent {
private LoginModule loginModule;
private DaggerLoginComponent(Builder builder) {
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
private LoginPresenter getLoginPresenter() {
return new LoginPresenter(
LoginModule_ProvideIBaseViewFactory.proxyProvideIBaseView(loginModule));
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.loginModule = builder.loginModule;
}
@Override
public void inject(LoginActivity loginActivity) {
injectLoginActivity(loginActivity);
}
private LoginActivity injectLoginActivity(LoginActivity instance) {
LoginActivity_MembersInjector.
injectLoginPresenter(instance, getLoginPresenter());
return instance;
}
}
首先这里先调用了inject
方法进行注入操作,接着调用了injectLoginActivity
方法来实现真正的注入操作。
其次,在injectLoginActivity
方法中通过getLoginPresenter()
来获取一个LoginPresenter对象。
在getLoginPresenter()
是通过新的源码类LoginModule_ProvideIBaseViewFactory来调用proxyProvideIBaseView(LoginModule instance)
方法来获取loginModule
上提供的IBaseView
接口实现对象最终实现LoginPresenter
的初始化操作
public static IBaseView proxyProvideIBaseView(LoginModule instance) {
return Preconditions.checkNotNull(instance.provideIBaseView(),
"Cannot return null from a non-@Nullable @Provides method");
}
最后用到了另外一个新的源码类LoginActivity_MembersInjector调用injectLoginPresenter方法来实现赋值操作,将参数中的LoginPresenter对象
赋值给LoginActivity
对象的loginPresenter
属性对象。
public static void injectLoginPresenter(LoginActivity instance, LoginPresenter loginPresenter) {
instance.loginPresenter = loginPresenter;
}
完成赋值操作后,就可以在LoginActivity
直接使用loginPresenter
属性来进行业务操作了。
解耦和复用示例
上面的示例虽然实现了Presenter
和Activity
之间的解耦合操作,但是在Component
中还是在inject
的时候指明了类型。这里还是存在一定的耦合性,如何将耦合性降低到最低,我们可以采用 基类注入的方式来实现。
创建自定义作用域
@Scope //自定义一个作用域注解标签
@Retention(RetentionPolicy.RUNTIME) //在运行时中检测作用域范围
public @interface BaseScope {
}
创建Module类
@Module
public class SecondFragmentModule {
private SecondFragmentContract.View mView;
@Inject
public SecondFragmentModule(SecondFragmentContract.View view){
mView = view;
}
//这里指定Module提供者的作用域
@BaseScope
//将View的实现者返回提供出来
@Provides
public SecondFragmentContract.View provideSecondFragmentContactView(){
return mView;
}
}
创建Component类
@BaseScope
@Component(modules = SecondFragmentModule.class)
public interface ScondFragmentComponent {
//这里为什么不能使用Fragment来指定类型?
//reply: 因为Fragment无法在源码内部实现inject注入操作
//解决方案:
// 第一步: 定义一个BaseFragment来实现IView接口,在BaseFragment中通过@Inject来注入SecondPresenter
// 第二部: 在自定义Fragment类中继承BaseFragment,重写IView接口方法来实现回调
void inject(BaseFragment fragment);
}
创建Contract接口类
public interface SecondFragmentContract {
//定义视图操作行为
interface View extends IBaseView<Presenter>{
public void onFirstLoadDataArrived(List list);
public void onLoadMoreDataArrived(List list);
public void onRefreshError();
public void onLoadMoreError();
}
//定义代理人操作行为
interface Presenter{
public void refershData(int dataType);
public void loadMoreData(int dataType);
}
}
创建Present类
public class SecondPresenter implements SecondFragmentContract.Presenter {
private SecondFragmentContract.View mView;
private static final String BASE_URL = "https://api.apiopen.top";
private int page = 1;
Retrofit retrofit;
PoetryAPI poetryAPI;
@Inject
public SecondPresenter(SecondFragmentContract.View view){
mView = view;
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
@Override
public void refershData(int dataType) {
if(null == mView){
Log.i("Debug", "mView is null");
mView.onRefreshError();
return;
}
getPoetryList(page);
page++;
}
@Override
public void loadMoreData(int dataType) {
if(null == mView){
Log.i("Debug", "mView is null");
mView.onLoadMoreError();
return;
}
getPoetryList(page);
page++;
}
private void getPoetryList(final int page){
poetryAPI = retrofit.create(PoetryAPI.class);
Call<PoetryList> call = poetryAPI.getPoetryList(page, 10);
call.enqueue(new Callback<PoetryList>() {
@Override
public void onResponse(Response<PoetryList> response, Retrofit retrofit) {
PoetryList poetryList = response.body();
if(poetryList.getResult().size() == 0){
mView.onLoadMoreError();
}else{
if(page == 1){
mView.onFirstLoadDataArrived(
poetryList.getResult());
} else {
mView.onLoadMoreDataArrived(
poetryList.getResult());
}
}
}
@Override
public void onFailure(Throwable t) {
Log.i("getPoetryList", t.getMessage());
if(page == 1){
mView.onRefreshError();
}else{
mView.onLoadMoreError();
}
}
});
}
}
创建BaseFragment类
public class BaseFragment extends Fragment implements SecondFragmentContract.View {
@Inject
SecondPresenter secondPresenter;
@Override
public void onFirstLoadDataArrived(List list) { }
@Override
public void onLoadMoreDataArrived(List list) { }
@Override
public void onRefreshError() { }
@Override
public void onLoadMoreError() { }
@Override
public boolean isAddedToParentView() { return true; }
@Override
public Context getCurrentContext() { return getContext(); }
@Override
public void onAttach(Context context){
super.onAttach(context);
DaggerScondFragmentComponent.builder()
.secondFragmentModule(new SecondFragmentModule(this))
.build()
.inject(this);
}
}
定义子类继承BaseFragment
public class SecondFragment extends BaseFragment{
private View view;
@BindView(R.id.poetryListView)
ListView poetryListView;
@BindViews({R.id.refreshDataButton, R.id.loadDataButton})
List<Button> buttonList;
private PoetryListAdapter poetryListAdapter;
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
Bundle savedInstanceState){
super.onCreateView(inflater, container, savedInstanceState);
if(null == view){
view = inflater.inflate(R.layout.second_fragment, null);
//这里必须进行绑定
ButterKnife.bind(this, view);
//secondPresenter = new SecondPresenter(this);
poetryListAdapter = new PoetryListAdapter(getContext(), null);
poetryListView.setAdapter(poetryListAdapter);
secondPresenter.refershData(1);
}
return view;
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onFirstLoadDataArrived(List list) {
poetryListAdapter.getDataSource().addAll(0, list);
poetryListAdapter.notifyDataSetChanged();
}
@Override
public void onLoadMoreDataArrived(List list) {
poetryListAdapter.getDataSource().addAll(list);
poetryListAdapter.notifyDataSetChanged();
}
@Override
public void onRefreshError() {
Toast.makeText(getContext(), "未发现新数据" , Toast.LENGTH_LONG).show();
buttonList.get(0).setEnabled(false);
}
@Override
public void onLoadMoreError() {
Toast.makeText(getContext(), "已加载完所有数据" , Toast.LENGTH_LONG).show();
buttonList.get(1).setEnabled(false);
}
@OnClick({R.id.refreshDataButton, R.id.loadDataButton})
public void onViewClicked(View v){
switch (v.getId()) {
case R.id.refreshDataButton:
if(null == secondPresenter){
Log.i("Debug", "firstPresenter is null");
return;
}
secondPresenter.refershData(1);
break;
case R.id.loadDataButton:
if(null == secondPresenter){
Log.i("Debug", "firstPresenter is null");
return;
}
secondPresenter.loadMoreData(1);
break;
}
}
}
注意要点
Component中只是通过inject注入了Fragment、Activity、Dialog的自定义基类,由基类来进行dagger注入和Presenter的注入操作, 这些自定义的基类的子类只需要继承基类就可以实现dagger的注入和自动加载功能, 实现了代码的复用。
总结
用法
这里使用Inject 注入需要如下四步:
- 1 定义一个Scope作用域类,限定注解的使用范围。
- 2 定义一个FragmentModule,构造函数需要使用Inject注入,其中包含Contract.View接口,实现Module注解和View接口提供。
- 3 定义一个FragmentComponment接口, 对FragmentModule进行装载,并指定inject参数类型。
- 4 在Presenter的构造函数上使用Inject进行注入。
- 5 在Fragment的onAttach方法内实现对Fragment的注解注入,注入完成后,就可以使用@Inject实现来实现自动装载的功能了
Dagger2的优势
- 1.可以对Presenter进行共用了,比如 有三个列表的页面,分别是 新闻、活动、消息,都是以列表的形式来加载数据的。
我们统一通过Presenter的refershData(int dataType)和loadMoreData(int dataType)来进行数据的加载和调用,然后通过IView的接口进行回调。
- 2.解除了传统Presenter对与Fragment和Activity的依赖,使用dagger进行组件注解后,Presenter中只关注传入进来的IView接口对象,而不关注这个实现这个IView接口的对象是谁,是Fragment、Dialog、Activity都不影响使用, 实现了真正意义上的解耦合。
Component中只是通过inject注入了Fragment、Activity、Dialog的自定义基类,由基类来进行dagger注入和Presenter的注入操作,这些自定义的基类的子类只需要继承基类就可以实现dagger的注入和自动加载功能, 实现了代码的复用。