前言: 许久没写过文章,距离上一次写文章都快有 8 个月了,深深的感受到自己到底是有多懒,自 8 月初从深圳回广州工作,加入新的团队,新的学习方式。在此记录、总结、分享。
组件化开发这个名词并不陌生,但真正什么才是组件化开发,大家在网上搜可以查看很多相应的文章,我概念中,模块化的开发,就是把很多模块独立出来,基础模块,业务模块等。什么是基础模块,基础模块就是公司常用的一些 sdk,一些封装好的基类,业务模块就是基于基础模块进行开发的。在以往的开发中,我并未真正的去使用组件化开发,直到加入新的团队可以说是开启新世界的大门,给我的感觉,组件化开发,贼爽,为什么爽
我总结了好几点:
1. 各自负责业务模块独立开发,以 application 进行开发,后期再以 library 引入项目 2. 因为每个模块独立出来,以最小模块的进行开发,编译速度快 3. 有利于单元测试,对业务模块进行单元测试 4. 降低耦合,提高模块的复用
以下为基础模块包:
整个项目结构:
Android studio:
在 gradle.properties 中,我们可以设置一个变量,控制是否使用模块化来开发
- #是否使用模块化开发
- isModule=false
然后在 settings.gradle 中设置项目引入包
- //默认都打开基础模块
- include ':sdk', ':model', ':widget', ':module-basic'
- //根据自己负责的模块分别进行相应的引入
- include ':module-user'
- include ':module-business'
- //根据是否模块开发,是否引入app 模块
- if (!isModule.toBoolean()) {
- include ':app'
- }
业务模块 gradle 进行模块判断
- //通过之前设定的变量进行设置是application还是library
- if (isModule.toBoolean()) {
- apply plugin: 'com.android.application'
- } else {
- apply plugin: 'com.android.library'
- }
根据结构图,我们基础模块的依赖,默认引入 sdk、model、widget、module-baisc 然后根据自己负责的业务模块,分别引入不同的业务,如果我是负责用户模块,我在开发就只需要引入用户模块即可,这样开发每个模块的时候可以提高每个模块的编译效率。
最后模块合并的时候,在 gradle.properties 中关闭模块开发,在 settings.gradle 引入项目相应的模块包, 并设置 app 的 build-gradle:
build-gradle:
- dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
- exclude group: 'com.android.support', module: 'support-annotations'
- })
- compile 'com.android.support:appcompat-v7:26.+'
- compile 'com.android.support.constraint:constraint-layout:1.0.2'
- compile 'com.android.support:design:26.+'
- testCompile 'junit:junit:4.12'
- //如果不使用模块化开发,就引入所有的业务模块
- if (!isModule.toBoolean()) {
- compile project(':module-business')
- compile project(':module-user')
- }
- }
现在的问题,不同模块的 activity 怎么跳转,以前我的做法都会在每个 activity 中写一个静态方法,把入参设定好.
- /**
- * 跳转
- *
- * @param context 上下文
- * @param param 参数
- */
- public static void toAcitivty(Context context, String param) {
- Intent intent = new Intent(context, MainActivity.class);
- intent.putExtra("param", param);
- context.startActivity(intent);
- }
因为使用模块化开发的话,不同业务模块是不能调用其 activity,因此我们使用阿里的 Arouter, 在每个 activity 头部使用注解进行跳转,就像 Spring mvc 的 controller 一样,使用路由进行设置跳转,在模块化的开发中,这个很关键,一方面使用 arouter 可以降低 activity 之间的耦合,另一方面可以对模块进行单元测试。
Arouter 具体的使用方法: https://github.com/alibaba/ARouter
关于 Retrofit 跟 RxJava,具体详细的用法就不在这里介绍,网上有很多现有的文章,为什么使用 Retrofit 跟 RxJava,Retrofit 是基于 Okhttp 封装一层的客户端,配合 RxJava 线程调度,很好的控制网络请求,使用 RxJava 可以提高代码的可读性,这里我分享一下 retrofit+Rxjava 封装。
ApiFactory 如下图:
图中的 ApiFactory 的职责是提供所有业务 Api 接口,具体提供的 Api 是通过接口 ApiProvider 提供每个业务接口,如果用户接口,交易接口,令牌接口等,ApiFactory 通过单例,获取 api 提供者,ApiProvider 具体的实现类 ApiProvideImpl 继承于网络引擎 RetrofitApi,RetrofitApi 用于初始化一些网络引擎。ApiProvideImpl 中使用 retrofit 来初始化各种 Api 接口。
ApiProviderImpl.java:
- class ApiProviderImpl extends RetrofitApi implements ApiProvider {
- private OkHttpClient httpClient;
- ApiProviderImpl(Context applicationContext) {
- super(applicationContext);
- }
- private <T> T create(Class<T> cls) {
- return mRetrofit.create(cls);
- }
- @Override
- public ITokenApi getTokenApi() {
- return create(ITokenApi.class);
- }
- @Override
- public IUserApi getUserApi() {
- return create(IUserApi.class);
- }
- @Override
- public IProductApi getProductApi() {
- return create(IProductApi.class);
- }
- @Override
- public IPushApi getPushApi() {
- return create(IPushApi.class);
- }
- @Override
- public IQuotationApi getQuotationApi() {
- return create(IQuotationApi.class);
- }
- @Override
- public ITradeApi getTradeApi() {
- return create(ITradeApi.class);
- }
- .....
- }
使用 mvp 可以解耦,结构清晰,对于业务复杂的场景来说,可以提高代码可读性,结构清晰,降低后期维护成本。如下图登录模块所示:
View 跟 presenter 都抽象成接口,这样相互不依赖于细节,有易于做单元测试,降低耦合。这里有两个基础接口,LoginView 跟 LoginPresenter 分别继承于 IView 跟 IPresenter,LoginViewImpl 以及 LoginPresenterImpl 分别实现 LoginView 跟 LoginPresenter,其依赖于抽象不依赖于实现的细节。
- /**
- * 登录契约类
- */
- public interface LoginContract {
- /**
- * 表现层接口
- */
- interface Presenter extends IPresenter {
- /**
- * 登录操作
- */
- void login();
- }
- /**
- * 视图层接口
- */
- interface View extends IPresenterView {
- /**
- * 获取密码
- *
- * @return return
- */
- String getPassword();
- /**
- * 获取用户信息
- *
- * @return return
- */
- String getUsername();
- /**
- * 登录成功
- */
- void loginSuccess();
- /**
- * 登录失败
- *
- * @param msg msg
- */
- void loginFailed(String msg);
- }
- }
我们通过定义一个 Contract 契约类,来制定接口,在定 Presenter 跟 view 接口的同时,我们可以很清晰的知道,表现层需要什么东西,view 层需要提供什么东西,包括网络请求后相应的响应,这样在我们做一个业务逻辑的时候思路可以更清晰,同事在进行 presenter 复用以及单元测试会更方便。
结合之前谈到的 Api 跟 mvp, 在这个基础上进行封装 Presenter 的实现基础类。
- /**
- * presenter基础实现类的封装
- * 1.跟视图view进行绑定与解绑
- * 2.对rx事件进行加工处理与释放资源
- */
- public class BasicPresenterImpl < T extends IPresenterView > implements IPresenter {
- /**
- * 视图
- */
- protected T mView;
- /**
- * 上下文
- */
- protected Context mContext;
- /**
- * 记录标识,用于此presenter所有的任务进行标识
- */
- private String mTag = this.getClass().getName();
- public BasicPresenterImpl(Context context, T view) {
- this.mView = view;
- this.mContext = context;
- }
- public void start() {}
- /**
- * 销毁资源,一般用于与view解绑操作
- * 如activity作为view中,activity 销毁的时候调用
- * 避免错误引用,避免内存泄露
- */
- public void destroy() {
- this.mView = null;
- this.mContext = null;
- this.cancelRequest();
- }
- /**
- * 根据tag清掉任务,如清掉未完成的网路请求
- */
- protected void cancelRequest() {
- RxObservable.dispose(this.mTag);
- RxObservable.dispose("PageDataObservable");
- }
- /**
- * rxJava 多数用于创建网络请求
- * 如createObservable(mUser.login())
- * retorfit结合rxJava
- *
- * @param observable observable
- * @param <T> t
- * @return return
- */
- protected < T > Observable < T > createObservable(Observable < T > observable) {
- //创建任务
- return RxObservable.create(observable, this.mTag);
- }
- }
基础 Presenter 封装了绑定与解绑的操作,presenter 跟 view 解绑时调用 destory 释放资源,并把此 presenter 中使用 rxJava 处理得事件全部清掉,释放资源,例如一些网络请求,当 view 跟 presenter 解绑后网络请求未来得及返回处理,容易出现 view 空指针的操作。
接着介绍一下 RxObservable 的封装:
- /**
- * 用于封装rx事件,通过键值对的方式保存任务
- * 对任务进行初始化,释放等操作所
- */
- public final class RxObservable {
- /**
- * 全局变量,使用tag标识保存Disposable集合
- * Disposable?Observer(观察者)与Observable(被观察者)切断链接
- */
- private static final Map < String,
- List < Disposable >> sObservableDisposableList = new WeakHashMap();
- public RxObservable() {}
- /**
- * 创建被观察者,如retrofit集合rxJava返回的网络请求,
- * 此方法用于事件在初始化时进行处理,把此事件保存到sObservableDisposableList集合中,
- * 以tag为key,以为List<Disposable>为值,订阅被观察者时可以获其Disposable
- */
- public static < T > Observable < T > create(Observable < T > observable, final String tag) {
- return observable.doOnSubscribe(new Consumer() {
- public void accept(@NonNull Disposable disposable) throws Exception {
- //在集合中判断是否存在集合
- //没有则创建,并以key-tag保存到sObservableDisposableList中
- List disposables = (List) RxObservable.sObservableDisposableList.get(tag);
- if (disposables == null) {
- ArrayList disposables1 = new ArrayList();
- RxObservable.sObservableDisposableList.put(tag, disposables1);
- }
- //把此事件的Disposable添加到对应的tag的集合中
- ((List) RxObservable.sObservableDisposableList.get(tag)).add(disposable);
- }
- //订阅过程在Io线程处理,发送在主线程处理
- }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
- }
- /**
- * 释放所有资源
- */
- public static void dispose() {
- try {
- Iterator e = sObservableDisposableList.values().iterator();
- while (e.hasNext()) {
- List disposables = (List) e.next();
- Iterator var2 = disposables.iterator();
- while (var2.hasNext()) {
- Disposable disposable = (Disposable) var2.next();
- if (disposable != null && !disposable.isDisposed()) {
- disposable.dispose();
- }
- }
- disposables.clear();
- }
- } catch(Exception var7) {
- Log.e("rae", "释放HTTP请求失败!", var7);
- } finally {
- sObservableDisposableList.clear();
- }
- }
- /**
- * 根据tag标识进行释放资源
- *
- * @param tag tag
- */
- public static void dispose(String tag) {
- try {
- if (!TextUtils.isEmpty(tag) && sObservableDisposableList.containsKey(tag)) {
- List e = (List) sObservableDisposableList.get(tag);
- Iterator var2 = e.iterator();
- while (var2.hasNext()) {
- Disposable disposable = (Disposable) var2.next();
- if (disposable != null && !disposable.isDisposed()) {
- disposable.dispose();
- }
- }
- e.clear();
- sObservableDisposableList.remove(tag);
- return;
- }
- } catch(Exception var7) {
- Log.e("rae", "释放HTTP请求失败!", var7);
- return;
- } finally {
- sObservableDisposableList.remove(tag);
- }
- }
- }
在 RxObservable 中,创建一个 sObservableDisposableList 用于保存每个 presenter 中处理的事件,通过 tag 作为标识创建,每个 presenter 中会通过 tag 找到对应的 Disposable 集合,Disposable 集合中保存了此 presenter 中的所有任务,如网络请求、io 操作等,通过此方法可以统一管理 tag 的任务,在 presenter 解绑的时候可以及时的销毁资源,避免内存泄露。
登录的一个小例子:
- public class LoginPresenterImpl extends BasicPresenterImpl<LoginContract.View> implements LoginContract.Presenter {
- IUserApi mUserApi;
- public LoginPresenterImpl(Context context, LoginContract.View view) {
- super(context, view);
- //初始化变量....
- }
- @Override
- public void login() {
- //在view层获取手机号跟密码
- final String mobile = mView.getMobile();
- final String password = mView.getPassword();
- if (TextUtils.isEmpty(mobile)) {
- mView.onLoginFailed("请输入手机号码");
- return;
- }
- if (TextUtils.isEmpty(password)) {
- mView.onLoginFailed("请输入密码");
- return;
- }
- if (!mPhoneValidator.isMobile(mobile)) {
- mView.onLoginFailed("请输入正确的手机号码");
- return;
- }
- createObservable(mUserApi.login(mobile, password)).subscribe(new ApiDefaultObserver<UserInfo>() {
- @Override
- protected void onError(String msg) {
- //登录失败
- mView.onLoginFailed(msg);
- }
- @Override
- protected void accept(UserInfo userInfo) {
- //登录成功等操作
- }
- });
- }
- }
此文章用于记录学习的一个心得和一些收获,在此感谢队友 Keegan 小钢跟 Rae 提供的帮助。感兴趣的朋友可以关注他们博客,能学到很多全栈的知识。
对于我来说,写博客就好像写日记一般,回来了广州后发现了感觉很充实,虽然团队没有像以往在深圳的 996 般,但整个团队的研发效率很高,很有活力的一个团队,还是觉得工作嘛,高效沟通、高效工作、开心工作、开心生活。
http://keeganlee.me/ 微信订阅号:keeganlee_me
http://www.raeblog.com
来源: https://juejin.im/post/5a31d55b6fb9a04527259953