前言
從自學安卓到畢業(yè)??然后工作已經快一年多了吧
這一年平時基本忙這公司的東西
自己想寫一些小項目,都是寫了一點就廢棄了
感覺完整的項目對個人的提升不大,因為大部分時間都在造輪子
總的來說,這一年還是做了一點微小的工作
![My contributions this year](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E8%B4%A1%E7%8C%AE.jpg)
有所成長,但是也知道自己欠缺的東西越來越多
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E5%B0%8F%E5%AE%89%E5%8D%93.jpeg)
前后待了兩個公司,都是小規(guī)模的創(chuàng)業(yè)公司
至今都是1個人(或者2個人)開發(fā)項目,感覺一直都在很低效很低效地工作
感覺android開發(fā)真是艱難啊,相比iOS
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/clear-architecture/effort.jpeg)
陸陸續(xù)續(xù)看過也練手過,MVP,MVVM,MVPVM之類的東西
當時并不能切身地感覺到它們的好處,現(xiàn)在想想真是圖樣圖森破
甚至以前我都不怎么喜歡用Fragment
,因為發(fā)現(xiàn)Fragment
很難管理
現(xiàn)在我開始在項目里面大量的使用Fragment
,因為多數(shù)情況下它們可以復用,可以節(jié)省很多時間
一個人開發(fā)嘛,心里的苦只有自己知道??
其實是所有的鍋只能一個人背(哈哈
也開始瘋狂封裝一些基類來復用
比如一個顯示列表的Fragment
基本就是傳入一個ViewModel和一個Item的布局文件就可以了
即便如此,雖然復用了大量代碼,但是限制也越來越多
每次產品經理一開口,我都感覺自己的代碼是一攤??屎
可能我能通過很好的封裝來解決Activity
和Fragment
過重的問題,但是項目還是很難維護
尤其是當需求一直改變,新功能不斷迭代,項目的歷史負擔越來越大
KPI壓力倒是沒用…感覺我們的用戶都是iOS人群??
每隔一段時間看自己的代碼,我都感覺是在屎山里面翻滾...
所以V/P分離貌似成為了一種必需
盡管各種架構前期總有一些額外的工作,但是之后的開發(fā)會變得很輕松
在app體積不斷增大的時候,讓項目細分下來的codebase盡量小
又一個前言
最近了解并嘗試了一下clean架構,發(fā)現(xiàn)它真的很給力
好后悔自己沒有早點熟悉它并投入使用.
Clean架構可以使你的代碼有如下特性:
- 獨立于架構
- 易于測試
- 獨立于UI
- 獨立于數(shù)據(jù)庫
- 獨立于任何外部類庫
如果你還不了解Clean架構,肯定要先去看下這個:
Uncle Bob的The Clean Architecture
這里只是想講講自己關于Clean架構的一點實踐,
有問題大家一起探討下...
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/clear-architecture/dicha.jpg)
開始
試著寫了一個demo:Vincent
雖然只寫了一點點(然后廢棄了= =),但是Clean架構的樣子感覺有了(我是這么覺得哈哈
初衷是寫一個tubmlr樣子的weibo, 寫著寫著發(fā)現(xiàn)渣浪對第三放開發(fā)者的限制太多了.
發(fā)現(xiàn)授個權還要認證,還要申請一個藍V的微博…我感覺好難受
首先這張圖你肯定見過...
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/clear-architecture/chart.jpg)
其實更直觀的,我們直接就著代碼來看3個module之間的依賴:
- data(數(shù)據(jù)層
apply plugin: 'java'
def cfg = rootProject.ext;
dependencies {
// ReactiveX
compile cfg.dependencies["rxjava"]
// Square
compile cfg.dependencies["retrofit"]
compile cfg.dependencies["converter-gson"]
compile cfg.dependencies["adapter-rxjava"]
compile cfg.dependencies["logging-interceptor"]
compile cfg.dependencies["dagger"]
}
- usecase(用例層 當然你叫domain也行
apply plugin: 'java'
dependencies {
compile project(':data')
}
data和usecase層都應該是java代碼,跟Android Framework無關
- app(就是我們平時寫的那個app
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
// skip
compile project(':usecase')
}
然后你們感受一下:
- 用例層從數(shù)據(jù)層獲取數(shù)據(jù)
- 在用例里面進行處理業(yè)務邏輯(雖然我發(fā)現(xiàn)一般的項目根本沒有什么業(yè)務邏輯可言
- 視圖層從用例層獲取數(shù)據(jù)然后顯示在界面上
Standing
然后講一下各種亂七八糟的東西
data層
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/clear-architecture/data%E5%B1%82.jpg)
entities:
實體類(最原始的數(shù)據(jù),不該隨著業(yè)務邏輯而改變
wrapper:
對實體類的一層封裝,在這里你可能要自己加一些東西:比如:
- 處理timestamp,轉換輸出本地時間
- 一些額外的參數(shù),比如數(shù)據(jù)邊界的驗證等等
當然這些你也可以放在View層的ViewModel里面
但是感覺放這里比較清晰
remote:
顧名思義嘛,這就是RemoteDataSource,在這里配置和封裝了(對于我來說)OkHttp
和Retrofit
的接口
utils:
- 自定義了一些
transformer
,比如切換線程和全局的錯誤處理 - 網絡請求的
Interceptor
- 其他亂七八糟的
當時這樣寫了我就感覺特別酷炫(說出來別笑我...
因為可以在這里直接用一個java類來測試api,而不是編譯整個項目...
不對不對,是API...有一次我們的python過來說你Api這個類應該全大寫...
每次叫我們python過來看logcat的時候 = = 他都會在旁邊發(fā)牢騷: 安卓編譯怎么會這么慢....
臥槽我也不想啊
當然你會說用Postman不就好了...
但是這樣能有一個配置了OkHttp
和Retrofit
的環(huán)境嘛...
usecase層
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/clear-architecture/usecase%E5%B1%82.jpg)
用例層就比較簡單了...因為一般的應用都沒有什么業(yè)務邏輯
舉個??,我要從一個從server端獲取微博(status)顯示到timeline列表上
首先我有一個Usecase
的接口要繼承(<T>
是網絡請求返回的數(shù)據(jù)類型
public abstract class Usecase<T> {
protected abstract Observable<T> buildObservable();
}
具體代碼:
public class GetTimelineUsecase extends Usecase<Timeline> {
private final RemoteRepository mRepository;
private final SchedulerTransformer<Timeline> mSchedulerTransformer;
private int page = 1;
@Named("uid")
private long uid;
@Named("timeline_type")
private String timeline_type;
@Inject
GetTimelineUsecase(RemoteRepository repository,
@Named("io_main") SchedulerTransformer schedulerTransformer,
@Named("timeline_type") String timeline_type,
@Named("uid") long uid) {
mRepository = repository;
mSchedulerTransformer = schedulerTransformer;
this.uid = uid;
this.timeline_type = timeline_type;
}
}
在構造函數(shù)里面我們注入了:
根據(jù)頁面邏輯相關的參數(shù)(
timeline_type
,uid
)-
跟Android Framework相關的東西(比如
AndroidSchedulers.mainThread()
因為這一層是
apply plugin: 'java'
嘛~
然后我需要有:
RemoteRepository(源,返回數(shù)據(jù)
-
SchedulerTransformer(切換線程,比如IO和UI線程之間
這里我直接注入一個transformer...當然你也可以注入不同的
thread
?
-
timeline_type,uid(接口的參數(shù)
針對返回相同數(shù)據(jù)類型的接口 我們可以封裝在一個用例里面
我在buildObservable()
中根據(jù)timeline_type
和uid
請求不同的接口:
@Override
protected Observable<Timeline> buildObservable() {
switch (timeline_type) {
case "home_timeline":
return mRepository.home_timeline(page, feature)
.compose(mSchedulerTransformer);
case "friends_timeline":
skip;
default:
return Observable.error(new Throwable("Timeline type can't be null!"));
}
然后在GetTimelineUsecase
中會有一個excute()
方法,我在這里處理業(yè)務邏輯:
public Observable<Timeline> excute(final boolean refresh) {
return buildObservable()
.doOnSubscribe(new Action0() {
@Override
public void call() {
if (refresh) {
page = 1;
}
}
})
.doOnNext(new Action1<Timeline>() {
@Override
public void call(Timeline timeline) {
if (0 != timeline.getNextCursor()) {
++page;
}
}
});
}
然后你會問業(yè)務邏輯在哪里(黑人問號???
我就說嘛,一般的項目沒什么業(yè)務邏輯....
這里不是有一點點處理分頁的邏輯(就這么多了
所以View層(或者說是你的Presenter)不用再關心分頁,不用有什么page或者cursor
只要在那邊一直調用excute()
就好了,想到一個View層的設計原則
讓View盡可能的笨拙和被動
雖然會傳過來一個refresh
的Boolean
值哈,但是可以認為這只是用戶操作(頁面邏輯),和業(yè)務邏輯無關.
PS:
在這里還自定義了一個注解
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD})
public @interface Nullable {
}
因為這個module沒有
support-annotation
可以用
Dagger會要求如果你注入的參數(shù)是null
,就必須用@Nullable
注解,不然就會報錯.
在一些地方我需要用到它,來傳入一些默認值(比如null和0,因為可能這個請求參數(shù)用不到
另外,在注入String
Long
等類型的參數(shù)的時候,需要用@Name
注解,不然編譯器肯定不知道你需要的是什么
View層
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/clear-architecture/app%E5%B1%82.jpg)
View層其實沒什么好講的,就是Databinding+Dagger+MVP
Dagger:
喜歡用Dagger的原因是因為它能迫使我理清所有的依賴關系
至于注入本身感覺也沒有那么方便,畢竟多了很多前期工作...
關于Dagger的使用,也是看了很多文章才拎清楚的
推薦一下frogermcs的博客~
MVP:
基本上是照著Google的android-architecture寫出來的
有很多分析的不錯的文章,我也是看了好久才搞清楚的
比如CameloeAnthony在簡書上的那個系列~
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/clear-architecture/dicha.jpg)
這里就不展開了…
EventBus:
項目里的事件總線用的是Eventbus.
不過漸漸發(fā)現(xiàn)代碼量一上去,各種event就會使邏輯變得很混亂
不知道Eventbus的正確打開方式到底是什么?
現(xiàn)在我反正是盡量少用...
DataBinding
其實用了之后感覺沒有一開始那么酷炫啦
雙向綁定什么的,實際使用場景很少...
而且Clean結構決定了View和真正的Model只會是單向的綁定
但是如果你還沒用過DataBinding,趕緊嘗試一下吧
其實Google的文檔寫的不是很全面,寫的最好的感覺還是大帥的博客
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/clear-architecture/dicha.jpg)
ViewModel:
既然是單向綁定,ViewModel扮演的角色就只是
- 暴露出需要的properties
- 讓View實現(xiàn)它的接口
當然關于ViewModel的寫法,講道理應該是對data層的model的一次映射,
但是感覺這樣要用手打好多代碼...如果AS有插件(類似GsonFormat之類的)我可能會這樣寫...
所以一般我們直接extends
或者wrap
某個model就好了…再調用getter
方法獲得properties
Tips
其實用ViewModel的一個好處是可以很方便的封裝一些方法.
比如圖片加載庫,你可能會覺得圖片加載庫還要封裝,不是只要一句話:
load(url).into(imageView);
對啊,但是可能你因為Glide的方法數(shù)太多,要換成Picasso,這下不就麻煩了
但是當你把所有圖片加載的場景(一般的...)都寫在@BindingAdapter
里面
舉個??:
@BindingAdapter("avatar")
public static void loadAvatar(ImageView view, String url) {
Glide.with(view.getContext()).load(url).into(view);
}
@BindingAdapter("photo")
public static void loadPhoto(ImageView view, String uri) {
Glide.with(view.getContext()).load(Uri.parse(uri)).into(view);
}
這樣到時候全局意義上的替換一個庫就很方便了
Rxjava
用Subscriber
來代替Callback
,在Presenter里面就全部都是從上往下的Rx的stream
舉個??,一個列表加載數(shù)據(jù):
public void loadMore() {
if (dataInTransit || reachBottom) return;
mSubscriptions.add(mUsecase.execute(false) // boolean: needRefresh
.doOnSubscribe(() -> dataInTransit = true)
.doOnTerminate(() -> dataInTransit = false)
.doOnSubscribe(mView::startLoading)
.doOnTerminate(mView::stopLoading)
.doOnError(mView::showError)
.subscribe(timeline -> {
if (timeline.getNextCursor() == 0) {
reachBottom = true;
mView.showNoMore();
} else {
mView.setData(timeline.getStatuses(), false);
}
}));
}
寫起來特別舒服~
關于RxJava,響應式編程什么的已經不是一個新鮮玩意兒,在這里就不贅述了
安利一下,感覺Awesome-RxJava列出的一些資料都非常不錯~
最后
其他還有一些東西...之后再補上了
總之,Clean架構是個蠻有意思然后很實用的東西
大家有時間可以了解一下~
最后,Everybody 新年快樂?????? 新的一年里都能成為更好的自己~
![](https://github.com/80998062/80998062.github.io/raw/master/img/in-post/clear-architecture/jiaban.png)