由於工做需求,因此最近補了補以前沒了解過的Dagger2的內容,基礎篇已經發布。接下來就是Dagger2在Android中的應用了。固然,和我同樣剛接觸Dagger2的朋友,能夠先看一下以前的基礎文章:java
出來混早晚要還的,技術債Dagger2:基礎篇android
這篇文章的Demo實在是太好了。因此我就厚顏無恥的把他的代碼拿過來用...這是一個外國哥們的文章,我猜他應該不會怪個人,哈哈...緩存
進入正文以前,咱們先看一下背景。代碼需求很簡單,從一個API上獲取數據,而後加載到RecycleView上,而且會涉及到圖片加載。 在這麼一個需求之下,咱們若是使用Dagger2爲咱們的工程提供相應的依賴呢?dom
簡單羅列一下代碼設計ide
涉及如下依賴項和庫。post
以上內容不重要,都是我們平常開發經常使用的東西。怎麼使用啥的,你們確定都很屬性,因此下文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
上述的業務其實還不少內容沒有考慮到,好比說緩存...所以咱們的業務若是更精細一些會發現,還有依賴更多的模塊:
因此若是完整的展開代碼,應該是這樣的:
@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模塊,沒啥好說的...
dependencies {
implementation 'com.google.dagger:dagger:2.13'
annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
}
複製代碼
組件將充當整個依賴關係圖的公共接口。使用組件的最佳實踐是僅暴露出最頂級的依賴關係。
這意味着,在Component中咱們只提供在依賴圖中綠色辨識的類。也就是:RandomUsersAPI和Picasso。
建立一個名爲RandomUserComponent
的組件並對外暴露RandomUsersApi
和Picasso
。
@Component
public interface RandomUserComponent {
RandomUsersApi getRandomUserService();
Picasso getPicasso();
}
複製代碼
Component將提供最頂層的依賴:RandomUsersApi和Picasso。
咱們如今須要將MainActivity
中的代碼移動到不一樣的模塊去。因此接下來,咱們須要基於依賴圖去設計咱們須要哪些模塊。
首先是,RandomUsersModule
:
RandomUsersModule
,構建它咱們須要提供
RandomUsersApi
、
GsonConverterFactory
、
Gson
和
Retrofit
以及一個
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
,它提供OkHttpClient
、Cache
、HttpLoggingInterceptor
和File
以及一個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
,而且爲它提供:Picasso
和OkHttp3Downloader
。
@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,咱們的高層模塊已準備就緒,不過你們還記不記得一個遺留問題:PicassoModule
和OkHttpClientModule
需求Context
。Context
的地位不言而喻,所以咱們必定會遇到其餘模塊也須要Context
的情形。那麼爲何不給它一個模塊呢?
@Module
public class ContextModule {
Context context;
public ContextModule(Context context){
this.context = context;
}
@Provides
public Context context(){ return context.getApplicationContext(); }
}
複製代碼
到此,咱們第2步的準備工做就完成了,接下來咱們須要讓它們須要互相提供依賴!
如今,咱們已經準備好全部模塊和組件:
可是咱們讓彼此獨立的模塊,互相依賴呢?這是includes屬性發揮做用的地方。includes屬性包括當前模塊中涉及的其餘模塊的依賴關係。
什麼模塊須要包括在內?
RandomUsersModule
須要OkHttpClientModule
OkHttpClientModule
須要ContextModule
PicassoModule
須要OkHttpClientModule
和ContextModule
。但因爲已經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 { ... }
複製代碼
經過提供上述內容,咱們已經連接了全部模塊。
如今,咱們全部的模塊(Module)都已鏈接,能夠相互依賴了。那麼接下來,咱們只需告訴咱們的頂層組件RandomUserComponent
,須要依賴哪些模塊來使本身可以正常工做。
有了第3步的基礎,這裏應該很容易捋清關係吧?只不過這裏不用includes,而是用modules。
@Component(modules = {RandomUsersModule.class, PicassoModule.class})
public interface RandomUserComponent {
RandomUsersApi getRandomUserService();
Picasso getPicasso();
}
複製代碼
是時候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更新啦,學不動啦~