前言:
许久没写过文章,距离上一次写文章都快有8个月了,深深的感受到自己到底是有多懒,自8月初从深圳回广州工作,加入新的团队,新的学习方式。在此记录、总结、分享。
1.组件化开发
组件化开发这个名词并不陌生,但真正什么才是组件化开发,大家在网上搜可以查看很多相应的文章,我概念中,模块化的开发,就是把很多模块独立出来,基础模块,业务模块等。什么是基础模块,基础模块就是公司常用的一些sdk,一些封装好的基类,业务模块就是基于基础模块进行开发的。在以往的开发中,我并未真正的去使用组件化开发,直到加入新的团队可以说是开启新世界的大门,给我的感觉,组件化开发,贼爽,为什么爽?
我总结了好几点:
1.各自负责业务模块独立开发,以application进行开发,后期再以library引入项目
2.因为每个模块独立出来,以最小模块的进行开发,编译速度快
3.有利于单元测试,对业务模块进行单元测试
4.降低耦合,提高模块的复用
以下为基础模块包:
package.png整个项目结构:
Android框架模块分布图.pngAndroid studio:
图片2.png在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进行模块判断
图片3.png//通过之前设定的变量进行设置是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:
图片4.png
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
2.Retrofit+RxJava+MVP模式
关于Retrofit跟RxJava,具体详细的用法就不在这里介绍,网上有很多现有的文章,为什么使用Retrofit跟RxJava,Retrofit是基于Okhttp封装一层的客户端,配合RxJava线程调度,很好的控制网络请求,使用RxJava可以提高代码的可读性,这里我分享一下retrofit+Rxjava封装。
1.基于Retrofit的Api工厂
ApiFactory如下图:
api工厂.png图中的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);
}
.....
}
2.MVP
使用mvp可以解耦,结构清晰,对于业务复杂的场景来说,可以提高代码可读性,结构清晰,降低后期维护成本。如下图登录模块所示:
mvp.pngView跟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复用以及单元测试会更方便。
3.结合Retrofit+RxJava+Mvp
结合之前谈到的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) {
//登录成功等操作
}
});
}
}
Anyway:
此文章用于记录学习的一个心得和一些收获,在此感谢队友Keegan小钢跟Rae提供的帮助。感兴趣的朋友可以关注他们博客,能学到很多全栈的知识。
对于我来说,写博客就好像写日记一般,回来了广州后发现了感觉很充实,虽然团队没有像以往在深圳的996般,但整个团队的研发效率很高,很有活力的一个团队,还是觉得工作嘛,高效沟通、高效工作、开心工作、开心生活。
Keegan小钢
http://keeganlee.me/
微信订阅号:keeganlee_me
网友评论