当前位置: 代码迷 >> 综合 >> Android进阶学习(6)-- Dagger2
  详细解决方案

Android进阶学习(6)-- Dagger2

热度:77   发布时间:2023-12-16 17:18:03.0

Dagger2

    • 依赖注入
        • 依赖
        • 依赖倒置
        • 控制反转(IOC)
        • 依赖注入
    • Dagger2
        • Dagger2 是什么
        • Dagger2 入门
        • Dagger2 使用
            • @Inject使用
            • @Module && @Provides
            • @Scope
            • @Qulifier
            • @Named
    • MVP模式
    • Dagger2 + MVP

依赖注入

依赖

什么是依赖?比如一个人Person,需要出门,他需要交通工具Car。那么我们可以说Person依赖Car

public class Car {public String byCar(){return "汽车";}
}public class Person {Car car = new Car();public void goToPlay(){System.out.println("出门玩耍:" + car.byCar());}
}

依赖倒置

软件设计六大原则之一,简称DIP;
接着上面的例子,现在需要去更远的地方,需要坐火车了,那么,还需要新创建火车类对代码做修改:

public class Car {public String byCar(){return "汽车";}
}
public String byTrain(){return "火车";}
public class Person {//Car car = new Car();Train train = new Train();public void goToPlay(){//System.out.println("出门玩耍:" + car.byCar());System.out.println("出门玩耍:" + train.byTrain());}
}

如果这样修改的话,那么现在需要去很近的地方,汽车限号了,只能骑自行车,我还需要去新建Bike类,再修改Person的代码,这样很麻烦,所以就出现了依赖倒置,依赖倒置就是上层模块不应该依赖底层模块,它们都应该依赖于抽象:
修改代码为如下:

public interface Driveable {String drive();
}
public class Bike implements Driveable{@Overridepublic String drive() {return "自行车";}
}
public class Car implements Driveable{@Overridepublic String drive() {return "汽车";}
}
public class Train implements Driveable{@Overridepublic String drive() {return "火车";}
}
public class Person {Bike bike;Car car;Train train;Person(){bike = new Bike();//car = new Car();//train = new Train();}public void goToPlay(){System.out.println("出门玩耍:" + car.drive());//System.out.println("出门玩耍:" + bike.drive());//System.out.println("出门玩耍:" + train.drive());}
}

上面这种写法,只需要修改Person的构造 决定使用哪个对象的方法即可。但是,这样仍然耦合性高,代码逻辑复杂的情况下不易维护,这时候控制反转出现了。

控制反转(IOC)

控制反转 IoC 是 Inversion of Control的缩写,意思就是对于控制权的反转;在上面依赖倒置的例子中,Person自己掌控着内部 Driveable 的实例化。
现在,我们可以更改一种方式。将 Driveable 的实例化移到 Person 外面。

public class Person {Driveable driveable;Person(Driveable driveable){this.driveable = driveable;}public void goToPlay(){System.out.println("出门玩耍:" + driveable.drive());}public static void main(String[] args) {Person person = new Person(new Bike());//Person person = new Person(new Car());//Person person = new Person(new Train());person.goToPlay();}
}

这种情况下,修改交通工具,并不需要再修改Person内部的任何代码,直接在外部修改传入的实例化对象即可。

依赖注入

依赖注入,也经常被简称为 DI,其实在上一节中,我们已经见到了它的身影。它是一种实现 IoC 的手段。

Dagger2

Dagger2 是什么

可以理解为是实现依赖注入的一种工具,也是利用注解和APT,在编译时生成代码,帮我们实现依赖注入;

Dagger2 入门

@Inject 1. 标记注解元素,告诉Dagger2为这个变量提供依赖 2. 标记构造方法,Dagger2通过这个注解可以在需要这个类的实例化变量时来找到这个构造方法,并把相关实例构造出来

@Module 当你需要的依赖是第三方库你无法修改类文件的时候就需要用到@Module,或者构造方法带参数的情况可以使用。

@Provides 用于标注@Module所标注的类中的方法,该方法在需要依赖时被调用,从而把预先提供好的对象当作依赖给@Inject标注的变量赋值

@Component 用于标注接口,是依赖需求方和依赖提供方的桥梁,被@Component注解的接口会在编译时生成对应的实现类

@Qulifier 用于自定义注解,可以理解为Java注解中的元注解

@Scope 同样用于自定义注解,可以限定注解的作用于

@Singleton 其实就是一个通过@Scope定义的注解,一般用来实现全局单例

Dagger2 使用

模拟以下场景,手机Phone需要依赖于手机屏幕Screen

@Inject使用

Screen:

public class Screen {@InjectScreen(){}public String getName(){return "三星";}
}

Phone:

public class Phone {@Inject//表示这个变量 Dagger需要给它提供依赖Screen screen;public Phone(){//编译时生成的类DaggerPhoneComponent.builder().build().inject(this);}public Screen getScreen(){return this.screen;}public static void main(String[] args) {Phone phone = new Phone();System.out.println("Phone:"+phone.getScreen().getName());}
}

现在需要为他们之间创建一个桥梁,也就是@Component

@Component
public interface PhoneComponent {void inject(Phone phone);
}

编译后,运行效果:
在这里插入图片描述
代码中,我并没有new出来Screen的对象给Phone中赋值,这就是Dagger2的作用。我们看一下在编译时它给我们生成了哪些文件:
在这里插入图片描述
我们从这里入手看一下生成的代码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这样就是在生成的文件中帮我们进行了实例化screen 并且赋值给 phone中的screen;
这是Dagger2 最基础的用法;

@Module && @Provides

当需要依赖的类是第三方库时,我们就需要使用 @Module && @Provides ,我们假设Screen类是第三方库中的文件 无法修改它的内容:
首先我们需要创建一个Module类——PhoneModule:

@Module
public class PhoneModule {public PhoneModule(){}@ProvidesScreen providesScreen(){return new Screen();}
}

修改PhoneCompnent类:

@Component(modules = PhoneModule.class)
public interface PhoneComponent {void inject(Phone phone);
}

修改Phone类:

public class Phone {@Inject//表示这个变量 Dagger需要给它提供依赖Screen screen;public Phone(){//编译时生成//修改这里DaggerPhoneComponent.builder().phoneModule(new PhoneModule()).build().inject(this);}public Screen getScreen(){return this.screen;}public static void main(String[] args) {Phone phone = new Phone();System.out.println("Phone:"+phone.getScreen().getName());}
}

最后把,Screen类中的注解都删除掉,因为假设它是第三方库的类,不能修改:

public class Screen {Screen(){ }public String getName(){return "三星";}
}

运行结果:
在这里插入图片描述
依然是可以获取到Screen的实例;这里要注意下,如果有参数,直接在PhoneModule中添加对应参数即可在这里插入图片描述
如果当@Inject 和 @Module 同时使用,会优先使用@Module。

@Scope

@Scope,作用域的意思,用来限定注解的作用域,实现局部单例。上面的例子中只有Phone一个类需要注入Screen的对象;如果这个Phone是一个双面屏手机,那么它就需要两个屏幕;我们修改Phone让它多一个Screen对象:
Phone:

public class Phone {@Inject//表示这个变量 Dagger需要给它提供依赖Screen screen;@InjectScreen endScreen;public Phone(){//编译时生成//修改这里DaggerPhoneComponent.builder().phoneModule(new PhoneModule()).build().inject(this);}public Screen getScreen(){return this.screen;}public Screen getEndScreen() {return endScreen;}public static void main(String[] args) {Phone phone = new Phone();System.out.println("Phone:"+phone.getScreen().getName());System.out.println("背屏:"+phone.getEndScreen().getName());}
}

输出结果:
在这里插入图片描述
两个Screen对象都被注入了,看下生成的代码中:
在这里插入图片描述
在这里插入图片描述
很明显,调用两次,意味着创建了两个Screen的对象;我们加上@Scope注解再来看看:
修改Screen:

public class Screen {//定义一个限定作用域的注解@Scope@Retention(RetentionPolicy.RUNTIME)public @interface ScreenScope {}@InjectScreen(){}public String getName(){return "三星";}
}

修改PhoneComponent :

//加上自定义的注解
@Screen.ScreenScope
@Component
public interface PhoneComponent {void inject(Phone phone);
}

修改:

@Module
public class PhoneModule {public PhoneModule(){}/加上自定义注解 限定作用域@Screen.ScreenScope@ProvidesScreen providesScreen(){return new Screen("三星屏幕");}
}

Phone代码不做任何修改,编译后,直接看生成的代码:
在这里插入图片描述
在这里插入图片描述
我们看生成的代码中是如何取对象的:
在这里插入图片描述
在这里插入图片描述
是个单例模式,那么也就意味着加上@Scope注解后只生成了一个Screen对象

@Qulifier

当需要被注入的对象是两个相同的类,但是构造不一样的时候就需要用到@Qulifier去自定义注解区分开两个对象。

public class Screen {//自定义两个注解@Qualifier@Retention(RetentionPolicy.RUNTIME)public @interface AScreen{ }@Qualifier@Retention(RetentionPolicy.RUNTIME)public @interface BScreen{ }String name;Screen(String name){ this.name = name; }public String getName(){return name;}
}

Phone中修改:

public class Phone {//当需要注入两个对象时,就需要用到自定义注解来区分@Screen.AScreen@Inject//表示这个变量 Dagger需要给它提供依赖Screen screen;@Screen.BScreen@InjectScreen endScreen;public Phone(){//编译时生成//修改这里DaggerPhoneComponent.builder().phoneModule(new PhoneModule()).build().inject(this);}public Screen getScreen(){return this.screen;}public Screen getEndScreen() {return endScreen;}public static void main(String[] args) {Phone phone = new Phone();System.out.println("Phone:"+phone.getScreen().getName());System.out.println("背屏:"+phone.getEndScreen().getName());}
}

PhoneModule中修改:

@Module
public class PhoneModule {public PhoneModule(){}//需要注入的对象的自定义注解 标注在对应的方法上@Screen.AScreen@ProvidesScreen providesScreen(){return new Screen("三星屏幕");}@Screen.BScreen@ProvidesScreen providesEndScreen(){ return new Screen("三星背面屏"); }
}

PhoneComponent不需要任何修改:

@Component(modules = PhoneModule.class)
public interface PhoneComponent {void inject(Phone phone);
}

输出结果:
在这里插入图片描述
两个对象,不同的构造都可以自动注入。

@Named

@Named注解本质上就是被@Qulifier标注的一个注解
在这里插入图片描述
@Named 的源码 和上面自定义的两个注解是一样的,只是多了个参数用来区分不同的@Named;
对于上面的例子,我们就不用自己定义两个注解去区分对象, 直接用@Name并传入参数即可

	//@Screen.AScreen@Named("AScreen")@InjectScreen screen;//@Screen.BScreen@Named("BScreen")@InjectScreen endScreen;

PhoneModule中也做同样的修改即可。

MVP模式

Mvp是Android常见的架构,是由MVC演变过来;
MVC:M指Model,在Mvc中我们的model一般来说就是bean。v和c 分别是view和controller,在Mvc中V和C对应的就是Activity,所以Activity又要处理逻辑又要处理view显示,就会造成代码冗余,一个Activity中的代码上千行都是常见的;
MVP:由Mvc演变而来,抽象出Present层去处理逻辑,回调view层控制view显示。P层需要处理逻辑,又需要调度view,在P层的基础上由抽离出了Model,也就是M层,让Model去处理真正的逻辑,P层只是将V和M关联起来,分别调用他们的方法;
在这里插入图片描述
我们实现一个简单的mvp:
在这里插入图片描述
BaseView:

public interface BaseView<T> {void setPresenter(T presenter);
}

BasePresenter :

public interface BasePresenter {void load();void onDestory();
}

BaseModel :

public interface BaseModel {
}

AbsPresenter:

//将 M 和 V 关联起来
public abstract class AbsPresenter<M extends BaseModel, V extends BaseView> {protected M mModel;protected V mView;//外部注入public abstract void setModel(M mModel);//外部注入public abstract void setView(V mView);
}

根据上面的基类,写一个ManiActivity模拟一次耗时操作:
在这里插入图片描述
根据谷歌官方MVP的Demo,要由一个管理者Contract
MainContract:

public interface MainContract {//模拟耗时操作 登陆interface Presenter extends BasePresenter {void login();}//登陆成功/失败 view层的回调interface View extends BaseView<Presenter> {void onSuccess();void onError();}//真正去处理逻辑的 Model 层interface Model extends BaseModel{boolean login();}
}

MainModel:

public class MainModel implements MainContract.Model {//用线程休眠来模拟耗时登陆@Overridepublic boolean login() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}return true;}
}

MainPresenter:

public class MainPresenter extends AbsPresenter<MainContract.Model, MainContract.View> implements MainContract.Presenter {MainContract.View view;MainPresenter(MainContract.View view){this.view = view;this.view.setPresenter(this);}@Overridepublic void login() {if (mModel.login()){view.onSuccess();}}//加载数据@Overridepublic void load() {}//将持有的view 释放 否则会内存溢出@Overridepublic void onDestory() {}@Overridepublic void setModel(MainContract.Model mModel) {this.mModel = mModel;}@Overridepublic void setView(MainContract.View mView) {this.mView = mView;}
}

MainActivity :

public class MainActivity extends BaseActivity<MainContract.Presenter> implements MainContract.View {TextView textView;@Overrideprotected int setLayoutId() {return R.layout.activity_main;}@Overrideprotected void initData() {mPresenter = new MainPresenter(this);((MainPresenter)mPresenter).setModel(new MainModel());((MainPresenter)mPresenter).setView(this);textView = findViewById(R.id.tvTest);textView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mPresenter.login();}});}@Overridepublic void setPresenter(MainContract.Presenter presenter) {if (this.mPresenter == null){mPresenter = presenter;}}@Overridepublic void onSuccess() {Toast.makeText(mContext, "登陆成功", Toast.LENGTH_SHORT).show();}@Overridepublic void onError() {}
}

运行效果,单击activity中的textview 出现Toast:
在这里插入图片描述

Dagger2 + MVP

Dagger2 和 Mvp 组合使用,可以解决Presenter业务逻辑变化,构造方法改变的问题。Dagger2的加入会让MVP更加解耦。可以将Presenter和Model,包括网络请求retrofit的对象实现自动注入,业务逻辑发生改变时,大大减少了代码修改量;用基本的Dagger2去实现,每一个Activity对应需要创建的类变得会更多, Dagger2推出了Dagger2-android,专门为android使用的,两种实现都可以达到MVP解耦自动注入的目的;

由于篇幅原因就不展示代码了。直接附上Demo的github:https://github.com/RDSunhy/Dagger2_demo

  相关解决方案