1. 程式人生 > >Android Dagger2 MVP架構 一看就明白

Android Dagger2 MVP架構 一看就明白

Dagger2介紹

好了,介紹一下Dagger2吧!
Dagger2 是Google 的新一代依賴注入框架(依賴注入不講,你都看到這篇文章了,那你應該懂,如果不懂,請度娘、谷哥之,此文不廢話),Dagger2是Dagger1的分支,但兩個框架沒有嚴格的繼承關係,亦如Struts1 和Struts2 的關係!

那就有人問了,為什麼要用Dagger2?
回答:解耦(DI的特性),易於測試(DI的特性),高效(不使用反射,google官方說名比Dagger快13%),易混淆(apt方式生成程式碼,混淆後依然正常使用)

學習成本

開啟官網,映入眼簾的第一句話便是:

DaggerA
fast dependency injector for Android and Java. - Google

如果你是個想要很簡單,並且速度很快的就能上手使用Dagger2的客官,你可以點選視窗右上角的X,關閉該文章了!
Dagger2的學習曲線相對比較陡峭,需要理解的概念也較多,需要一點一點的理解,Dagger2概念還是推薦看下面這篇文章。

本文只講基礎概念和使用,具體的概念請參見官方文件或其他博文!

關鍵的註解

@Inject

這個註解是用來說明該註解下方的屬性或方法需要依賴注入。(如果使用在類構造方法上,則該類也會被註冊在DI容器中作為注入物件。很重要,理解這個,就能理解Presenter注入到Activity的步驟!)

@Provider

在@Module註解的類中,使用@Provider註解,說明提供依賴注入的具體物件

@Component

簡單說就是,可以通過Component訪問到Module中提供的依賴注入物件。假設,如果有兩個Module,AModule、BModule,如果Component只註冊了AModule,而沒有註冊BModule,那麼BModule中提供的物件,無法進行依賴注入!

@SubComponent

該註解從名字上就能知道,它是子Component,會完全繼承父Component的所有依賴注入物件!

@Sigleton

被註解的物件,在App中是單例存在的!

@Scope

用來標註依賴注入物件的適用範圍。

@Named(@)

因為Dagger2 的以來注入是使用型別推斷的,所以同一型別的物件就無法區分,可以使用@Named註解區分同一型別物件,可以理解為物件的別名!

Android Studio 配置Dagger2(eclipse 請點選右上角X)

Step 1

專案根目錄下的build.gradle。根目錄、根目錄、根目錄,重要事情說三遍!
在dependencies程式碼塊中加入

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

加完成後的build.gradle如下

buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
        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
        //配置DBFlow
    }
}

allprojects {
    repositories {
        maven { url "https://www.jitpack.io" }
        jcenter()
        mavenCentral()
    }
}

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

如果你使用DB-Flow 或 ButterKnif已經新增過了,就不用添加了!
allprojects的內容不需要與我貼出來的完全一致。

Step 2

使用Dagger2的專案下的build.gradle,一般都是app資料夾下的。
在build.gradle檔案頂部 apply plugin: ‘com.android.application’ 下方新增

apply plugin: 'android-apt'

dependencies程式碼塊中新增Dagger2的依賴關係

//使用APT生成工具,生成需要的DI程式碼
apt 'com.google.dagger:dagger-compiler:2.5'
//JSR250的jar包,使用這個和使用glassFish的那個一樣,僅為了使用@Inject 和@Named註解
provided 'javax.annotation:jsr250-api:1.0'
//Dagger2 的依賴
compile 'com.google.dagger:dagger:2.5'

Step3 Make Project,使依賴生效!

配置單例物件,上程式碼!(還是配置,配置、配置)

分析一下,我們一般都需要哪些東西是單例的,Http 請求類,SharedPreference等等。

程式碼結構如下
這裡寫圖片描述

Step 1 建立ActivityScope

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

該類用於區分與@Sigleton或其他@Scope的作用域。

Strp2 建立module

我們首先來分析一下,需要哪些類是單例的,單例建立的,都和Application關聯起來。
1、提供 shredPreference,建立AppModule

@Module
public class AppModule {

    private Context context;

    public AppModule(DaggerApplication application) {
        this.context = application;
    }

    @Singleton
    @Provides
    public Context ProviderApplicationContext(){
        return context;
    }

    @Singleton
    @Provides
    @Named("default")
    public SharedPreferences providerDefaultSharedPreferences(){
        return PreferenceManager.getDefaultSharedPreferences(context);
    }

    @Singleton
    @Provides
    @Named("encode")
    public SharedPreferences providerEncodeSharedPreferences(){
        return context.getSharedPreferences("encode",Context.MODE_PRIVATE);
    }

}

2、因為是使用的Retrofit 所以要提供 OkhttpClient ,RetrofitClient
建立OkhttpModule

@Module
public class OkhttpModule {

    @Singleton
    @Provides
    @Named("cache")
    public OkHttpClient providerAutoCacheOkHttpClient(){
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        Interceptor cacheInterceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Response response = chain.proceed(request);

                String cacheControl = request.cacheControl().toString();
                if (TextUtils.isEmpty(cacheControl)) {
                    cacheControl = "public, max-age=" + 3600 * 6 + " ,max-stale=2419200";
                }
                return response.newBuilder()
                        .header("Cache-Control", cacheControl)
                        .removeHeader("Pragma")
                        .build();
            }
        };
        return new OkHttpClient.Builder()
                .addNetworkInterceptor(interceptor)
                .addNetworkInterceptor(cacheInterceptor)
                .retryOnConnectionFailure(true)
                .connectTimeout(10, TimeUnit.SECONDS)
                .build();
    }

    @Singleton
    @Provides
    @Named("default")
    public OkHttpClient providerOkHttpClient(){
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        return new OkHttpClient.Builder()
                .addNetworkInterceptor(interceptor)
                .retryOnConnectionFailure(true)
                .connectTimeout(10, TimeUnit.SECONDS)
                .build();
    }
}

建立RetrofitModule

@Module
public class RetrofitModule {

    @Singleton
    @Provides
    public LocalRetrofit providerLocalRetrofit(@Named("default") OkHttpClient okHttpClient){
        return new LocalRetrofit(okHttpClient);
    }

    @Singleton
    @Provides
    public TaobaoRetrofit providerTaobaoRetrofit(@Named("cache") OkHttpClient okHttpClient){
        return new TaobaoRetrofit(okHttpClient);
    }
}

這裡因為有時候的http請求是針對多個地址的,所以我又封裝了兩個提供retrofit的類

TaobaoRetrofit

    private static final String BASE_URL = "http://ip.taobao.com/";
    private static Retrofit retrofit;

    public TaobaoRetrofit(OkHttpClient okHttpClient) {
        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(okHttpClient)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(FastJsonConverterFactory.create())
                .build();
    }


    public Retrofit getRetrofit() {
        return retrofit;
    }

LocalRetrofit

public class LocalRetrofit {
    private static final String BASE_URL = "http://xxxxxx.xxx.xxxx/";
    private static Retrofit retrofit;

    public LocalRetrofit(OkHttpClient okHttpClient) {
        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(okHttpClient)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(FastJsonConverterFactory.create())
                .build();
    }


    public Retrofit getRetrofit() {
        return retrofit;
    }
}

我希望網路請求中的Service對呼叫者是黑盒,呼叫者只需要知道呼叫哪個Service即可,建立過程不需要了解,所以又提供了ServiceModule

LocalServiceModule

@Module
public class LocalServiceModule {

    @Singleton
    @Provides
    public UserService providerUserService(LocalRetrofit retrofit){
        return retrofit.getRetrofit().create(UserService.class);
    }
}

TaobaoIPLocationServiceModule

@Module
public class TaobaoIPLocationServiceModule {

    @Singleton
    @Provides
    public TaobaoIPLocationService proidverIPLocationServiceModule(TaobaoRetrofit taoBaoRetrofitClient) {
        return taoBaoRetrofitClient.getRetrofit().create(TaobaoIPLocationService.class);
    }
}

單例的module 建立完畢!

Step 3 建立AppCompontent(個人感覺類於Spring的Context類)

@Singleton
//關鍵程式碼在這!component會把Module裡的提供的物件,註冊到容器裡
@Component(modules = {AppModule.class,
        OkhttpModule.class,
        RetrofitModule.class,
        LocalServiceModule.class,
        TaobaoIPLocationServiceModule.class})
public interface AppComponent {
    //SubComponent 繼承當前Component
    MainComponent addSub(MainModule mainModule);
}

Step 4 make Project

app旁邊的綠色下箭頭按鈕
點選綠色下箭頭按鈕,make project。
Dagger2會自動生成Dagger字首的Dagger注入工具。

Step5 改造Application

public class DaggerApplication extends Application {

    private static AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    public static DaggerApplication get(Context context) {
        return (DaggerApplication) context.getApplicationContext();
    }

    private void setupApplicationComponent() {
        //Dagger開頭的注入類DaggerAppComponent
        appComponent = DaggerAppComponent.builder()
        //此時appModule方法是過時方法,因為我們沒有使用到任何一個module中提供的物件
                .appModule(new AppModule(this))
                .build();
    }

    //獲取AppComponent 以便於SubComponent繼承
    public AppComponent getAppComponent() {
        if(appComponent == null){
            this.setupApplicationComponent();
        }
        return appComponent;
    }

}

正片 MVP

提供了那麼多物件,到底怎麼用???
下面是真正的正片、正片、正片。
[碼字。。。。好累 (?_?)]

Step1 建立Activity的module

因為Activity的類的構造器,我們無法加入@Inject註解,所以必須提供Module才能提供View介面的例項化物件。

@Module
public class MainModule {

    private MainContract.View view;

    //構造方法傳遞View 介面的例項化物件
    public MainModule(MainContract.View view){
        this.view = view;
    }

    //在DI容器中提供View介面的例項化物件
    @ActivityScope
    @Provides
    public MainContract.View providerView(){
        return view;
    }

}

Step2 建立Activity的Conponent

//生命週期管理
@ActivityScope
//很重要!這個Component應該是AppComponent的子Component,所以要使用這個註解
//不使用@Component註解的Dependents屬性是因為希望能統一管理子Component
@Subcomponent(modules = MainModule.class)
public interface MainComponent {
    //方法引數中,只能傳遞被注入物件!要在哪個類中注入,寫哪個類,注入到父類沒用!
    void inject(MainActivity mainActivity);
}

Step3 改造AppComponent(重要)

在AppComponent類中新增一行

MainComponent addSub(MainModule mainModule);

程式碼如下

@Singleton
@Component(modules = {AppModule.class,
        OkhttpModule.class,
        RetrofitModule.class,
        LocalServiceModule.class,
        TaobaoIPLocationServiceModule.class})
public interface AppComponent {
    MainComponent addSub(MainModule mainModule);
}

Step3 MVP模式類中使用Dagger2

1、建立MainContract(不需要改造)

public interface MainContract {
    interface View{

        void showLocationInfo(TaobaoIPLocationInfo taobaoIPLocationInfo);

        void showError(String message);
    }

    interface  presenter{

    }
}

2、建立Presenter(注意@Inject)

public class MainPresenter implements MainContract.presenter {

    private final MainContract.View view;
    private final SharedPreferences sharedPreferences;
    private final TaobaoIPLocationService locationService;
    private final UserService userService;

    //此處關鍵,用來提供Presenter 的例項化物件
    @Inject
    public MainPresenter(MainContract.View view,
                         //注入Default SharedPreferences
                         @Named("default") SharedPreferences sharedPreferences,
                         TaobaoIPLocationService locationService,
                         UserService userService) {
        this.view = view;
        this.sharedPreferences = sharedPreferences;
        this.locationService = locationService;
        this.userService = userService;
    }

    //IP定位測試
    public void main(){
        locationService.getIPInfo("myip")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<TaobaoIPLocationInfo>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        view.showError(e.getMessage());
                    }

                    @Override
                    public void onNext(TaobaoIPLocationInfo taobaoIPLocationInfo) {
                        view.showLocationInfo(taobaoIPLocationInfo);
                    }
                });

    }
}

3、Activity(需要關注addSub方法、Inject方法)


public class MainActivity extends AppCompatActivity implements MainContract.View{

    //注入presenter 物件
    @Inject
    MainPresenter mainPresenter;

    private TextView city;
    private TextView cityCode;
    private TextView ip;
    private TextView isp;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupActivityComponent();
        bindView();
        mainPresenter.main();
    }

    private void bindView() {
        city = (TextView) findViewById(R.id.city);
        cityCode = (TextView) findViewById(R.id.cityCode);
        ip = (TextView) findViewById(R.id.ip);
        isp = (TextView) findViewById(R.id.isp);
    }

    /**
     * 初始化屬於自己Activity的Component物件
     * 本例將MainComponent新增成為AppComponent的子Component
     */
    private void setupActivityComponent() {
        DaggerApplication.get(this)
                .getAppComponent()
                //將AppComponent繼承然後轉換成MainComponent
                //MainModule的構造器中傳遞的是View介面的例項化物件
                .addSub(new MainModule(this))
                //注入到當前類中
                .inject(this);
    }

    /**
     * MVP Presenter 中的回撥
     * @param taobaoIPLocationInfo IP定位後的返回資訊
     */
    @Override
    public void showLocationInfo(TaobaoIPLocationInfo taobaoIPLocationInfo) {
        city.setText(String.format("定位城市:%s", taobaoIPLocationInfo.getData().getCity()));
        cityCode.setText(String.format("定位城市程式碼:%s", taobaoIPLocationInfo.getData().getCity_id()));
        ip.setText(String.format("地位地區IP:%s", taobaoIPLocationInfo.getData().getIp()));
        isp.setText(String.format("isp服務提供商:%s", taobaoIPLocationInfo.getData().getIsp()));
    }

    /**
     * MVP Presenter 中的回撥
     */
    @Override
    public void showError(String message) {
        Toast.makeText(this,message,Toast.LENGTH_LONG).show();
    }
}

執行程式,然後看到下面的介面!Success

這裡寫圖片描述

程式碼已經上傳至Github,請下載後參照部落格文件,自行體會,有很多東西只可意會、不可言傳。