Android進階學習(6)-- Dagger2

依賴注入

依賴

什麼是依賴?比如一個人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{
    @Override
    public String drive() {
        return "自行車";
    }
}
public class Car implements Driveable{
    @Override
    public String drive() {
        return "汽車";
    }
}
public class Train implements Driveable{
    @Override
    public 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 {

    @Inject
    Screen(){

    }

    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(){}

    @Provides
    Screen 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;

    @Inject
    Screen 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 {
    }

    @Inject
    Screen(){

    }

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

修改PhoneComponent :

//加上自定義的註解
@Screen.ScreenScope
@Component
public interface PhoneComponent {

    void inject(Phone phone);
}

修改:

@Module
public class PhoneModule {

    public PhoneModule(){}

	/加上自定義註解 限定作用域
    @Screen.ScreenScope
    @Provides
    Screen 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
    @Inject
    Screen 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
    @Provides
    Screen providesScreen(){
        return new Screen("三星屏幕");
    }

    @Screen.BScreen
    @Provides
    Screen 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")
    @Inject
    Screen screen;

    //@Screen.BScreen
    @Named("BScreen")
    @Inject
    Screen 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 {
    //用線程休眠來模擬耗時登陸
    @Override
    public 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);
    }
    @Override
    public void login() {
        if (mModel.login()){
            view.onSuccess();
        }
    }
    //加載數據
    @Override
    public void load() {}
    //將持有的view 釋放 否則會內存溢出
    @Override
    public void onDestory() {}

    @Override
    public void setModel(MainContract.Model mModel) {
        this.mModel = mModel;
    }

    @Override
    public void setView(MainContract.View mView) {
        this.mView = mView;
    }
}

MainActivity :

public class MainActivity extends BaseActivity<MainContract.Presenter> implements MainContract.View {

    TextView textView;
    
    @Override
    protected int setLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected 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() {
            @Override
            public void onClick(View v) {
                mPresenter.login();
            }
        });
    }

    @Override
    public void setPresenter(MainContract.Presenter presenter) {
        if (this.mPresenter == null){
            mPresenter = presenter;
        }
    }
    @Override
    public void onSuccess() {
        Toast.makeText(mContext, "登陸成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public 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

發佈了81 篇原創文章 · 獲贊 49 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章