使用Clean架構開發Android應用詳細指南

來自: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0218/3979.html

原文鏈接: A detailed guide on developing Android apps using the Clean Architecture pattern

譯文鏈接: 在Android應用中使用Clean架構

自從開始開發安卓應用,我一直感覺我可以做得更好。我看過不少爛代碼,其中當然有我寫的。安卓系統的復雜性加上爛代碼勢必釀成災禍,所以從錯誤中成長就很重要。我Google了如何更好地開發應用,發現了這個叫做 Clean 架構的東西。于是我嘗試將它應用于安卓開發,根據我在類似項目中的經驗做了一些改善,寫出了這篇我覺得較為實用、值得分享的文章。

我會在這篇文章中手把手教你在Android應用中使用Clean架構。我最近一直用這種方式優雅地編寫應用。

什么是Clean架構?

有許多文章已經很好地回答了這個問題。我在這里講一講Clean架構的核心概念。

一般來說,在Clean架構中,代碼被分層成洋蔥形,層層包裹,其中有一個依賴性規則:內層不能依賴外層,即內層不知道有關外層的任何事情,所以這個架構是向內依賴的。看個圖感受一下:

圖片由Bob大叔提供

Clean架構可以使你的代碼有如下特性:

  1. 獨立于架構

  2. 易于測試

  3. 獨立于UI

  4. 獨立于數據庫

  5. 獨立于任何外部類庫

我將通過下面的例子解釋這些特性是怎么來的。如果你想深入了解Clean架構,不妨看 這篇文章這個視頻

Clean在Android中如何表現

一般來說,一個應用可以有任意數目的層,但除非你的應用到處是企業級功能邏輯,一般需要這三層:

  • 外層:實現層

  • 中層:接口適配層

  • 內層:邏輯層

接口實現層是體現架構細節的地方。實現架構的代碼是所有不用來解決問題的代碼,這包括所有與安卓相關的東西,比如創建Activity和Fragment,發送Intent以及其他聯網與數據庫的架構相關的代碼。

添加接口適配層的目的就是橋接邏輯層和架構層的代碼。

最重要的是邏輯層,這里包含了真正解決問題的代碼。這一層不包含任何實現架構的代碼, 不用模擬器也應能運行這里的代碼 。這樣一來你的邏輯代碼就有了易于測試、開發和維護的優點。這就是Clean架構的一個主要的好處。

每一個位于核心層外部的層都應能將外部模型轉成可以被內層處理的內部模型。內層不能持有屬于外層的模型類的引用。這也是由于剛才說的依賴性規則,這樣內外層可以很好地分離。

為什么要進行模型轉換呢?舉個例子,當邏輯層的模型不能直接很優雅地展現給用戶,或是需要同時展示多個邏輯層的模型時,最好創建一個ViewModel類來更好的進行UI展示。這樣一來,你就需要一個屬于外層的Converter類來將邏輯層模型轉換成合適的ViewModel。

再舉一個例子:你從外部數據庫層獲得了ContentProvider的Cursor對象,外層首先要將這個對象轉換成內層模型,再將它傳給內層處理。

在文章的最后我還提供了一些學習資源。我們已經知道了Clean架構的基本原則,現在我們來實踐一下。我會在下一部分中使用Clean架構構建一個示例功能。

如何開始寫Clean應用?

我已經寫好了一個樣板項目,里面把準備工作做好了。這相當于是一個Clean的底層包,可以直接在它的基礎上進行開發。請隨意下載、修改。項目包: Android Clean Boilerplate

開始寫用例

這一部分會詳細說明如何用在樣例項目的基礎之上以Clean方式進行開發。首先讓我們看一下應用的結構,當這只是我的習慣,不需要完全按這個進行。

一般來說一個安卓應用的結構如下:

  • 外層項目包:UI,Storage,Network等等。

  • 中層項目包:Presenter,Converter。

  • 內層項目包:Interactor,Model,Repository,Executor。

看不懂不要緊,下面有具體解釋。

外層體現了框架的細節。

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包

  • storage包

  • domain包

前兩個包屬于外層,最后一個包屬于內層(核心層)。

presentation包負責將信息展示在屏幕上,而且包含整個MVP棧,即同時包含UI和presenter這兩個屬于不同層的組件。下面上碼。

寫一個內層的Interactor

你可以從任何一層開始編寫,我建議從內層的邏輯代碼寫起。因為邏輯代碼寫好之后可以測試,不需要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);
}

}</pre>

這段代碼獲取了數據,并向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的回調方法。

測試Interactor

現在不需要模擬器也可以運行這段代碼了,我們編寫一個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);

}</pre>

重復一遍,Interactor根本不知道它在Android環境下運行。

編寫presentation層

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();

}</pre>

所有的Presenter在繼承BasePresenter時都要實現resume()方法。我們在MainPresenter的onResume()方法中啟動Interactor。

@Override
public void resume() {

mView.showProgress();

// 初始化Interactor
WelcomingInteractor interactor = new WelcomingInteractorImpl(
        mExecutor,
        mMainThread,
        this,
        mMessageRepository
);

// 執行interactor
interactor.execute();

}</pre>

execute()方法會在后臺線程中調用WelcomingInteractorImpl類的run()方法。run()方法的實現可以看上文 寫一個內層的Interactor 部分。

你可能已經發現Interactor很像AsyncTask,都是提供所有需要的東西然后運行。那為什么不用AsyncTask呢?因為AsyncTask是Android的代碼,需要模擬器來運行與測試。

在上面的代碼中我們給Interactor傳入了下列屬性:

  • ThreadExecutor對象:用于在后臺線程運行Interactor。我喜歡將這個類設計成單例。這個類屬于domain包,不需要在外層實現。

  • MainThreadImpl對象:用于在主線程中執行Interactor的Runnable對象。在依賴框架的外層代碼中我們可以訪問主線程,所以這個類要在外層實現。

  • 我們傳入this是因為MainPresenter也是一個Callback對象,Interactor要通過Callback來更新UI。

  • 我們傳入實現了MessageRepository接口的WelcomMessageRepository對象讓Interactor使用。下面會講到WelcomMessageRepository。

為什么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); }</pre>

在代碼段中我們看到的View其實就是實現了MainPresenter.View接口的MainActivity:

public class MainActivity extends AppCompatActivity implements MainPresenter.View {

View用于展示消息:

@Override
public void displayWelcomeMessage(String msg) {
    mWelcomeTextView.setText(msg);
}

Presentation層的東西就這么多了。

編寫Storage層

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;
}

}</pre>

WelcomingInteractor可能以為延遲是網絡或其他原因造成的,但它并不關心,它只需要數據提供者實現了MessageRepository接口。

詳細代碼請看這個 git repo 。總結一下各個類的觸發順序:

MainActivity ->MainPresenter -> WelcomingInteractor -> WelcomeMessageRepository -> WelcomingInteractor -> MainPresenter -> MainActivity

控制流的順序:

Outer — Mid — Core — Outer — Core — Mid — Outer

在一個use case中多次訪問外層很正常。比如當你要顯示、存儲加訪問網絡,你的控制流會訪問外層至少三次。

</div>

 本文由用戶 RobRechner 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!