編輯:Android資訊
自從開始開發安卓應用,我一直感覺我可以做得更好。我看過不少爛代碼,其中當然有我寫的。安卓系統的復雜性加上爛代碼勢必釀成災禍,所以從錯誤中成長就很重要。我Google了如何更好地開發應用,發現了這個叫做Clean架構的東西。於是我嘗試將它應用於安卓開發,根據我在類似項目中的經驗做了一些改善,寫出了這篇我覺得較為實用、值得分享的文章。
我會在這篇文章中手把手教你在Android應用中使用Clean架構。我最近一直用這種方式優雅地編寫應用。
有許多文章已經很好地回答了這個問題。我在這裡講一講Clean架構的核心概念。
一般來說,在Clean架構中,代碼被分層成洋蔥形,層層包裹,其中有一個依賴性規則:內層不能依賴外層,即內層不知道有關外層的任何事情,所以這個架構是向內依賴的。看個圖感受一下:
圖片由Bob大叔提供
Clean架構可以使你的代碼有如下特性:
我將通過下面的例子解釋這些特性是怎麼來的。如果你想深入了解Clean架構,不妨看這篇文章和這個視頻
一般來說,一個應用可以有任意數目的層,但除非你的應用到處是企業級功能邏輯,一般需要這三層:
接口實現層是體現架構細節的地方。實現架構的代碼是所有不用來解決問題的代碼,這包括所有與安卓相關的東西,比如創建Activity和Fragment,發送Intent以及其他聯網與數據庫的架構相關的代碼。
添加接口適配層的目的就是橋接邏輯層和架構層的代碼。
最重要的是邏輯層,這裡包含了真正解決問題的代碼。這一層不包含任何實現架構的代碼,不用模擬器也應能運行這裡的代碼。這樣一來你的邏輯代碼就有了易於測試、開發和維護的優點。這就是Clean架構的一個主要的好處。
每一個位於核心層外部的層都應能將外部模型轉成可以被內層處理的內部模型。內層不能持有屬於外層的模型類的引用。這也是由於剛才說的依賴性規則,這樣內外層可以很好地分離。
為什麼要進行模型轉換呢?舉個例子,當邏輯層的模型不能直接很優雅地展現給用戶,或是需要同時展示多個邏輯層的模型時,最好創建一個ViewModel類來更好的進行UI展示。這樣一來,你就需要一個屬於外層的Converter類來將邏輯層模型轉換成合適的ViewModel。
再舉一個例子:你從外部數據庫層獲得了ContentProvider的Cursor對象,外層首先要將這個對象轉換成內層模型,再將它傳給內層處理。
在文章的最後我還提供了一些學習資源。我們已經知道了Clean架構的基本原則,現在我們來實踐一下。我會在下一部分中使用Clean架構構建一個示例功能。
我已經寫好了一個樣板項目,裡面把准備工作做好了。這相當於是一個Clean的底層包,可以直接在它的基礎上進行開發。請隨意下載、修改。項目包:Android Clean Boilerplate
這一部分會詳細說明如何用在樣例項目的基礎之上以Clean方式進行開發。首先讓我們看一下應用的結構,當這只是我的習慣,不需要完全按這個進行。
一般來說一個安卓應用的結構如下:
看不懂不要緊,下面有具體解釋。
外層體現了框架的細節。
UI – 包括所有的Activity,Fragment,Adapter和其他UI相關的Android代碼。
Storage – 用於讓交互類獲取和存儲數據的接口實現類,包含了數據庫相關的代碼。包括了如ContentProvider或DBFlow等組件。
Network – 網絡操作。
橋接實現代碼與邏輯代碼的Glue Code。
Presenter – presenter處理UI事件,如單擊事件,通常包含內層Interactor的回調方法。
Converter – 負責將內外層的模型互相轉換。
內層包含了最高級的代碼,裡面都是POJO類,這一層的類和對象不知道外層的任何信息,且應能在任何JVM下運行。
Interactor – Interactor中包含了解決問題的邏輯代碼。這裡的代碼在後台執行,並通過回調方法向外層傳遞事件。在其他項目中這個模塊被稱為用例Use Case。一個項目中可能有很多小Interactor,這符合單一職責原則,而且這樣更容易讓人接受。
Model – 在業務邏輯代碼中操作的業務模型。
Repository – 包含接口讓外層類實現,如操作數據庫的類等。Interactor用這些接口的實現類來讀取和存儲數據。這也叫資源庫模式Repository Pattern。
Executor – 通過Worker Thread Executor讓Interactor在後台執行。一般不需要修改這個包裡的代碼。
在這個簡單例子中,我們的use case是在應用啟動時讀取數據庫中的歡迎語句並展示。下面演示如何編寫代碼包讓use case運行起來。
前兩個包屬於外層,最後一個包屬於內層(核心層)。
presentation包負責將信息展示在屏幕上,而且包含整個MVP棧,即同時包含UI和presenter這兩個屬於不同層的組件。下面上碼。
你可以從任何一層開始編寫,我建議從內層的邏輯代碼寫起。因為邏輯代碼寫好之後可以測試,不需要activity也可以正常運行。
所以我們先寫一個Interactor,這個Interactor包含了處理業務邏輯的代碼。**所有的Interactor都應該在後台運行,而不應影響UI展示。**我在這裡先編寫一個WelcomingInteractor。
public interface WelcomingInteractor extends Interactor { interface Callback { void onMessageRetrieved(String message); void onRetrievalFailed(String error); } }
Callback負責與主線程的UI組件聯通。將它放在WelcomingInteractor中可以避免給所有Callback接口起不同的名字而又能將它們有效區分。而後我們要實現獲取消息的邏輯。現在已經有一個接口MessageRepository用於獲取數據:
public interface MessageRepository { String getWelcomeMessage(); }
現在我們可以用業務邏輯代碼來實現Interactor接口了。注意要實現AbstractInteractor接口,這樣代碼就會在後台執行了。
public class WelcomingInteractorImpl extends AbstractInteractor implements WelcomingInteractor { ... private void notifyError() { mMainThread.post(new Runnable() { @Override public void run() { mCallback.onRetrievalFailed("Nothing to welcome you with : ("); } }); } private void postMessage(final String msg) { mMainThread.post(new Runnable() { @Override public void run() { mCallback.onMessageRetrieved(msg); } }); } @Override public void run() { // 獲取消息 final String message = mMessageRepository.getWelcomeMessage(); // 檢查是否獲取失敗 if (message == null || message.length() == 0) { // 在主線程中通知錯誤 notifyError(); return; } // 已成功獲取消息,通知UI postMessage(message); } }
這段代碼獲取了數據,並向UI層發送數據或報錯。這裡通過Callback向UI發送信息,這個Callback扮演的是presenter的角色。這段代碼是邏輯的核心,其他代碼都是依賴框架的。看一下這個類的引用:
import com.kodelabs.boilerplate.domain.executor.Executor; import com.kodelabs.boilerplate.domain.executor.MainThread; import com.kodelabs.boilerplate.domain.interactors.WelcomingInteractor; import com.kodelabs.boilerplate.domain.interactors.base.AbstractInteractor; import com.kodelabs.boilerplate.domain.repository.MessageRepository;
可以看到,沒有和Android相關的類庫,這就是Clean架構的好處。還有就是寫邏輯代碼時不需要關心UI或數據庫,只需要調用外層實現的Callback的回調方法。
現在不需要模擬器也可以運行這段代碼了,我們編寫一個JUnit測試來確保這段代碼運行正常。
@Test public void testWelcomeMessageFound() throws Exception { String msg = "Welcome, friend!"; when(mMessageRepository.getWelcomeMessage()).thenReturn(msg); WelcomingInteractorImpl interactor = new WelcomingInteractorImpl( mExecutor, mMainThread, mMockedCallback, mMessageRepository ); interactor.run(); Mockito.verify(mMessageRepository).getWelcomeMessage(); Mockito.verifyNoMoreInteractions(mMessageRepository); Mockito.verify(mMockedCallback).onMessageRetrieved(msg); }
重復一遍,Interactor根本不知道它在Android環境下運行。
Presentation層在Clean架構中屬於外層的范圍,它依賴於框架,包含了UI展示的代碼。我們用MainActivity類在應用啟動時展示歡迎信息。
首先編寫Presenter和View的接口。View只需要展示歡迎信息。
public interface MainPresenter extends BasePresenter { interface View extends BaseView { void displayWelcomeMessage(String msg); } }
那怎麼在App啟動時運行Interactor呢?所有和View無關的代碼都寫進Presenter類中。這樣可以實現關注分離(Separation of Concerns)並能避免Activity過於復雜。這些代碼包括和Interactor交互的代碼。
在MainActivity中重寫onResume()方法。
@Override protected void onResume() { super.onResume(); // 在活動resume時開始獲取數據 mPresenter.resume(); }
所有的Presenter在繼承BasePresenter時都要實現resume()方法。我們在MainPresenter的onResume()方法中啟動Interactor。
@Override public void resume() { mView.showProgress(); // 初始化Interactor WelcomingInteractor interactor = new WelcomingInteractorImpl( mExecutor, mMainThread, this, mMessageRepository ); // 執行interactor interactor.execute(); }
execute()方法會在後台線程中調用WelcomingInteractorImpl類的run()方法。run()方法的實現可以看上文寫一個內層的Interactor部分。
你可能已經發現Interactor很像AsyncTask,都是提供所有需要的東西然後運行。那為什麼不用AsyncTask呢?因為AsyncTask是Android的代碼,需要模擬器來運行與測試。
在上面的代碼中我們給Interactor傳入了下列屬性:
為什麼this也是Callback呢?因為MainActivity的MainPresenter實現了Callback接口:
public class MainPresenterImpl extends AbstractPresenter implements MainPresenter, WelcomingInteractor.Callback {
我們就是這麼監聽Interactor的事件的。下面是MainPresenter的代碼:
@Override public void onMessageRetrieved(String message) { mView.hideProgress(); mView.displayWelcomeMessage(message); } @Override public void onRetrievalFailed(String error) { mView.hideProgress(); onError(error); }
在代碼段中我們看到的View其實就是實現了MainPresenter.View接口的MainActivity:
public class MainActivity extends AppCompatActivity implements MainPresenter.View {
View用於展示消息:
@Override public void displayWelcomeMessage(String msg) { mWelcomeTextView.setText(msg); }
Presentation層的東西就這麼多了。
repository中的接口就在storage層實現。所有與數據庫相關的代碼都在這裡。資源庫模式下數據的來源是不確定的,意思是邏輯代碼不關心數據的來源,不論是數據庫、服務器還是文件。
你可以用ContentProvider或DBFlow等ORM工具處理更復雜的數據。如果你需要從網絡獲取數據那你可以用Retrofit。如果你只需要基本的鍵值對存儲那你可以用SharedPreferences。不管怎樣,一定要選對工具。
這裡我們的數據庫不是真正的數據庫,只是一個模擬了延遲的一個很簡單的類。
public class WelcomeMessageRepository implements MessageRepository { @Override public String getWelcomeMessage() { String msg = "Welcome, friend!"; // 模擬網絡/數據庫延遲 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return msg; } }
WelcomingInteractor可能以為延遲是網絡或其他原因造成的,但它並不關心,它只需要數據提供者實現了MessageRepository接口。
詳細代碼請看這個git repo。總結一下各個類的觸發順序:
MainActivity ->MainPresenter -> WelcomingInteractor -> WelcomeMessageRepository -> WelcomingInteractor -> MainPresenter -> MainActivity
控制流的順序:
Outer — Mid — Core — Outer — Core — Mid — Outer
在一個use case中多次訪問外層很正常。比如當你要顯示、存儲加訪問網絡,你的控制流會訪問外層至少三次。
通常一個App的成長過程都是這樣的: 第一階:先用最少的成本和時間快速把東西做出來。 第二階段:積累一定用戶量之後在小步快跑的迭代功能。 第三階段:性能和體驗上逐
一個簡單易用的導航欄TitleBar,可以輕松實現IOS導航欄的各種效果整個代碼全部集中在TitleBar.java中,所有控件都動態生成,動態布局。不需要引用任
View事件傳遞 touch事件分發 學習Android一年有余,今天開始以自己的理解去介紹一下Android開發常用到的基礎技術。第一個介紹的是View 的事件
隨著市場上越來越多的APP上線,好多軟件對手機的內存要求也是很大,所以我們在開發的時候一定要掌握如何去優化內存,將自己的APP盡可能優化。今天我們就一起看一下九宮