前言
MVP是Android端的一种架构模式,RRD = Rxjava + Retrofit + Dagger2这套全家桶,GeekNews就是基于以上框架搭建的阅读APP,阅读内容主要面向程序员 & 极客,目前包括:
- 知乎日报 — 汇集知乎上的热门话题与新鲜事,板块众多
- 微信精选 — 汇集微信上的海量精选新闻资讯,支持搜索
- 干货集中营 — 或许是国内第三方客户端最多的干货分享地,内含福利
- 稀土掘金 — 目前最火热的技术分发平台
- V2EX — 一个关于分享和探索的地方,创意工作者们的社区
下面分享一些搭建这个项目时的心得体会:
分包
- app ——
Application
对象与全局常量
- base ——
Activity
,Fragment
,Presenter
的基类
- component —— 基础组件,如更新检测
Service
,异步初始化Service
,图片加载封装,错误处理封装,缓存器,RxBus
等
- di —— 基于
Dagger2
的依赖注入
- model —— 对应MVP的
model
层,存放网络,数据库相关的类,以及实体类bean
- presenter —— 对应MVP的
presenter
层,存放Presenter
的具体实现以及契约类Contract
- ui —— 对应MVP的
view
层,存放具体的Activity
,Presenter
,Adapter
- util —— 工具类
- widget —— 自定义控件或组件
MVP
在传统的MVC(model-view-controller)模式中,Activity既承担了View的作用,又承担了大部分Controller的工作,一些业务复杂的Activity动辄就是数千行代码,大量的逻辑与视图操作分散在其中,耦合严重,非常不利于后期的扩展以及重构,在这种环境下,诞生了更适合Android开发场景的MVP(model-view-presenter)模式
先说下MVP的核心思路:View与Model不直接交互,Presenter充当两者的桥梁,专注逻辑的处理,所有的UI操作均抽象为view层的接口,所有的交互操作均抽象为presenter层的接口,model层的职责不变,负责提供网络、内存、本地磁盘中的数据
MVP模式的好处:
Contract
契约类中定义了V层与P层的接口,所有功能一目了然,也能方便快速定位
- Presenter与View均面向接口编程,可以随时替换具体实现,方便单元测试或业务变更
Activity
与Fragment
充分减压,代码量分摊到了Presenter
中,方便阅读、debug、扩展功能
- 独立出的
Presenter
层正好可以和RxJava
配合,使用操作符高效处理各种逻辑,还可以把Presenter的生命周期同一系列Observable
订阅绑定,防止内存泄漏
- View层变的更加纯净、被动,只负责响应视图变化,响应生命周期,更符合前端目前火热的响应式的理念
上面都是我在使用MVP模式时的一些体会,下面开始聊聊MVP在GeekNews中的应用,这里参考了Google官方的android-architecture项目,用契约类Contract
来统一管理P层与V层的接口,也参考了开源项目Hot的做法,把泛型进一步融入到了MVP中
首先定义View层的基类BaseView
1 2 3 4 5 6 7
| public interface BaseView { void showError(String msg); void useNightMode(boolean isNight); }
|
定义了两个接口方法showError
,useNightMode
,意味着所有的视图层都要求具有提示错误信息与切换夜间模式的能力
接着定义Presenter层的基类BasePresenter
1 2 3 4 5 6
| public interface BasePresenter<T extends BaseView>{ void attachView(T view); void detachView(); }
|
定义了两个接口方法attachView
,detactView
,V层与P层是互相持有彼此的引用的,Presenter在View创建后与之绑定,在View销毁时解绑,这里也算定义了Presenter的生命周期
接下来为了让Presenter更好的配合RxJava
,使订阅时间的生命周期同Presenter绑定,防止内存泄漏,进一步封装出了RxPresenter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class RxPresenter<T extends BaseView> implements BasePresenter<T> { protected T mView; protected CompositeSubscription mCompositeSubscription; protected void unSubscribe() { if (mCompositeSubscription != null) { mCompositeSubscription.unsubscribe(); } } protected void addSubscrebe(Subscription subscription) { if (mCompositeSubscription == null) { mCompositeSubscription = new CompositeSubscription(); } mCompositeSubscription.add(subscription); } protected <U> void addRxBusSubscribe(Class<U> eventType, Action1<U> act) { if (mCompositeSubscription == null) { mCompositeSubscription = new CompositeSubscription(); } mCompositeSubscription.add(RxBus.getDefault().toDefaultObservable(eventType, act)); } @Override public void attachView(T view) { this.mView = view; } @Override public void detachView() { this.mView = null; unSubscribe(); } }
|
使用CompositeSubscription
来收集所有订阅事件,在解绑时统一取消订阅
接着定义Activity
与Fragment
的基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| public abstract class BaseActivity<T extends BasePresenter> extends SupportActivity implements BaseView{ @Inject protected T mPresenter; protected Activity mContext; private Unbinder mUnBinder; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayout()); mUnBinder = ButterKnife.bind(this); mContext = this; initInject(); if (mPresenter != null) mPresenter.attachView(this); App.getInstance().addActivity(this); initEventAndData(); } @Override protected void onResume() { super.onResume(); } protected void setToolBar(Toolbar toolbar, String title) { toolbar.setTitle(title); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { onBackPressedSupport(); } }); } protected ActivityComponent getActivityComponent(){ return DaggerActivityComponent.builder() .appComponent(App.getAppComponent()) .activityModule(getActivityModule()) .build(); } protected ActivityModule getActivityModule(){ return new ActivityModule(this); } @Override protected void onDestroy() { super.onDestroy(); if (mPresenter != null) mPresenter.detachView(); mUnBinder.unbind(); App.getInstance().removeActivity(this); } @Override public void useNightMode(boolean isNight) { if (isNight) { AppCompatDelegate.setDefaultNightMode( AppCompatDelegate.MODE_NIGHT_YES); } else { AppCompatDelegate.setDefaultNightMode( AppCompatDelegate.MODE_NIGHT_NO); } recreate(); } protected abstract void initInject(); protected abstract int getLayout(); protected abstract void initEventAndData(); }
|
为了规范引入了Presenter泛型,强制要求所有Activity,Fragment都要定义与之对应的Presenter,并在基类中同步了两者的生命周期,除此之外还做了Presenter注入,ButterKnife,Toolbar初始化等一系列工作
但是在我们的应用中也存在一些偏静态的页面,它们不与数据层交互,只用作展示页面,总不能强制它们去定义空实现的Presenter吧,所以这里还定义了不带泛型的SimpleActivity
,SimpleFragment
用于无数据交互页面的基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public abstract class SimpleActivity extends SupportActivity { protected Activity mContext; private Unbinder mUnBinder; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayout()); mUnBinder = ButterKnife.bind(this); mContext = this; App.getInstance().addActivity(this); initEventAndData(); } protected void setToolBar(Toolbar toolbar, String title) { toolbar.setTitle(title); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { onBackPressedSupport(); } }); } @Override protected void onDestroy() { super.onDestroy(); App.getInstance().removeActivity(this); mUnBinder.unbind(); } protected abstract int getLayout(); protected abstract void initEventAndData(); }
|
至此,所有基类都定义好了,在实际使用时只要定义好Contract
类中的接口,让View层继承BaseActivity
并实现Contract.View
接口,让Present层继承RxPresenter
并实现Contract.Presentr
接口即可,下面以主界面为例:
定义Contract
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public interface MainContract { interface View extends BaseView{ void showUpdateDialog(String versionContent); void startDownloadService(); } interface Presenter extends BasePresenter<View> { void checkVersion(String currentVersion); void checkPermissions(RxPermissions rxPermissions); } }
|
实现View层MainActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class MainActivity extends BaseActivity<MainPresenter> implements MainContract.View{ @Override protected void initInject() { getActivityComponent().inject(this); } @Override protected int getLayout() { return R.layout.activity_main; } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override protected void initEventAndData() { } @Override public void showError(String msg) { } @Override public void showUpdateDialog(String versionContent) { } @Override public void startDownloadService() { } }
|
实现Presenter层MainPresenter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| public class MainPresenter extends RxPresenter<MainContract.View> implements MainContract.Presenter{ private RetrofitHelper mRetrofitHelper; @Inject public MainPresenter(RetrofitHelper mRetrofitHelper) { this.mRetrofitHelper = mRetrofitHelper; registerEvent(); } void registerEvent() { Subscription rxSubscription = RxBus.getDefault().toObservable(NightModeEvent.class) .compose(RxUtil.<NightModeEvent>rxSchedulerHelper()) .map(new Func1<NightModeEvent, Boolean>() { @Override public Boolean call(NightModeEvent nightModeEvent) { return nightModeEvent.getNightMode(); } }) .subscribe(new CommonSubscriber<Boolean>(mView, "切换模式失败ヽ(≧Д≦)ノ") { @Override public void onNext(Boolean aBoolean) { mView.useNightMode(aBoolean); } }); addSubscrebe(rxSubscription); } @Override public void checkVersion(final String currentVersion) { Subscription rxSubscription = mRetrofitHelper.fetchVersionInfo() .compose(RxUtil.<MyHttpResponse<VersionBean>>rxSchedulerHelper()) .compose(RxUtil.<VersionBean>handleMyResult()) .filter(new Func1<VersionBean, Boolean>() { @Override public Boolean call(VersionBean versionBean) { return Integer.valueOf(currentVersion.replace(".", "")) < Integer.valueOf(versionBean.getCode().replace(".", "")); } }) .map(new Func1<VersionBean, String>() { @Override public String call(VersionBean bean) { StringBuilder content = new StringBuilder("版本号: v"); content.append(bean.getCode()); content.append("\r\n"); content.append("版本大小: "); content.append(bean.getSize()); content.append("\r\n"); content.append("更新内容:\r\n"); content.append(bean.getDes().replace("\\r\\n","\r\n")); return content.toString(); } }) .subscribe(new CommonSubscriber<String>(mView) { @Override public void onNext(String s) { mView.showUpdateDialog(s); } }); addSubscrebe(rxSubscription); } @Override public void checkPermissions(RxPermissions rxPermissions) { Subscription rxSubscription = rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE) .subscribe(new Action1<Boolean>() { @Override public void call(Boolean granted) { if (granted) { mView.startDownloadService(); } else { mView.showError("下载应用需要文件写入权限哦~"); } } }); addSubscrebe(rxSubscription); } }
|
model层的实例由Dagger2
注入到Presenter,由Presenter调用方法来控制存取
RxJava + Retrofit
和上面的MVP一样,依旧先总结下使用RxJava的好处:
- 链式编程,直观、清晰、有序、简洁(逻辑上),即使是很复杂的逻辑,一个一个操作符传递下去也十分利于阅读,团队协作时能快速理解别人的逻辑
- 解决回调地狱(callback hell),避免层层嵌套,还有比层层嵌套更丑陋的代码块么
- 方便的线程切换,并且可以主线程子线程间多次来回切换,试想用传统的
AsycTask/Handler
做多次来回切换是不是菊花一紧?
- 与Presenter层契合,用操作符依照逻辑在P层处理数据流,还能为所有订阅统一绑定生命周期,防止内存泄漏
- 很多第三方开源库开始支持RxJava,与
Retrofit
等可以充分配合
- 附赠RxBus做事件总线
链式调用写起来很过瘾
关于RxJava和Retrofit的用法这里就不赘述了,已经有很多文章介绍过基本用法了,这里主要指明在GeekNews中做了哪些封装来让RxJava+Retrofit
用起来更方便:
- 封装线程操作
- 封装返回结果的统一处理
- 封装错误的统一处理
代码分别如下:
1 2 3 4 5 6 7 8 9 10
| public static <T> Observable.Transformer<T, T> rxSchedulerHelper() { return new Observable.Transformer<T, T>() { @Override public Observable<T> call(Observable<T> observable) { return observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } }; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static <T> Observable.Transformer<GankHttpResponse<T>, T> handleResult() { return new Observable.Transformer<GankHttpResponse<T>, T>() { @Override public Observable<T> call(Observable<GankHttpResponse<T>> httpResponseObservable) { return httpResponseObservable.flatMap(new Func1<GankHttpResponse<T>, Observable<T>>() { @Override public Observable<T> call(GankHttpResponse<T> tGankHttpResponse) { if(!tGankHttpResponse.getError()) { return createData(tGankHttpResponse.getResults()); } else { return Observable.error(new ApiException("服务器返回error")); } } }); } }; }
|
上面两个方法都可以借助RxJava的compose
操作符直接调用,compose
操作符的作用就是允许我们在Transformer
对象中封装一系列的操作,从而在不打破链式调用的前提下封装一些重复性的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public abstract class CommonSubscriber<T> extends Subscriber<T> { private BaseView mView; private String mErrorMsg; protected CommonSubscriber(BaseView view){ this.mView = view; } protected CommonSubscriber(BaseView view, String errorMsg){ this.mView = view; this.mErrorMsg = errorMsg; } @Override public void onCompleted() { } @Override public void onError(Throwable e) { if (mView == null) return; if (mErrorMsg != null && !TextUtils.isEmpty(mErrorMsg)) { mView.showError(mErrorMsg); } else if (e instanceof ApiException) { mView.showError(e.toString()); } else if (e instanceof HttpException) { mView.showError("数据加载失败ヽ(≧Д≦)ノ"); } else { mView.showError("未知错误ヽ(≧Д≦)ノ"); } } }
|
RxJava的Observer<T>
接口有三个方法,经过一系列的操作符处理后,最终数据会由onNext
方法发出,所以为了应对大多数场景我们可以做一层封装,对发生错误的回调onError
做统一处理,让上层专注于处理onNext
中的逻辑
1 2 3 4 5 6 7 8 9 10
| Subscription rxSubscription = mRetrofitHelper.fetchGirlList(NUM_OF_PAGE,currentPage) .compose(RxUtil.<GankHttpResponse<List<GankItemBean>>>rxSchedulerHelper()) .compose(RxUtil.<List<GankItemBean>>handleResult()) .subscribe(new CommonSubscriber<List<GankItemBean>>(mView) { @Override public void onNext(List<GankItemBean> gankItemBeen) { mView.showContent(gankItemBeen); } }); addSubscrebe(rxSubscription);
|
在CommonSubscriber
中传入View层对象,发生错误后将错误信息直接传递到View层做展示
最后再总结下Retrofit,OkHttp,RxJava在一次网络请求中各自的职责,在GeekNews中的写法大致可以用一张图来描述:
Retrofit
作为上层网络框架,OkHttp
作为底层网络框架,RxJava
处理数据流,完美配合~
并且这三者都具有非常强大的扩展性,你可以针对具体的项目或业务,为Retrofit.Builder添加各种自定义的Factory
,为OkHttp.Buidler添加各种Interceptor
,为RxJava封装多种操作符,玩出各种花样
Dagger2
Dagger2
的学习曲线应该是这几者中最高的,其中一部分原因是因为很多人可能学习了一段时间后甚至都没弄明白Dagger2
究竟是用来做什么的,我始终认为在学习一个框架前第一件事情就是要认清这个框架可以为我们带来什么好处,在弄明白这个问题之后,不仅有助于你去理解整个框架的设计思路,也能方便你找出最合适的使用场景从而灵活运用。如果你已经学过了Dagger2
中那些注解的基本含义,但是还是不知道该从何下手,或不明白Dagger2
能做到哪些事,希望下面的内容可以帮你解惑
Dagger2
是一款帮助我们完成依赖注入
的框架,从而做到降低耦合,解除依赖关系,可复用,易测试等等,下面举个栗子来说明:
参考Google的DaggerGuide
1 2 3 4 5 6 7 8 9
| class CofferMaker { private final Heater heater; private final Pump pump; CofferMaker() { this.heater = new ElectricHeater(); this.pump = new Thermosiphon(heater); } }
|
最直接的写法,咖啡机依赖于加热器和泵,泵又依赖加热器,这里直接在咖啡机里面new了电加热器和虹吸泵,试想哪天出现了比电加热器更好的其他加热器,我们为了升级需要找到依赖着电加热器的咖啡机一同修改,不仅是咖啡机,其他与电加热器强依赖的对象都要受波及,如果我们换一种方式,将依赖注入进来,会显得灵活得多
1 2 3 4 5 6 7 8 9
| class CofferMaker { private final Heater heater; private final Pump pump; CofferMaker(Heater heater, Pump pump) { this.heater = checkNotNull(heater); this.pump = checkNotNull(pump); } }
|
这就是上面提到的依赖注入
,灵活性与扩展性有了明显的提升,这时你可能会说,那我的注意力不就要转移到传入的参数上了吗?我还得帮咖啡机去找这些参数对象,这就是Dagger2
可以为我们做的,需要依赖什么,它来帮我们找,下面以GeekNews中的HttpModule
为例,这个例子更贴近我们平时的使用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| @Module public class HttpModule { @Singleton @Provides Retrofit.Builder provideRetrofitBuilder() { return new Retrofit.Builder(); } @Singleton @Provides OkHttpClient.Builder provideOkHttpBuilder() { return new OkHttpClient.Builder(); } @Singleton @Provides @ZhihuUrl Retrofit provideZhihuRetrofit(Retrofit.Builder builder, OkHttpClient client) { return createRetrofit(builder, client, ZhihuApis.HOST); } @Singleton @Provides OkHttpClient provideClient(OkHttpClient.Builder builder) { if (BuildConfig.DEBUG) { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC); builder.addInterceptor(loggingInterceptor); } File cacheFile = new File(Constants.PATH_CACHE); Cache cache = new Cache(cacheFile, 1024 * 1024 * 50); Interceptor cacheInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (!SystemUtil.isNetworkConnected()) { request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build(); } Response response = chain.proceed(request); if (SystemUtil.isNetworkConnected()) { int maxAge = 0; response.newBuilder() .header("Cache-Control", "public, max-age=" + maxAge) .removeHeader("Pragma") .build(); } else { int maxStale = 60 * 60 * 24 * 28; response.newBuilder() .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .removeHeader("Pragma") .build(); } return response; } }; builder.addNetworkInterceptor(cacheInterceptor); builder.addInterceptor(cacheInterceptor); builder.cache(cache); builder.connectTimeout(10, TimeUnit.SECONDS); builder.readTimeout(20, TimeUnit.SECONDS); builder.writeTimeout(20, TimeUnit.SECONDS); builder.retryOnConnectionFailure(true); return builder.build(); } @Singleton @Provides ZhihuApis provideZhihuService(@ZhihuUrl Retrofit retrofit) { return retrofit.create(ZhihuApis.class); } private Retrofit createRetrofit(Retrofit.Builder builder, OkHttpClient client, String url) { return builder .baseUrl(url) .client(client) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build(); } }
|
上面代码的依赖关系如下:
ZhihuApis
依赖于Retrofit
Retrofit
依赖于Retrofit.Builder
和OkHttpClient
OkHttpClient
依赖于OkHttpClient.Builder
但是我并不用关注这些依赖关系,我只需要把依赖列在参数中即可,Dagger2
会自动帮我找到提供依赖的provide或inject,生成依赖关系的代码,代替了手动传参(手动注入依赖),参考这篇文章,Dagger2寻找依赖的过程如下:
步骤1:查找Module中是否存在创建该类的方法。
步骤2:若存在创建类方法,查看该方法是否存在参数
步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
步骤3:若不存在创建类方法,则查找Inject注解的构造函数,
看构造函数是否存在参数
步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
现在可以做如下总结,假如不使用Dagger2
时,我们需要面临的问题:
- 要时刻关注依赖的先后顺序,被依赖对象要在依赖者之前创建
- 某个被依赖者发现变化,若没有以注入的方式依赖,会牵连一部分依赖者
- 错综复杂的函数调用与参数传递
使用Dagger2
后:
- 无需关心先后顺序或依赖关系,由
Dagger2
帮你找到参数中需要依赖的对象的Provider或构造函数,进而确定依赖关系,省力省心
- 某个被依赖者发生变化只需要修改其对应的Provider或构造函数即可,不用修改依赖者,避免了直接使用
new
带来的耦合问题,如果变化后产生了同种类型的依赖使用Qualifier
注解加以区分即可
- 依赖被统一到了
Module
中,由Inject
标注构造函数或Provide
提供,非常清晰直观,方便管理
- 每个provide相对独立,可以替换具体实现做单元测试
- 最后你可能会达到这种境界:蛤?需求变更又多了个依赖?在构造函数里加上这个依赖,相应的再加个provide或标注个inject~ 收工~ 至于新加的依赖和其他依赖谁先谁后,哪些依赖我,我又依赖哪些和我有什么关系?
最后提一下Scope
注解的一些常见误解:
Singleton
也是一个Scope
注解,它本身并没有创造单例的能力,是由于它通常配合管理全局类实例的Component
使用,而有了单例的效果,所以Scope
的具体效果与它依赖的Component
相关
Scope
最大的作用是限制,并不是定义一个ActivityScope
就神奇的绑定了Activity
的生命周期,不同层级的Component
需要定义不同的Scope
,从而规范作用域,也可以借助Scope
实现局部单例
结合这些总结和上面Module
注解中的代码不知道你有没有些许感悟呢(= ̄ω ̄=)
最后
GeekNews只是一个小项目,而且仍有很多不完善的地方,还无法把MVP,RxJava,Dagger等的优势充分展现,尤其是MVP和Dagger,在入门级项目中甚至会徒增代码量,还有类与接口的数量,但是在越大型的项目上越能发挥它们解耦,解除依赖的优势。这就需要我们充分理解这些框架或架构思路诞生之初的目的和内部的实现原理,不是为了用而用,或依照模板机械的使用,而是可以根据场景在最需要的地方用出效果,并加以合理的封装与改造让它更适合我们的项目,最终帮助我们构建出更好的应用(<ゝω·)
上面很多内容都是自己的理解,如果有错误或不足之处也欢迎指出,互相学习~
参考文章
从零开始的Android新项目4 - Dagger2篇
RxJava+Retrofit+OkHttp封装
声明:本站所有文章均为原创或翻译,遵循署名-非商业性使用-禁止演绎 4.0 国际许可协议,如需转载请确保您对该协议有足够了解,并附上作者名(Est)及原贴地址