編輯:Android資訊
大家好! 過了一好陣子了(在此期間我收到了大量的讀者反饋) 我決定是時候回到手機程序架構這個話題上了(這裡用android代碼舉例), 給大家另一個我認為好的解決方案.
在開始之前, 我這裡假設大家都讀過了我之前用簡潔的辦法架構Android程序一文. 如果你還沒有讀過, 現在應該去讀一下那篇文章, 讀過之後可以更好的理解我下面要講的內容.
演化是指一個事物變化成為另一個不同的事物的一個平緩過程, 通常情況下會變得更加復雜或者變成更好.
軟件開發一直在進化和改變. 實際上, 一個好的代碼結構必須幫助我們成長, 這意味著不用重新寫所有代碼就可以擴展功能. (盡管有些情況下應該大量的重寫代碼, 但那又是另一回事了, 這裡先不做探討).
這篇文章的重點是如何保持android代碼的清晰直觀, 為了闡述這一問題, 我將會帶著大家看幾個我認為重要的關鍵點. 記住下面這個圖我們就可以開始了.
在這裡我就不講RxJava的好處了(我猜大家都已經自己體會過了) , 因為已經有很多文章和壞蛋們都講過了, 而且講的還都不錯. 這裡我要講的是它是怎麼使得android開發變得非常有趣的, 還有它是如何幫助我完成搭建第一個干淨簡潔的架構的.
首先, 我選擇一個反應式模式讓用例(在簡潔的架構命名規范中叫做interactor) 都返回Observables
public abstract class UseCase { private final ThreadExecutor threadExecutor; private final PostExecutionThread postExecutionThread; private Subscription subscription = Subscriptions.empty(); protected UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { this.threadExecutor = threadExecutor; this.postExecutionThread = postExecutionThread; } protected abstract Observable buildUseCaseObservable(); public void execute(Subscriber UseCaseSubscriber) { this.subscription = this.buildUseCaseObservable() .subscribeOn(Schedulers.from(threadExecutor)) .observeOn(postExecutionThread.getScheduler()) .subscribe(UseCaseSubscriber); } public void unsubscribe() { if (!subscription.isUnsubscribed()) { subscription.unsubscribe(); } } }
可以看出,所有的子用例都繼承自這個抽象類,並在buildUseCaseObservable()這個抽象方法中構造一個可以完成耗時操作並返回需要數據的Observable
需要注意的是execute()這個方法, 我們保證了Observable讓自己執行在一個單獨的線程中, 這樣的話就可以最小限度的減少在主線程耗時. 然後通過主線程的scheduler機制把Observable的執行結果返回給主線程.
到現在為止, 我們已經有了Observable , 但是它產生的數據得有人來處理 . 所以我這裡將presenter(MVP 三層架構中的presentation層的一部分)改為了 Subscribers ,當用例產生數據後可以及時更新UI.
也就是下面這樣的subscriber:
private final class UserListSubscriber extends DefaultSubscriber<List<User>> { @Override public void onCompleted() { UserListPresenter.this.hideViewLoading(); } @Override public void onError(Throwable e) { UserListPresenter.this.hideViewLoading(); UserListPresenter.this.showErrorMessage(new DefaultErrorBundle((Exception) e)); UserListPresenter.this.showViewRetry(); } @Override public void onNext(List<User> users) { UserListPresenter.this.showUsersCollectionInView(users); } }
DefaultSubscriber只是簡單實現了對錯誤的處理, 每一個subscriber都是presenter中的一個繼承自 DefaultSubscriber 的內部類.
從下面這張圖中, 你可以得到一個比較完整的思路.
我們來總結一下RxJava帶給我們的好處:
從我的角度看這裡有一個小問題, 也是必須要付出的代價, 就是對這一概念不太熟悉的開發者的學習過程. 但是你會從中學到非常有價值的內容. 為了成功學習Reactive吧!
我這裡不會講太多關於依賴注入的例子, 因為我之前寫過一篇專門說依賴注入的文章, 為了跟上我這裡的腳步, 強烈推薦大家讀一讀這篇文章.
值得強調的是, 像Dagger 2一樣的依賴注入框架可以帶給我們:
沒有人會反對在我們的代碼中使用Java 8 的 Lambdas, 使用Lambdas可以省去大量的樣板代碼, 就像下面的代碼塊:
private final Action1<UserEntity> saveToCacheAction = userEntity -> { if (userEntity != null) { CloudUserDataStore.this.userCache.put(userEntity); } };
但是這個問題我非常糾結. 在我們@SoundCloud, 曾經有過一次關於Retrolambda的討論, 主要的分歧是要不要用它 討論的結果是:
利:
弊:
最終我們決定Retrolambda並不是一個能解決我們任何問題的庫: 使用了Retrolambda後代碼的確好看易理解, 但這對我們來說並不是必須的, 因為現在大部分的IDE已經可以是實現這一功能, 至少是以可以接受的方式
老實說, 我在這裡提到Retrolambda的主要原因是想用一用它, 體驗一把在Android代碼裡使用lambda是什麼感覺. 也許在我的業余項目中可能會用到這個庫. 我只是把我的想法放在這裡, 用不用它的最終決定權在大家手裡. 當然了, 該作者創造了這麼偉大的一個庫也非常值得贊揚
說到測試, 和之前的例子並沒有什麼太大的不同.
慶幸的是那都過去了, 現在所有的東西都可以直接用, 所以我重新把它們放在了數據模塊中, 特別是可以放在默認的測試文件夾 src/test/java 下.
我認為代碼/包的組織是一個良好架構的關鍵要素之一: 包結構是一個程序員看項目代碼時最先注意到的 其他的一切要素都由它而來,也都取決於它.
下面是組織包結構常見的兩種方式:
我的建議是根據功能的不同來組織包結構, 可以帶來下面這些好處:
有趣的是, 如果你在一個所謂的 功能性團隊 工作,(比如@SoundCloud), 代碼結構的分配會變得更加容易更加模塊化, 如果許多工程師在同樣的基礎代碼上開發時這個優點就變得格外明顯.
大家可以看出, 我的包結構看起來像是根據層級關系組織的: 這裡可能有舉例不太恰當的地方(比如將一切都放在’user’下面) 但是我會原諒自己這一次, 因為舉這個例子是為了供大家學習,為了表達我的觀點,主要目的是包含簡潔的架構思路. 照我說的做, 而不是照我做的做
我們都知道房子都是從地基開始修築的. 軟件開發也是一個道理, 我這裡要強調的是, 代碼架構中, 打包系統(及其組織架構)是非常重要的一部分
在Android開發中, 我們使用一個叫做gradle的非常強大的打包系統. 這裡有 一系列竅門 幫助大家在組織打包腳本時變得格外輕松:
根據功能的不同, 將打包系統分成多個腳本文件.
ci.gradle:
def ciServer = 'TRAVIS' def executingOnCI = "true".equals(System.getenv(ciServer)) // Since for CI we always do full clean builds, we don't want to pre-dex // See http://tools.android.com/tech-docs/new-build-system/tips subprojects { project.plugins.whenPluginAdded { plugin -> if ('com.android.build.gradle.AppPlugin'.equals(plugin.class.name) || 'com.android.build.gradle.LibraryPlugin'.equals(plugin.class.name)) { project.android.dexOptions.preDexLibraries = !executingOnCI } } }
build.gradle:
apply from: 'buildsystem/ci.gradle' apply from: 'buildsystem/dependencies.gradle' buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.2.3' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' } } allprojects { ext { ... } } ...
如上圖, 你可以利用 “apply from: ‘buildsystem/ci.gradle’” 將配置文件導入任意build腳本中. 不要將所有打包腳本寫在一個build.gradle文件中, 否則你會慢慢制造出一個怪物. 我已經受過教訓了.
將依賴整合到map中
dependencies.gradle:
... ext { //Libraries daggerVersion = '2.0' butterKnifeVersion = '7.0.1' recyclerViewVersion = '21.0.3' rxJavaVersion = '1.0.12' //Testing robolectricVersion = '3.0' jUnitVersion = '4.12' assertJVersion = '1.7.1' mockitoVersion = '1.9.5' dexmakerVersion = '1.0' espressoVersion = '2.0' testingSupportLibVersion = '0.1' ... domainDependencies = [ daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}", dagger: "com.google.dagger:dagger:${daggerVersion}", javaxAnnotation: "org.glassfish:javax.annotation:${javaxAnnotationVersion}", rxJava: "io.reactivex:rxjava:${rxJavaVersion}", ] domainTestDependencies = [ junit: "junit:junit:${jUnitVersion}", mockito: "org.mockito:mockito-core:${mockitoVersion}", ] ... dataTestDependencies = [ junit: "junit:junit:${jUnitVersion}", assertj: "org.assertj:assertj-core:${assertJVersion}", mockito: "org.mockito:mockito-core:${mockitoVersion}", robolectric: "org.robolectric:robolectric:${robolectricVersion}", ] }
build.gradle:
apply plugin: 'java' sourceCompatibility = 1.7 targetCompatibility = 1.7 ... dependencies { def domainDependencies = rootProject.ext.domainDependencies def domainTestDependencies = rootProject.ext.domainTestDependencies provided domainDependencies.daggerCompiler provided domainDependencies.javaxAnnotation compile domainDependencies.dagger compile domainDependencies.rxJava testCompile domainTestDependencies.junit testCompile domainTestDependencies.mockito }
如果你希望在不同的模塊中重復利用相同的依賴的版本,上面的建議就會變得非常有用 或者你想將不同的依賴版本放到不同的模塊中去也是一樣的. 另一個好處是 可以在一個地方控制所有的依賴
這差不多就是我要說的了, 大家要記住 並沒有包治百病的藥, 但是一個好的程序架構可以幫助我們保持代碼的整潔和健康, 同時也保證整了靈活性和可維護性
下面是一些我想指出的當你遇到程序問題時應有的態度:
Google近期在Udacity上發布了Android性能優化的在線課程,分別從渲染,運算與內存,電量幾個方面介紹了如何去優化性能,這些課程是Google之前在Y
在清楚了View繪制機制中的第一步測量之後,我們繼續來了解分析View繪制的第二個過程,那就是布局定位。繼續跟蹤分析源碼,根據之前的流程分析我們知道View的繪制
Android開發者在語言限制方面面臨著一個困境。眾所周知,目前的Android開發只支持Java 6(語言本身從Java 7開始進行了一些改進),因此我們每天
Android快捷方式作為Android設備的殺手锏技能,一直都是非常重要的一個功能,也正是如此,各種流氓App也不斷通過快捷方式霸占著這樣一個用戶入口。 同時,