依賴注入 Dagger 2 詳解 [復制鏈接]

2019-8-27 09:49
rongchuang 閱讀:609 評論:0 贊:0
Tag:  依賴注入 Dagger

前言

依賴注入概念網絡有很多解釋,這里就不詳細介紹,本文通過一個簡單的示例一步步深入了解依賴注入的優勢以及為什么使用依賴注入。

概念

依賴注入(Dependency Injection),簡稱DI,又叫控制反轉(Inversion of Control),簡稱IOC。
當一個類的實例需要另另一個類的實例進行協助時,在傳統的設計中,通常由調用者來創建被調用者的實例,然而依賴注入的方式,創建被調用者不再由調用者創建實例,創建被調用者的實例的工作由IOC容器來完成,然后注入到調用者。因此也被稱為依賴注入。

作用

將各層的對象以松耦合的方式組織在一起,解耦,各層對象的調用完全面向接口。當系統重構或者修改的時候,代碼的改寫量將大大減少。

Android Studio 引入Dagger2

1.在工程根目錄的build.gradle引入apt插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
整體文件:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0-alpha1'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

2.在app目錄下的build.gradle加上一行

apply plugin: 'com.neenbedankt.android-apt'

dependencies下面加上:
apt 'com.google.dagger:dagger-compiler:2.2'
provided 'org.glassfish:javax.annotation:10.0-b28'
compile 'com.google.dagger:dagger:2.2'

別忘了加入lint warning
lintOptions {
warning 'InvalidPackage'
}

整體文件:

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "com.iiseeuu.dagger2demo"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        warning 'InvalidPackage'
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha1'
    testCompile 'junit:junit:4.12'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
    androidTestCompile 'com.android.support.test:runner:0.5'
    androidTestCompile 'com.android.support:support-annotations:23.4.0'
    apt 'com.google.dagger:dagger-compiler:2.2'
    provided 'org.glassfish:javax.annotation:10.0-b28'
    compile 'com.google.dagger:dagger:2.2'
    compile 'com.google.code.gson:gson:2.7'
}

@Inject介紹

注解(Annotation)來標注目標類中所依賴的其他類,同樣用注解來標注所依賴的其他類的構造函數,那注解的名字就叫Inject

使用dagger2之前

public class A {
    private String field;

    public A(){

    }

    public void doSomething(){
        Log.e("A", "do something");
    }
}
//Activity調用
public class MainActivity extends AppCompatActivity {

    A a;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        a = new A();
        a.doSomething();
    }
}

使用dagger2之后

public class A {
    private String field;

    @Inject
    public A(){

    }

    public void doSomething(){
        Log.e("A", "do something");
    }
}
//Activity調用
public class MainActivity extends AppCompatActivity {

    @Inject
    A a;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        a.doSomething();
    }
}

這樣寫直接運行的話 是會報錯的,變量a沒有被實例化,會報空指針錯誤。因為他們沒有任何關聯。這個時候我們需要把MainActivity和A類關聯起來,這個時候就需要@Commponent了,下面請看@Commponent:

@Commponent

A類的構造函數與調用類Activity都使用Inject注解,Component一般是個接口,就是將MainActivity與A類橋接起來。
我們定義一個AComponent接口,并使用@Component注解:

@Component
public interface AComponent {
    A a();
}

//Activity調用
public class MainActivity extends AppCompatActivity {

    @Inject
    A a;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        a = DaggerAComponent.builder().build().a();

        a.doSomething();
    }
}

AComponent會查找MainActivity中用Inject注解標注的屬性,查找到相應的屬性后會接著查找該屬性對應的用Inject標注的構造函數(這時候就發生聯系了),剩下的工作就是初始化該屬性的實例并把實例進行賦值。因此我們也可以給Component叫另外一個名字注入器(Injector)

Component現在是一個注入器,就像注射器一樣,Component會把A的實例注入到MainActivity中,來初始化MainActivity類中的依賴。

a = DaggerAComponent.builder().build().a();這種寫法不太友好,一般情況下,我們只需要將MainActivity的實例交給AComponent引用即可。

public class MainActivity extends AppCompatActivity {

    @Inject
    A a;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerAComponent.builder().build().inject(this);

        a.doSomething();  
    }
}

@Module

Module的職責是用來生成實例,可以把他比作一個工廠,專業生成各種類的實例。

項目中使用到了第三方的類庫,第三方類庫又不能修改,所以根本不可能把Inject注解加入這些類中,這時我們的Inject就失效了。

比如我項目中依賴GSON解析庫。這個時候我需要新建一個類一個提供一個Gson的實例。

@Module
public class AModule {

    @Provides
    public Gson provideGson(){
        return new Gson();
    }
}
//component類 引用module為AModule.class
@Component(modules = AModule.class)
public interface AComponent {
    void inject(MainActivity mainActivity);
}

//activity調用
public class MainActivity extends AppCompatActivity {

    @Inject
    A a;

    @Inject
    Gson gson;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerAComponent.builder().build().inject(this);

        a.field = "test";
        String aStr = gson.toJson(a);
        Log.e("mainactivity","astr = "+aStr);
        a.doSomething();
    }
}

@Provides

module類對外提供實例方法的注解

注意:

  1. component首先先去提供的module里面尋找提供的實例,沒有找到時再去找構造函數@inject注解
  2. 一個component可以依賴多個module,一個component可以依賴另一個component
    例如:AModule 提供A類的實例,GsonModule提供Gson的實例
//AModule.java
@Module
public class AModule {

    @Provides
    public A provideA(){
        return new A();
    }
}
//GsonModule.java
@Module
public class GsonModule {

    @Provides
    public Gson provideGson(){
        return new Gson();
    }
}
//component
@Component(modules = {AModule.class,GsonModule.class})
public interface AComponent {
    void inject(MainActivity mainActivity);
}
//調用activity不變
...

@Scope and @Singleton

這個注解是用來劃分作用域的,標記當前對象使用范圍。
比如限制對象只能在所有Activity中使用,或者只能在Application中使用,或者只能在Fragment中使用
@Singleton 單例模式全局共用一個對象 就是@Scope的一個實現。

這個Scope比較難以理解,我們舉個例子自定義一個Scope:
假如有個項目包含用戶體系,用戶登錄成功后,A界面、B界面和C界面要依賴用戶來獲取一些數據,LoginActivity界面不依賴于用戶體系。
我們想要把User對象實例可以在A、B、C界面共用。

那么整體項目的Scope劃分結果圖為:


untitled.jpg

1.自定義UserScope注解

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {

}

2.新建一個User實體類

public class User {
    private String name;
    private String avatar;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }
}

3.新建一個UserModule來提供User的實例,提供實例方法使用自定義的UserScope注解,表示提供實例僅限于UserScope范圍內使用。

@Module
public class UserModule {
    private User user;

    public UserModule(User user) {
        this.user = user;
    }

    @Provides
    @UserScope
    User provideUser(){
        return user;
    }

}

4.新建Component橋梁。他是一個子Component,依賴于一個全局的父Component。
Subcomponent注解與Component依賴另一個Component有點像,他們區別在于:
Subcomponent可以獲取到父Component的所有可以產生出的對象。
Component依賴則只能獲取到被依賴的Component所暴露出來的可以生產的對象

@UserScope
@Subcomponent(modules = UserModule.class)
public interface UserComponent {
    AComponent plus(AModule aModule);
    BComponent plus(BModule bModule);
    CComponent plus(CModule cModule);
}

5.新建一個全局的父Component,引用子Component。

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    UserComponent plus(UserModule userModule);
}

6.新建一個AppModule,提供一個全局的application實例

@Module
public class AppModule {
    private Application application;

    public AppModule(Application application) {
        this.application = application;
    }

    @Provides
    @Singleton
    public Application provideApplication() {
        return application;
    }
}

7.創建一個App實例,在程序啟動時,調用它。

public class App extends Application{
    //application組件
    private AppComponent appComponent;
    //用戶組件
    private UserComponent userComponent;

    //獲取當前application的實例
    public static App get(Context context) {
        return (App) context.getApplicationContext();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //注入全局Application
        appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
    }

    //對外提供UserComponent
    public UserComponent getUserComponent() {
        return userComponent;
    }

    //注入UserComponent,調用此方法后,UserCope生效
    public UserComponent createUserComponent(User user) {
        userComponent = appComponent.plus(new UserModule(user));
        return userComponent;
    }
    //釋放UserComponent組件
    public void releaseUserComponent() {
        userComponent = null;
    }
}

8.LoginActivity 點擊登錄按鈕,創建User實例,并開始UserScope生命周期

findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                User user = new User();
                user.setName(etUserName.getText().toString());
                user.setAvatar(etPassword.getText().toString());

                App.get(MainActivity.this).createUserComponent(user);
                startActivity(new Intent(MainActivity.this, AActivity.class));
            }
        });

9.AActivity、BActivity、CActivity 使用User對象為同一個User對象,LoginActivity是沒有權限使用User對象的。
下面為詳細代碼:

//AActivity.java
public class AActivity extends AppCompatActivity{
    @Inject
    User user;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
        //注入
        App.get(this).getUserComponent().plus(new AModule()).inject(this);
        TextView textView = (TextView) findViewById(R.id.text);
        textView.setText("username:"+user.getName()+"user:"+user);



        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(AActivity.this, BActivity.class));
            }
        });

    }

}
//BActivity.java
public class BActivity extends AppCompatActivity{
    @Inject
    User user;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        App.get(this).getUserComponent().plus(new BModule()).inject(this);
        TextView textView = (TextView) findViewById(R.id.text);
        textView.setText("username:"+user.getName()+"--user:"+user);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(BActivity.this, CActivity.class));
            }
        });
    }


}

//CActivity.java
public class CActivity extends AppCompatActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_c);


        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                App.get(CActivity.this).releaseUserComponent();
                startActivity(new Intent(CActivity.this, MainActivity.class));
                finish();
            }
        });

    }
}


10.最終效果,打印出User對象地址都是同一個地址:[email protected]


device-2016-06-23-144101.png
device-2016-06-23-144101.png

device-2016-06-23-144137.png
device-2016-06-23-144137.png

device-2016-06-23-144101.png
device-2016-06-23-144101.png

@Qualifier and @Named

@Named 其實是@Qualifier的一種實現,弄明白@Qualifier(限定符)基本上也就明白了@Named
@Qualifier(限定符)主要作用是用來區分不同對象實例。

假如上面示例系統支持多用戶,在Activity中引用了兩個不同的User實例,我們該怎么區分呢?

1.首先我們自定義一個@Qualifier 名稱叫做UserNamed

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface UserNamed {
    String value() default "";
}

2.修改UserModule對外提供兩個User實例userA和UserB,并使用@UserNamed注解標識實例

@Module
public class UserModule {
    private User userA;
    private User userB;

    public UserModule(User userA,User userB) {
        this.userB = userB;
        this.userA = userA;
    }

    @UserNamed("a")
    @Provides
    @UserScope
    User provideUserA(){
        return userA;
    }
    @UserNamed("b")
    @Provides
    @UserScope
    User provideUserB(){
        return userB;
    }

}

3.修改App.java的createUserComponent 傳入兩個實例

 public UserComponent createUserComponent(User userA,User userB) {
        userComponent = appComponent.plus(new UserModule(userA,userB));
        return userComponent;
    }

4.登錄時創建2個實例

findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                User userA = new User();
                userA.setName(etUserName.getText().toString()+"AAA");
                userA.setAvatar(etPassword.getText().toString());

                User userB = new User();
                userB.setName(etUserName.getText().toString()+"BBB");
                userB.setAvatar(etPassword.getText().toString());

                App.get(MainActivity.this).createUserComponent(userA,userB);
                startActivity(new Intent(MainActivity.this, AActivity.class));
            }
        });

5.Activity中的使用:

public class AActivity extends AppCompatActivity{
    @UserNamed("a")
    @Inject
    User userA;

    @UserNamed("b")
    @Inject
    User userB;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
        //注入
        App.get(this).getUserComponent().plus(new AModule()).inject(this);
        TextView textView = (TextView) findViewById(R.id.text);
        textView.setText("username:"+userA.getName()+"--user:"+userA+""+"username:"+userB.getName()+"--user:"+userB);



        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(AActivity.this, BActivity.class));
            }
        });

    }

}

我們在回頭看下我們自定義的@UserNamed與系統定義的@Named源碼的區別大家應該就能明白了~兩個類的實現是一樣的代碼

//@UserNamed定義:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface UserNamed {
    String value() default "";
}

//@Named的定義:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

dagger2 主要功能 就此介紹完畢,如果覺得不錯,就盡快用起來吧~



我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(0)
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

阿拉斯加垂钓APP下载
二分彩 福建快3 刮刮乐 黑龙江十一选五 金猪配资 体彩6+1 湖北11选5 美国职业棒球比分 尊享配资 云南时时彩 甘肃11选5 展鹏配资 七星彩 球探篮球比分直播 申捷策略 蓝球nba比分网