出來混早晚要還的,技術債Dagger2:Android篇(上)

前言

由於工做需求,因此最近補了補以前沒了解過的Dagger2的內容,基礎篇已經發布。接下來就是Dagger2在Android中的應用了。固然,和我同樣剛接觸Dagger2的朋友,能夠先看一下以前的基礎文章:java

出來混早晚要還的,技術債Dagger2:基礎篇android

正文

這篇文章的Demo實在是太好了。因此我就厚顏無恥的把他的代碼拿過來用...這是一個外國哥們的文章,我猜他應該不會怪個人,哈哈...緩存

原文地址:Dagger 2 for Android Beginners — Advanced part I網絡

進入正文以前,咱們先看一下背景。代碼需求很簡單,從一個API上獲取數據,而後加載到RecycleView上,而且會涉及到圖片加載。 在這麼一個需求之下,咱們若是使用Dagger2爲咱們的工程提供相應的依賴呢?dom

簡單羅列一下代碼設計ide

  • MainActivity.java:請求API並顯示項目 RecyclerView
  • Result.java:用於API響應的POJO,使用JSON Schema建立到POJO
  • RandomUsersAdapter.java:適配器 RecyclerView

涉及如下依賴項和庫。post

  • Retrofit
  • GsonBuilder&Gson
  • HttpLoggingInterceptor
  • OkHttpClient
  • Picasso

以上內容不重要,都是我們平常開發經常使用的東西。怎麼使用啥的,你們確定都很屬性,因此下文demo不少初始化啥的就跳過了,我們的關注是Dagger2在Android中的應用。gradle

注意,這裏不是叭叭叭的貼代碼,講API,而是從一個業務出發,以Dagger2的角度講解Daager2的依賴關係,相信大家會我和同樣,看完必定會「撥雲霧見青天」,哈哈~ui

通常解決方案

面對這種需求,咱們的常規寫法:this

public class MainActivity extends AppCompatActivity {
    // 省略變量聲明
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 省略View的初始化
        // 省略Gson初始化
        // 省略HttpLoggingInterceptor及OkHttpClient初始化
        // 不省略太多了,省得失去代入感,哈哈。
        retrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl("https://randomuser.me/")
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

        populateUsers();
    }
    
    // 網絡請求
    private void populateUsers() {
        Call<RandomUsers> randomUsersCall = getRandomUserService().getRandomUsers(10);
        randomUsersCall.enqueue(new Callback<RandomUsers>() {
            @Override
            public void onResponse(Call<RandomUsers> call, @NonNull Response<RandomUsers> response) {
                if(response.isSuccessful()) {
                    mAdapter = new RandomUserAdapter();
                    mAdapter.setItems(response.body().getResults());
                    recyclerView.setAdapter(mAdapter);
                }
            }
            // 省略請求失敗
        });
    }

    public RandomUsersApi getRandomUserService(){
        return retrofit.create(RandomUsersApi.class);
    }
}
複製代碼
public class RandomUserAdapter extends RecyclerView.Adapter<RandomUserAdapter.RandomUserViewHolder> {
    private List<Result> resultList = new ArrayList<>();

    public RandomUserAdapter() {}

    @Override
    public RandomUserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_random_user,
                parent, false);
        return new RandomUserViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RandomUserViewHolder holder, int position) {
        Result result = resultList.get(position);
        // setText操做
        holder.textView.setText(String.format("%s %s", result.getName().getFirst(),
                result.getName().getLast()));
        // 圖片庫加載圖片
        Picasso.with(holder.imageView.getContext())
                .load(result.getPicture().getLarge())
                .into(holder.imageView);
    }
    
    // 省略部分代碼
}
複製代碼

寫完上述代碼以後,讓咱們挺一分鐘,想想咱們剛纔寫的東西,是否是有明顯的依賴關係?好比,咱們的Activity依賴Retrofit,咱們的Retrofit又依賴OkHttp等等這種關係。

並且,全部的初始化操做都其中在Activity作了處理,若是此時咱們須要更多的Activity,難道還有一遍遍寫重複的代碼?

固然可能有朋友會說可使用基類,或者單例等等的封裝方式。不過今天咱們統統不考慮這些,今天只聊Dagger2

上述的業務其實還不少內容沒有考慮到,好比說緩存...所以咱們的業務若是更精細一些會發現,還有依賴更多的模塊:

  • File/DB: 持久化緩存
  • 內存Cache: 內存緩存
  • OkHttp3Downloader: 下載模塊

因此若是完整的展開代碼,應該是這樣的:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
        // Gson依賴
        GsonBuilder gsonBuilder = new GsonBuilder();
        Gson gson = gsonBuilder.create();
        // File持久化依賴
        File cacheFile = new File(this.getCacheDir(), "HttpCache");
        cacheFile.mkdirs();
        // Cache內存依賴
        Cache cache = new Cache(cacheFile, 10 * 1000 * 1000); //10 MB
        // Log依賴
        HttpLoggingInterceptor httpLoggingInterceptor = new
                HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(@NonNull String message) {
                Timber.i(message);
            }
        });
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        // OkHttp依賴
        OkHttpClient okHttpClient = new OkHttpClient()
                .newBuilder()
                .cache(cache)
                .addInterceptor(httpLoggingInterceptor)
                .build();
        OkHttp3Downloader okHttpDownloader = new OkHttp3Downloader(okHttpClient);
        // Picasso依賴
        picasso = new Picasso.Builder(this).downloader(okHttpDownloader).build();
        // Retrofit依賴
        retrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl("https://randomuser.me/")
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

        populateUsers();
    }
複製代碼

做爲「久經沙場」的老司機,這些都是屢見不鮮,便飯之餘咱們聊一些茶語飯後的話題:從這些初始化代碼中抽象出一個依賴圖。就若是Retrofit初始化時傳了一個Gson,那就說明Retrofit依賴Gson...

所以,咱們差很少可以梳理一個依賴關係圖:

綠色框表示它們是依賴關係中的頂級的(任何模塊都不想要依賴它),它們只會被依賴。 結合咱們寫過的初始化代碼,這個圖很好理解吧?我猜確定有朋友這張圖都已經在寫下代碼之時出如今腦海中了...

走到這一步,其實問題就已經顯現出來了。這麼龐大的初始化的過程,任誰都不會想再寫第二遍。所以重構迫在眉睫。既然咱們都已經捋清楚了咱們所須要模塊的依賴關係,那麼接下來就是讓Dagger2大展身手的時候了...

Dagger2登場

前置準備

最開始固然是引入咱們的Dagger2模塊,沒啥好說的...

dependencies {
    implementation 'com.google.dagger:dagger:2.13'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
}
複製代碼

第1步:建立組件(Component)

組件將充當整個依賴關係圖的公共接口。使用組件的最佳實踐是僅暴露出最頂級的依賴關係。

這意味着,在Component中咱們只提供在依賴圖中綠色辨識的類。也就是:RandomUsersAPI和Picasso。

建立一個名爲RandomUserComponent的組件並對外暴露RandomUsersApiPicasso

@Component
public interface RandomUserComponent {
    RandomUsersApi getRandomUserService();
    Picasso getPicasso();
}
複製代碼

Component將提供最頂層的依賴:RandomUsersApi和Picasso。

第2步:建立模塊(Module)

咱們如今須要將MainActivity中的代碼移動到不一樣的模塊去。因此接下來,咱們須要基於依賴圖去設計咱們須要哪些模塊。

首先是,RandomUsersModule

經過圖,咱們能夠基本設計出來 RandomUsersModule,構建它咱們須要提供 RandomUsersApiGsonConverterFactoryGsonRetrofit以及一個 OkHttpClient

@Module
public class RandomUsersModule {

    @Provides
    public RandomUsersApi randomUsersApi(Retrofit retrofit){
        return retrofit.create(RandomUsersApi.class);
    }

    @Provides
    public Retrofit retrofit(OkHttpClient okHttpClient, GsonConverterFactory gsonConverterFactory, Gson gson){
        return new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl("https://randomuser.me/")
                .addConverterFactory(gsonConverterFactory)
                .build();
    }

    @Provides
    public Gson gson(){
        GsonBuilder gsonBuilder = new GsonBuilder();
        return gsonBuilder.create();
    }

    @Provides
    public GsonConverterFactory gsonConverterFactory(Gson gson){
        return GsonConverterFactory.create(gson);
    }
}
複製代碼

寫到這,咱們會發現,想要提供一個OkHttpClient須要提供太多的依賴,所以讓咱們建立一個OkHttpClientModule,它提供OkHttpClientCacheHttpLoggingInterceptorFile以及一個Context

@Module
public class OkHttpClientModule {

    @Provides
    public OkHttpClient okHttpClient(Cache cache, HttpLoggingInterceptor httpLoggingInterceptor){
        return new OkHttpClient()
                .newBuilder()
                .cache(cache)
                .addInterceptor(httpLoggingInterceptor)
                .build();
    }

    @Provides
    public Cache cache(File cacheFile){
        return new Cache(cacheFile, 10 * 1000 * 1000); //10 MB
    }

    @Provides
    public File file(Context context){
        File file = new File(context.getCacheDir(), "HttpCache");
        file.mkdirs();
        return file;
    }

    @Provides
    public HttpLoggingInterceptor httpLoggingInterceptor(){
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                Timber.d(message);
            }
        });
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        return httpLoggingInterceptor;
    }
}
複製代碼

寫完這個,咱們會發現沒辦法給它提供Context,如今先不要着急,由於咱們發現彷佛還有一個模塊也須要Context

沒錯,就是Picasso,讓咱們建立一個PicassoModule,而且爲它提供:PicassoOkHttp3Downloader

@Module
public class PicassoModule {

    @Provides
    public Picasso picasso(Context context, OkHttp3Downloader okHttp3Downloader){
        return new Picasso.Builder(context).
                downloader(okHttp3Downloader).
                build();
    }

    @Provides
    public OkHttp3Downloader okHttp3Downloader(OkHttpClient okHttpClient){
        return new OkHttp3Downloader(okHttpClient);
    }
}
複製代碼

OK,咱們的高層模塊已準備就緒,不過你們還記不記得一個遺留問題:PicassoModuleOkHttpClientModule需求ContextContext的地位不言而喻,所以咱們必定會遇到其餘模塊也須要Context的情形。那麼爲何不給它一個模塊呢?

@Module
public class ContextModule {
    Context context;

    public ContextModule(Context context){
        this.context = context;
    }

    @Provides
    public Context context(){ return context.getApplicationContext(); }
}
複製代碼

到此,咱們第2步的準備工做就完成了,接下來咱們須要讓它們須要互相提供依賴!

第3步:鏈接全部模塊

如今,咱們已經準備好全部模塊和組件:

可是咱們讓彼此獨立的模塊,互相依賴呢?這是includes屬性發揮做用的地方。includes屬性包括當前模塊中涉及的其餘模塊的依賴關係。

什麼模塊須要包括在內?

  • RandomUsersModule須要OkHttpClientModule
  • OkHttpClientModule須要ContextModule
  • PicassoModule須要OkHttpClientModuleContextModule。但因爲已經OkHttpClientModule與之相關ContextModule,因此咱們只包括OkHttpClientModule
//in RandomUsersModule.java
@Module(includes = OkHttpClientModule.class)
public class RandomUsersModule { ... }

//in OkHttpClientModule.java
@Module(includes = ContextModule.class)
public class OkHttpClientModule { ... }

//in PicassoModule.java
@Module(includes = OkHttpClientModule.class)
public class PicassoModule { ... }
複製代碼

經過提供上述內容,咱們已經連接了全部模塊。

第4步:連通組件

如今,咱們全部的模塊(Module)都已鏈接,能夠相互依賴了。那麼接下來,咱們只需告訴咱們的頂層組件RandomUserComponent,須要依賴哪些模塊來使本身可以正常工做。

有了第3步的基礎,這裏應該很容易捋清關係吧?只不過這裏不用includes,而是用modules。

@Component(modules = {RandomUsersModule.class, PicassoModule.class})
public interface RandomUserComponent {
    RandomUsersApi getRandomUserService();
    Picasso getPicasso();
}
複製代碼

第5步:Build就好了

是時候Build工程了。Dagger將使用Builder模式建立RandomUserComponent。如今,咱們的MainActivity能夠很容易地得到Picasso並RandomUsersApi

public class MainActivity extends AppCompatActivity {
  RandomUsersApi randomUsersApi;
  Picasso picasso;
  ....
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        RandomUserComponent daggerRandomUserComponent = DaggerRandomUserComponent.builder()
                .contextModule(new ContextModule(this))
                .build();
        picasso = daggerRandomUserComponent.getPicasso();
        randomUsersApi = daggerRandomUserComponent.getRandomUserService();
        populateUsers();
        ...
    }
  ...
}
複製代碼

大功告成,咱們很「魔法」的完成了依賴注入,就醬,是否是cao簡單噠?...

可是

每次調用<DaggerComponent>.build()時,它都會建立全部對象或依賴項的新實例,咱們都很清楚,這些內容單例就行了。爲何Dagger2不知道咱們只須要一個Picasso單例呢?

換句話說,咱們如何告訴Dagger2爲咱們提供單實例依賴?這就是下一篇內容所要涉及的內容~

尾聲

說實話,關於Dagger2怎麼說呢?要不是由於組裏有一個Dagger2大佬,還真沒信心沒動力去搞它。不過既然有資源,那就學一學吧。

唉,別tm更新啦,學不動啦~

我的公衆號:鹹魚正翻身
相關文章
相關標籤/搜索