手把手帶你搭建Mvp+Dagger架構
0. 序言
- 之前寫過一篇名為"看完不會寫MVP架構我跪搓板"的博文,收到一些閱讀者的建議,希望能夠對Rxjava的生命週期進行管理以及新增Dagger到MVP架構中,所以今天抽一點時間寫一篇拿來即可於實戰的Demo。
1. 博文目錄
-
新增依賴
-
建立專案基本目錄
-
實現Model
-
定義契約介面NewsInfoContract
-
實現Presenter
-
實現View
-
補充網路配置程式碼
-
適配Android28網路請求
-
新增Dagger2
-
實現RetrofitManager單例
2. 新增依賴
implementation 'com.squareup.retrofit2:retrofit:2.5.0' implementation 'com.squareup.retrofit2:converter-gson:2.5.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.4' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'com.squareup.okhttp3:okhttp:3.12.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0' implementation 'com.trello.rxlifecycle2:rxlifecycle-android-lifecycle:2.2.1'
3. 建立專案基本目錄

Picture
4. 實現Model
- 建立實體類NewsInfo:
public class NewsInfo { private String reason; private ResultBean result; ... public static class ResultBean { private String stat; private List<DataBean> data; ... public static class DataBean { private String uniquekey; private String title; private String date; private String category; private String author_name; private String url; private String thumbnail_pic_s; private String thumbnail_pic_s02; private String thumbnail_pic_s03; ... } } }
- 定義獲取網路資料的介面類NetTask:
public interface NetTask { void execute(LifecycleProvider lifecycleProvider, String type, LoadTasksCallBack callBack); }
public interface LoadTasksCallBack { void OnSuccess(NewsInfo newsInfo); void OnStart(); void onFailed(); void onFinish(); }
- 編寫NetTask的實現類NewsInfoTask:
public class NewsInfoTask implements NetTask { private Disposable mDisposable; private NewsInfoTask() { } public static NewsInfoTask getInstance() { return NewsInfoTaskHolder.sNewsInfoFask; } private static class NewsInfoTaskHolder { private static final NewsInfoTask sNewsInfoFask = new NewsInfoTask(); } @Override public void execute(LifecycleProvider lifecycleProvider,String type, final LoadTasksCallBack callBack) { RetrofitManager.getInstance().getRetrofit(Constant.BASEURL).create(NewsService.class) .getNewsInfo(type, BuildConfig.NewKey) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .compose(lifecycleProvider.<Long>bindUntilEvent(Lifecycle.Event.ON_DESTROY)) .subscribe(new Observer<NewsInfo>() { @Override public void onSubscribe(Disposable disposable) { mDisposable= disposable; callBack.OnStart(); } @Override public void onNext(NewsInfo newsInfo) { callBack.OnSuccess(newsInfo); } @Override public void onError(Throwable e) { callBack.onFailed(); } @Override public void onComplete() { callBack.onFinish(); mDisposable.dispose(); } }); } }
6. 定義契約介面NewsInfoContract:
public interface NewsInfoContract { interface Presenter{ void getNewsInfo(LifecycleProvider lifecycleProvider, String type); } interface View { void setNewsInfo(NewsInfo newsInfo); void showLoading(); void hideLoading(); void showError(); } }
7. 實現Presenter
- 編寫NewsInfoContract.Presenter介面的實現類NewsInfoPresenter:
public class NewsInfoPresenter implements NewsInfoContract.Presenter,LoadTasksCallBack { private NetTask mNetTask; private NewsInfoContract.View mView; public NewsInfoPresenter(NetTask netTask,NewsInfoContract.View view) { mNetTask = netTask; mView = view; } @Override public void getNewsInfo(LifecycleProvider lifecycleProvider, String type) { mNetTask.execute(lifecycleProvider,type,this); } @Override public void OnSuccess(NewsInfo newsInfo) { mView.setNewsInfo(newsInfo); } @Override public void OnStart() { mView.showLoading(); } @Override public void onFailed() { mView.showError(); mView.hideLoading(); } @Override public void onFinish() { mView.hideLoading(); } }
6. 實現View
public class MainActivity extends AppCompatActivity implements NewsInfoContract.View { private NewsInfoContract.Presenter mPresenter = new NewsInfoPresenter(NewsInfoTask.getInstance(),this); LifecycleProvider<Lifecycle.Event> lifecycleProvider = AndroidLifecycle.createLifecycleProvider(this); private TextView mNew_Content; private Dialog mDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mNew_Content = findViewById(R.id.tv_content); mDialog = new ProgressDialog(this); mDialog.setTitle(R.string.dialog_get_info); findViewById(R.id.bt_get_news).setOnClickListener(v -> { mPresenter.getNewsInfo(lifecycleProvider,Constant.DEFAULT_TYPE); }); } @Override public void setNewsInfo(NewsInfo newsInfo) { if (newsInfo != null && newsInfo.getResult() != null && newsInfo.getResult().getData() != null) { mNew_Content.setText(newsInfo.getResult().getData().get(0).getTitle()); } } @Override public void showLoading() { mDialog.show(); } @Override public void hideLoading() { if (mDialog.isShowing()) mDialog.dismiss(); } @Override public void showError() { Toast.makeText(this, R.string.toast_net_tip, Toast.LENGTH_SHORT).show(); } }
7. 補充網路配置程式碼
- Retrofit 管理類:
public class RetrofitManager { private static Retrofit sRetrofit = null; private static String sUrl = ""; private static final int TIMEOUT = 20; private RetrofitManager() { } public static RetrofitManager getInstance() { return RetrofitManagerHolder.sInstance; } private static class RetrofitManagerHolder { private static final RetrofitManager sInstance = new RetrofitManager(); } public Retrofit getRetrofit(String baseUrl) { sUrl = baseUrl; if (sRetrofit == null) { return create(); } else { return sRetrofit; } } private Retrofit create() { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); if (BuildConfig.DEBUG) { loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); } else { loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC); } OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(loggingInterceptor) .connectTimeout(TIMEOUT,TimeUnit.SECONDS) .readTimeout(TIMEOUT, TimeUnit.SECONDS) .writeTimeout(TIMEOUT, TimeUnit.SECONDS) .retryOnConnectionFailure(true).build(); sRetrofit = new Retrofit.Builder() .baseUrl(sUrl) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) .build(); return sRetrofit; } }
- 網路請求介面服務類NewsService:
public interface NewsService { @GET("toutiao/index") Observable<NewsInfo> getNewsInfo(@Query("type") String type, @Query("key")String key); }
8. 適配Android28網路請求
- 在res目錄下建立名為xml的資料夾,並在資料夾裡面建立名為network_security_config的xml檔案:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
- AndroidManifest配置檔案中新增配置:
android:networkSecurityConfig="@xml/network_security_config"
9. 新增Dagger2
9.0 分析
這裡我們需要把NewsInfoTask和MainActivity注入到了Presenter中,把Presenter注入到了MainActivity中。
9.1 Dagger2實現NewsInfoTask單例
- 去掉NewsInfoTask以下程式碼:
private NewsInfoTask() { } public static NewsInfoTask getInstance() { return NewsInfoTaskHolder.sNewsInfoFask; } private static class NewsInfoTaskHolder { private static final NewsInfoTask sNewsInfoFask = new NewsInfoTask(); }
- 新建名為NetTaskModule的Module:
@Module public class NetTaskModule { @Singleton @Provides public NetTask provideNewsInfoTask(){ return new NewsInfoTask(); } }
說明:生成NewsInfoTask的例項物件,並用@Singleton修飾。
- 新建名為NetTaskComponent的Component:
@Singleton @Component(modules = NetTaskModule.class) public interface NetTaskComponent { NetTask getNetTask(); }
說明:
① NetTaskModule 中的方法provideNewsInfoTask用@Singleton修飾,NetTaskComponent也必須用@Singleton修飾。
② NetTaskComponent中不需要指明注入的目標,而需要提供例項物件的時候,可以用“NetTask getNetTask();”這種形式表示,指明返回的是NetTask,返回值很重要。
-
實現NewsInfoTask在App全域性單例:
@Singleton可以保證區域性單例,即NetTaskComponent下的注入目標中NewsInfoTask的記憶體地址都是同一個,而一旦建立其他Component並關聯NetTaskModule,此時創建出的NewsInfoTask的記憶體地址就會發生變化,所以保證全域性單例我們只能初始化一次Component,而初始化的地方就是Application:
public class App extends Application { private NetTaskComponent mNetTaskComponent; @Override public void onCreate() { super.onCreate(); mNetTaskComponent = DaggerNetTaskComponent.builder().build(); } public static App get(Context context){ return (App)context.getApplicationContext(); } public NetTaskComponent getNetTaskComponent() { return mNetTaskComponent; } }
說明:這裡通過.builder().build()來獲取NetTaskComponent。需要NewsInfoTask只通過NetTaskComponent,就可以保證NewsInfoTask物件的記憶體地址唯一了。
9.2 Presenter注入MainActivity
- @Inject修飾構造方法
@Inject public NewsInfoPresenter(NewsInfoContract.View view,NetTask netTask) { mView = view; mNetTask = netTask; }
說明:@Inject修飾構造方法意思是告訴Dagger2可以用這個構造方法構建NewsInfoPresenter。只有構造方法上有@Inject註解修飾,NewsInfoPresenter才可以對外提供例項物件。
- NewsInfoContract.View新增setPresenter方法
public interface NewsInfoContract { interface Presenter{ void getNewsInfo(LifecycleProvider lifecycleProvider, String type); } interface View { void setNewsInfo(NewsInfo newsInfo); void showLoading(); void hideLoading(); void showError(); void setPresenter(NewsInfoPresenter presenter); } }
- NewsInfoPresenter建立setPresenter方法,並用@Inject修飾
@Inject void setPresenter(){ mView.setPresenter(this); }
說明:@Inject修飾方法的意思是方法注入,這裡是把NewsInfoPresenter注入給MainActivity。方法注入是在構造方法後執行的。方法注入需要構造方法使用@Inject註解修飾,不然方法注入無法執行。
- MainActivity中用@Inject修飾變數NewsInfoPresenter:
@Inject NewsInfoPresenter mPresenter;
說明:@Inject修飾變數意思是NewsInfoPresenter需要依賴注入。
- MainActivity實現setPresenter方法
@Override public void setPresenter(NewsInfoPresenter presenter) { mPresenter = presenter; }
綜上:以上幾步完成了Persenter注入到MainActivity。
9.3 NewsInfoTask和MainActivity注入到了Presenter
- 建立名為ActivityScoped的自定義Scope:
@Scope @Documented @Retention(RUNTIME) public @interface ActivityScoped { }
- 建立NewsInfoModule的Module:
@Module public class NewsInfoModule { private NewsInfoContract.View mView; public NewsInfoModule(NewsInfoContract.View view) { mView = view; } @Provides public NewsInfoContract.View provideNewsInfoContractView() { return mView; } }
說明:這裡是為了將NewsInfoContract.View注入給MainActivity。
- 建立名為MainActivityComponent的Component:
@ActivityScoped @Component(modules = NewsInfoModule.class,dependencies =NetTaskComponent.class) public interface MainActivityComponent { void inject(MainActivity mainActivity); }
說明:
① dependencies意思是把NetTaskComponent的內容也拿過來注入。
② @ActivityScoped之所以建立自定義Scope是因為如果dependencies中的NetTaskComponent用了@Singleton修飾,這裡就不能使用@Singleton修飾了。
- 修改MainActivity完成注入:
DaggerMainActivityComponent.builder().newsInfoModule(new NewsInfoModule(this)) .netTaskComponent(App.get(this).getNetTaskComponent()).build().inject(this);
說明:因為Presenter需要兩個引數,所以這裡一句話就把需要的兩個引數傳入了Presenter。我們看下DaggerMainActivityComponent的原始碼:
public Builder newsInfoModule(NewsInfoModule newsInfoModule) { this.newsInfoModule = Preconditions.checkNotNull(newsInfoModule); return this; } public Builder netTaskComponent(NetTaskComponent netTaskComponent) { this.netTaskComponent = Preconditions.checkNotNull(netTaskComponent); return this; }
說明:.newsInfoModule和.netTaskComponent接收Presenter需要的兩個引數。然後看build():
public MainActivityComponent build() { if (newsInfoModule == null) { throw new IllegalStateException(NewsInfoModule.class.getCanonicalName() + " must be set"); } if (netTaskComponent == null) { throw new IllegalStateException(NetTaskComponent.class.getCanonicalName() + " must be set"); } return new DaggerMainActivityComponent(this); }
說明:這裡把build()所在的Build傳入了DaggerMainActivityComponent的構造方法,我們看下:
private DaggerMainActivityComponent(Builder builder) { assert builder != null; initialize(builder); }
說明:再看下initialize方法:
this.provideNewsInfoContractViewProvider = NewsInfoModule_ProvideNewsInfoContractViewFactory.create(builder.newsInfoModule); this.getNetTaskProvider = new Factory<NetTask>() { private final NetTaskComponent netTaskComponent = builder.netTaskComponent; @Override public NetTask get() { return Preconditions.checkNotNull( netTaskComponent.getNetTask(), "Cannot return null from a non-@Nullable component method"); } }; this.newsInfoPresenterProvider = NewsInfoPresenter_Factory.create( newsInfoPresenterMembersInjector, provideNewsInfoContractViewProvider, getNetTaskProvider);
說明:newsInfoModule得到provideNewsInfoContractViewProvider,netTaskComponent得到getNetTaskProvider,然後把兩者,放入NewsInfoPresenter_Factory.create方法中:
public static Factory<NewsInfoPresenter> create( MembersInjector<NewsInfoPresenter> newsInfoPresenterMembersInjector, Provider<NewsInfoContract.View> viewProvider, Provider<NetTask> netTaskProvider) { return new NewsInfoPresenter_Factory( newsInfoPresenterMembersInjector, viewProvider, netTaskProvider); }
說明:再看NewsInfoPresenter_Factory
public NewsInfoPresenter_Factory( MembersInjector<NewsInfoPresenter> newsInfoPresenterMembersInjector, Provider<NewsInfoContract.View> viewProvider, Provider<NetTask> netTaskProvider) { assert newsInfoPresenterMembersInjector != null; this.newsInfoPresenterMembersInjector = newsInfoPresenterMembersInjector; assert viewProvider != null; this.viewProvider = viewProvider; assert netTaskProvider != null; this.netTaskProvider = netTaskProvider; }
說明:用viewProvider封裝了NewsInfoContract.View;用netTaskProvider封裝了NetTask。他們呼叫給了get方法:
@Override public NewsInfoPresenter get() { return MembersInjectors.injectMembers( newsInfoPresenterMembersInjector, new NewsInfoPresenter(viewProvider.get(), netTaskProvider.get())); }
說明:看到這裡,你會清晰的看到NetTask和MainActivity注入到了Presenter,而這個get方法何時呼叫的呢?我們看下inject方法:
@Override public void inject(MainActivity mainActivity) { mainActivityMembersInjector.injectMembers(mainActivity); }
說明:再接著看injectMembers方法:
@Override public void injectMembers(MainActivity instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.mPresenter = mPresenterProvider.get(); }
說明:mPresenterProvider是誰呢:
private final Provider<NewsInfoPresenter> mPresenterProvider;
說明:到這裡會發現其實inject的目的就是把兩個引數賦值給this中的presenter,而賦值離不開new,自然離不開構造方法,而構造方法那裡也離不開@Inject。而這也正是Dagger2的原理所在,通過工廠方法把例項物件賦值給注入目標的用@Inject所修飾的成員變數。
10. 實現RetrofitManager單例
- 去除之前實現單例模式的以下程式碼
private RetrofitManager() { } public static RetrofitManager getInstance() { return RetrofitManagerHolder.sInstance; } private static class RetrofitManagerHolder { private static final RetrofitManager sInstance = new RetrofitManager(); }
- NetTaskModule中新增以下方法:
@Singleton @Provides public RetrofitManager provideRetrofitManager(){ return new RetrofitManager(); }
- NetTaskComponent中新增以下方法:
@Singleton @Component(modules = NetTaskModule.class) public interface NetTaskComponent { NetTask getNetTask(); RetrofitManager getRetrofitManager(); }
- 修改Applicaion的程式碼:
public class App extends Application { private static NetTaskComponent mNetTaskComponent; @Override public void onCreate() { super.onCreate(); mNetTaskComponent = DaggerNetTaskComponent.builder().build(); } public static NetTaskComponent getNetTaskComponent() { return mNetTaskComponent; } }
說明:之前的方式需要傳入Context,但是NewsInfoTask中並沒有Context,所以我們這裡修改為static,因為是單例模式,從建立就開始存在直到App應用程式退出,所以不會有記憶體洩露的情況。
- MainActivity和NewsInfoTask中修改獲取NetTaskComponent的程式碼。