好搭项目架构总结

该篇文章主要是分析一下目前项目的架构。

背景

好搭作为一个较为成熟的软件,从2016年我刚毕业的那年开始进行设计架构,到现在过了整整2年了。经手该项目的有大约20人了,我算是第四代接手这个项目的人。

架构

clean结构

clean结构示意图

一开始看到这个架构是挺懵逼的,以前听说的时候知道这个架构是谷歌的,主要是用于项目架构,目的是为了解耦。

项目中依托clean架构搭建了一套完整的解耦系统。

domain层

domian层是最内层,提供了项目的基础架构,包括了接口,util方法类(可以被视为实体类),model(完整的实体对象),event(消息通知的实体对象),exception(所有的自定义异常实体对象)

另外提供了精华处,针对网络的封装实体对象,interactor

1
2
3
public interface InteractorExecutor extends Executor {

}

这是一个内部写的interactorexcutor,其实就是继承了executor,作为处理runnable的方式。

另外还写了一个PostInteractionThread,作为获取线程的方式。

1
2
3
public interface PostInteractionThread {
Scheduler getScheduler();
}

仅提供了很简单的方法

interactor类:

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
public abstract class Interactor<T> {

private static final boolean isDelay3Second = false;
protected PostInteractionThread postInteractionThread;
protected InteractorExecutor interactorExecutor;
private EventBus mEventBus;
private Disposable subscription = Disposables.empty();

public Interactor(InteractorExecutor interactorExecutor, PostInteractionThread postInteractionThread) {
this.postInteractionThread = postInteractionThread;
this.interactorExecutor = interactorExecutor;

mEventBus = EventBus.getDefault();
}

protected abstract Observable<T> buildObservable();

protected void checkConditions() {
}

public T executeSync() {
return buildObservable().blockingFirst();
}

public void execute(Consumer<? super T> onNext) {
execute(onNext, null, null);
}

public void execute(Consumer<? super T> onNext, Consumer<Throwable> onError) {
execute(onNext, onError, null);
}

public void execute(Consumer<? super T> onNext, Consumer<Throwable> onError, Action onComplete) {

execute(
new Observer<T>() {
@Override
public void onError(Throwable e) {
Throwable t = new Exception();
if (e instanceof HttpException) {
if (((HttpException) e).code() == 401) {
HttpException s = (HttpException) e;
if (s != null && s.response() != null && s.response().errorBody() != null) {
try {
HttpErrorBody body = new Gson().fromJson(s.response().errorBody().string(), HttpErrorBody.class);
if (body != null && body.getError_reason() != null && body.getError_reason().startsWith("AuthenticationFailed")) {
//todo
} else {
mEventBus.post(new OnAuthExpiredEvent());
}
} catch (IOException e1) {
e1.printStackTrace();
mEventBus.post(new OnAuthExpiredEvent());
}
} else {
mEventBus.post(new OnAuthExpiredEvent());
}
} else if (((HttpException) e).code() == 400 || ((HttpException) e).code() == 403 || ((HttpException) e).code() == 500) {
try {
if (((HttpException) e).response() != null && ((HttpException) e).response().errorBody() != null) {
String json = ((HttpException) e).response().errorBody().string();
HttpErrorBody body = new Gson().fromJson(json, HttpErrorBody.class);
HttpErrorMessageBody messageBody = new Gson().fromJson(json, HttpErrorMessageBody.class);
if (body != null && body.getError_reason() != null) {
// mEventBus.post(new OnHttpErrorEvent(body.getError_reason()));
} else if (messageBody != null && messageBody.getMessage() != null) {
mEventBus.post(new OnHttpErrorEvent(messageBody.getCode(), messageBody.getMessage()));
} else {
mEventBus.post(new OnHttpErrorEvent("网络异常,请稍后再试"));
}
t = new Exception(messageBody.getMessage());
}

} catch (Exception e1) {
e1.printStackTrace();
}
}
}
if (onError == null || subscription.isDisposed()) {
e.printStackTrace();
} else {
try {
onError.accept(t);
} catch (Exception e1) {
e1.printStackTrace();
}
}
handleHttpError(e);
}

@Override
public void onComplete() {
if (onComplete != null && !subscription.isDisposed()) {
try {
onComplete.run();
} catch (Exception e) {
onError(e);
}
}
}

@Override
public void onSubscribe(Disposable d) {
subscription = d;
}

@Override
public void onNext(T t) {
if (onNext != null && !subscription.isDisposed()) {
try {
onNext.accept(t);
} catch (Exception e) {
onError(e);
}
}
}
});
}

private void execute(Observer<T> observer) {
Observable<T> observable;
try {
checkConditions();
observable = buildObservable();
} catch (Exception e) {
observable = Observable.create(observableEmitter -> observableEmitter.onError(e));
}
// cancel latest execution
cancel();
if (isDelay3Second) {
observable.subscribeOn(Schedulers.io())
.delay(3, TimeUnit.SECONDS)
.observeOn(postInteractionThread.getScheduler())
.subscribe(observer);
} else {
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
}

public void cancel() {
if (!subscription.isDisposed()) {
subscription.dispose();
}
}

public boolean isCancel() {
return subscription == null || subscription.isDisposed();
}

public Observable<T> getObservable() {
return buildObservable();
}
private void handleHttpError(Throwable e) {
if (e instanceof RuntimeException) {
if (e.getCause() instanceof ConnectException) {
mEventBus.post(new OnHttpErrorEvent("连接失败,请检查网络后再试"));
}
if (e.getCause() instanceof SocketTimeoutException) {
mEventBus.post(new OnHttpErrorEvent("请求超时,请检查网络后再试"));
}
if (e.getCause() instanceof UnknownHostException) {
mEventBus.post(new OnHttpErrorEvent("域名连接失败,请检查网络后再试"));
}
if (e.getCause() instanceof NetworkErrorException) {
mEventBus.post(new OnHttpErrorEvent("网络异常,请检查网络后再试"));
}
}
if (e instanceof ConnectException) {
mEventBus.post(new OnHttpErrorEvent("连接失败,请检查网络后再试"));
}
if (e instanceof SocketTimeoutException) {
mEventBus.post(new OnHttpErrorEvent("请求超时,请检查网络后再试"));
}
if (e instanceof UnknownHostException) {
mEventBus.post(new OnHttpErrorEvent("域名连接失败,请检查网络后再试"));
}
if (e instanceof NetworkErrorException) {
mEventBus.post(new OnHttpErrorEvent("网络异常,请检查网络后再试"));
}
}
}

interactor可以说是整个domain的精华,首先包装了observable,然后在执行部分统一处理了异常等。并且切换线程也提供了统一的处理方式,这样完善的避免了OKhttp无法统一的问题。从上层提供了处理的机制。而且此处需要的是observable,刚好可以通过retrofit2进行提供。这样就形成了一个闭环,只需要对接interactor,然后接上retrofit提供的接口即可完整的处理网络请求。

domain中除了基类interactor,另外还提供了一系列基于基类interactor衍生的网络请求实体类,虽然此处这些实体类的存在仅仅使用到了接口来实现。接口的具体实现还需要更上一层来完成。但是巧妙的是此处通过dagger的注入,将上层提供的实现方法注入到了下层,这样实现的底层可以直接拿来用。

data层

domain层是独立的,无任何模块依赖,而data层就是domain层更上一层的,仅仅只依赖于domain模块。

data层是数据层,此处主要是处理或者说提供数据。domain层需要的接口实现就是在data层实现好了通过dagger注入到底层。

data层除了数据提供者这一个身份外,还有一个数据缓存者这个身份。整个data在关键的地方,比如说userbody,usercache等地方设置了三级缓存。这样完整的实现了一个数据层的功能。

data层对于数据的处理方面集中在获取,提供,以及保存。将数据隔离的效果很明显能够大幅度解耦。

如果data层不隔离,那么获取的数据,存储的数据,提供者,三者会混乱,在A区域获取的数据可能和B区域获取的数据不同。当处于不同模块的代码进行相同的缓存,又会导致缓存不同步,而相同的代码进行不同的缓存,更是乱七八糟。因此这一层做隔离,哪怕是仅仅只有数据区域做隔离,总好过数据区域掺杂在不同的区域而导致相同的数据采用了过多缓存步骤。

另外项目中提供的缓存方法,主要是ACache和Lrucache这两个老牌的库,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface CacheStore {
int NONE_TIME = 0;
int DURATION_ONE_MINUTE = 60;
int DURATION_ONE_HOUR = DURATION_ONE_MINUTE * 60;
int DURATION_THREE_HOUR = DURATION_ONE_HOUR * 3;
int DURATION_ONE_DAY = DURATION_ONE_HOUR * 24;
int DURATION_FIVE_MINUTE = DURATION_ONE_MINUTE * 5;

void put(String key, byte[] data);
void put(String key, byte[] data, int duration);
byte[] get(String key);
void put(String key, Serializable obj);
void put(String key, Serializable obj, int duration);
Object getAsObject(String key);
void put(String key, boolean value);
boolean getAsBoolean(String key);
void clear();
void remove(String key);
}

实现缓存使用的就是这个模板,这个模板演化为两个,一个memstore,一个DiskcatchStore。memstore就是内存缓存,仿照的是Lrucache,主要是规避gc的清除,实现的原理看一下就懂了。diskcatchstore就是文件缓存,使用的是Acache来实现的,甚至可以说是一个封装而已。

实现缓存有个方法

1
void put(String key, Serializable obj, int duration);

其中有保存时长,这个保留的时长会转化为当前时间+时长的时间戳,将和data一起包装起来存储,在取出保存的data时会进行判断,如果携带了时间信息,就对时间进行判断,如果时间过期,那么在memcache里面就是移除这个key,在Acache里面也是相同的原理

baselibrary层

baselibrary是data层之上的一层。如果说domain是心脏,data是血液的话,那么baselibrary就是骨架。

baselibrary将所有用到的库进行综合,自我封装,以及改写,然后通过暴露api的方式让上层可以直接使用暴露出来的api

到baselibrary这一层截止,整个项目的脚手架就已经都搭建ok,也就是到这一层为止,整个项目的核心基本上就已经完成。如果需要热插拔,使用我们的项目另外新建一个新的项目的话,那么只需要将baselibrary这一层以下的进行迁移,然后复写逻辑即可。

baselibrary在后期逐渐演化为和app混合了。

app层

app是整个项目的逻辑层,当然也做了很多初始化的工作,最重要的比如说dagger的初始化工作,app层就像是肌肉,在肌肉填充满了之后,才会成为一个正常的人。