亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

App開發:模擬服務器數據接口 - MockApi

2019-11-14 12:51:15
字體:
來源:轉載
供稿:網友


為了方便app開發過程中,不受服務器接口的限制,便于客戶端功能的快速測試,可以在客戶端實現一個模擬服務器數據接口的MockApi模塊。本篇文章就嘗試為使用gradle的android項目設計實現MockApi。

需求概述

在app開發過程中,在和服務器人員協作時,一般會第一時間確定數據接口的請求參數和返回數據格式,然后服務器人員會盡快提供給客戶端可調試的假數據接口。不過有時候就算是假數據接口也來不及提供,或者是接口數據格式來回變動——很可能是客戶端展示的原因,這個是產品設計決定的,總之帶來的問題就算服務器端的開發進度會影響客戶端。

所以,如果可以在客戶端的正常項目代碼中,自然地(不影響最終apk)添加一種模擬服務器數據返回的功能,這樣就可以很方便的在不依賴服務器的情況下展開客戶端的開發。而且考慮一種情況,為了測試不同網絡速度,網絡異常以及服務器錯誤等各種“可能的真實數據請求的場景”對客戶端UI交互的影響,我們往往需要做很多手動測試——千篇一律!如果本地有一種控制這種服務器響應行為的能力那真是太好了。

本文將介紹一種為客戶端項目增加模擬數據接口功能的方式,希望能減少一些開發中的煩惱。

設計過程

下面從分層設計、可開關模擬模塊、不同網絡請求結果的制造這幾個方面來闡述下模擬接口模塊的設計。為了表達方便,這里要實現的功能表示為“數據接口模擬模塊”,對應英文為MockDataApi,或簡寫為MockApi,正常的數據接口模塊定義為DataApi。

分層思想

說到分層設計,MVC、MVP等模式一定程度上就起到了對代碼所屬功能的一個劃分。分層設計簡單的目標就是讓項目代碼更加清晰,各層相互獨立,好處不多說。

移動app的邏輯主要就是交互邏輯,然后需要和服務器溝通數據。所以最簡單的情形下可以將一個功能(比如一個列表界面)的實現分UI層和數據訪問層。

下面將數據訪問層表述為DataApi模塊,DataApi層會定義一系列的接口來描述不同類別的數據訪問請求。UI層使用這些接口來獲取數據,而具體的數據訪問實現類就可以在不修改UI層代碼的情況下進行替換。

例如,有一個ITaskApi定義了方法List<Task> getTasks(),UI層一個界面展示任務列表,那么它使用ITaskApi來獲取數據,而具體ITaskApi的實現類可以由DataApi層的一個工廠類DataApiManager來統一提供。

有了上面的分層設計,就可以為UI層動態提供真實數據接口或模擬數據接口。

模擬接口的開關

可能大家都經歷過在UI層代碼里臨時寫一些假數據得情況。比如任務列表界面,開發初,可以寫一個mockTaskData()方法來返回一個List<Task>。但這種代碼只能是開發階段有,最終apk不應該存在。

不能讓“模擬數據”的代碼到處散亂,在分層設計的方式下,可以將真實的數據接口DataApi和模擬數據接口MockDataApi分別作為兩個數據接口的實現模塊,這樣就可以根據項目的構建類型來動態提供不同的數據接口實現。

實現MockDataApi的動態提供的方法也不止一種。一般的java項目可以使用“工廠模式+反射”來動態提供不同的接口實現類,再專業點就是依賴注入——DI框架的使用了。目前gradle是java的最先進的構建工具,它支持根據buildType來分別指定不同的代碼資源,或不同的依賴??梢栽谝粋€單獨的類庫module(就是maven中的項目)中來編寫各種MockDataApi的實現類,然后主app module在debug構建時添加對它的依賴,此時數據接口的提供者DataApiManager可以向UI層返回這些mock類型的實例。

為了讓“正常邏輯代碼”和mock相關代碼的關聯盡量少,可以提供一個MockApiManager來唯一獲取各個MockDataApi的實例。然后在debug構建下的MockApiManager會返回提供了mock實現的數據接口實例,而release構建時MockApiManager會一律返null。

不同請求結果的模擬

MockApi在多次請求時提供不同的網絡請求結果,如服務器錯誤,網絡錯誤,成功等,并模擬出一定的網絡延遲,這樣就很好的滿足了UI層代碼的各種測試需求。

為了達到上述目標,定義一個接口IMockApiStrategy來表示對數據請求的響應策略,它定義了方法onResponse(int callCount)。根據當前請求的次數callCount,onResponse()會得到不同的模擬響應結果。很明顯,可以根據測試需要提供不同的請求響應策略,比如不斷返回成功請求,或者不斷返回錯誤請求,或輪流返回成功和錯誤等。

關鍵代碼解析

下面就給出各個部分的關鍵代碼,來說明以上所描述的MockDataApi模塊的實現。

UI層代碼

作為示例,界面MainActivity是一個“任務列表”的展示。任務由Task類表示:

public class Task {  public String name;}

界面MainActivity使用一個TextView來顯示“加載中、任務列表、網絡錯誤”等效果,并提供一個Button來點擊刷新數據。代碼如下:

public class MainActivity extends Activity {    PRivate TextView tv_data;    private boolean requesting = false;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv_data = (TextView) findViewById(R.id.tv_data);        getData();    }    private void getData() {        if (requesting) return;        requesting = true;        ITaskApi api = DataApiManager.ofTask();        if (api != null) {            api.getTasks(new DataApiCallback<List<Task>>() {                @Override                public void onSuccess(List<Task> data) {                    // 顯示數據                    StringBuilder sb = new StringBuilder("請求數據成功:/n");                    for (int i = 0; i < data.size(); i++) {                        sb.append(data.get(i).name).append("/n");                    }                    tv_data.setText(sb.toString());                    requesting = false;                }                @Override                public void onError(Throwable e) {                    // 顯示錯誤                    tv_data.setText("錯誤:/n" + e.getMessage());                    requesting = false;                }                @Override                public void onStart() {                    // 顯示loading                    tv_data.setText("正在加載...");                }            });        }    }    public void onRefreshClick(View view) {        getData();    }}

在UI層代碼中,使用DataApiManager.ofTask()獲得數據訪問接口的實例??紤]到數據請求會是耗時的異步操作,這里每個數據接口方法接收一個DataApiCallback<T> 回調對象,T是將返回的數據類型。

public interface DataApiCallback<T>  {    void onSuccess(T data);    void onError(Throwable e);    void onStart();}

接口DataApiCallback定義了數據接口請求數據開始和結束時的通知。

DataApiManager

根據分層設計,UI層和數據訪問層之間的通信就是基于DataApi接口的,每個DataApi接口提供一組相關數據的獲取方法。獲取Task數據的接口就是ITaskApi:

public interface ITaskApi {    void getTasks(DataApiCallback<List<Task>> callback);}

UI層通過DataApiManager來獲得各個DataApi接口的實例。也就是在這里,會根據當前項目構建是debug還是release來選擇性提供MockApi或最終的DataApi。

public class DataApiManager {    private static final boolean MOCK_ENABLE = BuildConfig.DEBUG;    public static ITaskApi ofTask() {        if (MOCK_ENABLE) {            ITaskApi api = MockApiManager.getMockApi(ITaskApi.class);            if (api != null) return api;        }        return new NetTaskApi();    }}

當MOCK_ENABLE為true時,會去MockApiManager檢索一個所需接口的mock實例,如果沒找到,會返回真實的數據接口的實現,上面的NetTaskApi就是。倘若現在服務器還無法進行聯合調試,它的實現就簡單的返回一個服務器錯誤:

public class NetTaskApi implements ITaskApi {    @Override    public void getTasks(DataApiCallback<List<Task>> callback) {        // 暫時沒用實際的數據接口實現        callback.onError(new Exception("數據接口未實現"));    }}

MockApiManager

DataApiManager利用MockApiManager來獲取數據接口的mock實例。這樣的好處是模擬數據接口的相關類型都被“封閉”起來,僅通過一個唯一類型來獲取已知的DataApi的一種(這里就指mock)實例。這樣為分離出mock相關代碼打下了基礎。

在DataApiManager中,獲取數據接口實例時會根據開關變量MOCK_ENABLE判斷是否可以返回mock實例。僅從功能上看是滿足動態提供MockApi的要求了。不過,為了讓最終release構建的apk中不包含多余的mock相關的代碼,可以利用gradle提供的buildVariant。

buildVariant使用gradle來構建項目時,可以指定不同的buildType,默認會有debug和release兩個“構建類型”。此外,還可以提供productFlavors來提供不同的“產品類型”,如demo版,專業版等。每一種productFlavor和一個buildType組成一個buildVariant(構建變種)??梢詾槊恳粋€buildType,buildVariant,或productFlavor指定特定的代碼資源。

這里利用buildType來為debug和release構建分別指定不同的MockApiManager類的實現。

默認的項目代碼是在src/main/java/目錄下,創建目錄/src/debug/java/來放置只在debug構建時編譯的代碼。在/src/release/java/目錄下放置只在release構建時編譯的代碼。

debug構建時的MockApiManager
public class MockApiManager {    private static final MockApiManager INSTANCE = new MockApiManager();    private HashMap<String, BaseMockApi> mockApis;    private MockApiManager() {}    public static <T> T getMockApi(Class<T> dataApiClass) {        if (dataApiClass == null) return null;        String key = dataApiClass.getName();        try {            T mock = (T) getInstance().mockApis.get(key);            return mock;        } catch (Exception e) {            return null;        }    }    private void initApiTable() {        mockApis = new HashMap<>();        mockApis.put(ITaskApi.class.getName(), new MockTaskApi());    }    private static MockApiManager getInstance() {        if (INSTANCE.mockApis == null) {            synchronized (MockApiManager.class) {                if (INSTANCE.mockApis == null) {                    INSTANCE.initApiTable();                }            }        }        return INSTANCE;    }}

靜態方法getMockApi()根據傳遞的接口類型信息從mockApis中獲取可能的mock實例,mockApis中注冊了需要mock的那些接口的實現類對象。

release構建時的MockApiManager
public class MockApiManager {    public static <T> T getMockApi(Class<T> dataApiClass) {        return null;    }   }

因為最終release構建時是不需要任何mock接口的,所以此時getMockApi()一律返回null。也沒有任何和提供mock接口相關的類型。

通過為debug和release構建提供不同的MockApiManager代碼,就徹底實現了MockApi代碼的動態添加和移除。

MockApi的實現

模擬數據接口的思路非常簡單:根據請求的次數callCount,運行一定的策略來不斷地返回不同的響應結果。響應結果包括“網絡錯誤、服務器錯誤、成功”三種狀態,而且還提供一定的網絡時間延遲的模擬。

IMockApiStrategy

接口IMockApiStrategy的作用就是抽象對請求返回不同響應結果的策略,響應結果由IMockApiStrategy.Response表示。

public interface IMockApiStrategy {    void onResponse(int callCount, Response out);    /**     * Mock響應返回結果,表示響應的狀態     */    class Response {        public static final int STATE_NETWORK_ERROR = 1;        public static final int STATE_SERVER_ERROR = 2;        public static final int STATE_SUCCESS = 3;        public int state = STATE_SUCCESS;        public int delayMillis = 600;    }}

Response表示的響應結果包含結果狀態和延遲時間。

作為一個默認的實現,WheelApiStrategy類根據請求次數,不斷返回上述的三種結果:

public class WheelApiStrategy implements IMockApiStrategy {    @Override    public void onResponse(int callCount, Response out) {        if (out == null) return;        int step = callCount % 10;        switch (step) {            case 0:            case 1:            case 2:            case 3:                out.state = Response.STATE_SUCCESS;                break;            case 4:            case 5:                out.state = Response.STATE_SERVER_ERROR;                break;            case 6:            case 7:                out.state = Response.STATE_SUCCESS;                break;            case 8:            case 9:                out.state = Response.STATE_NETWORK_ERROR;                break;        }        out.delayMillis = 700;    }}

方法onResponse()的參數out僅僅是為了避免多次創建小對象,對應debug構建,倒也沒太大意義。

BaseMockApi

針對每一個數據訪問接口,都可以提供一個mock實現。比如為接口ITaskApi提供MockTaskApi實現類。

為了簡化代碼,抽象基類BaseMockApi完成了大部分公共的邏輯。

public abstract class BaseMockApi {    protected int mCallCount;    private IMockApiStrategy mStrategy;    private Response mResponse = new Response();    public Response onResponse() {        if (mStrategy == null) {            mStrategy = getMockApiStrategy();        }        if (mStrategy != null) {            mStrategy.onResponse(mCallCount, mResponse);            mCallCount++;        }        return mResponse;    }    protected IMockApiStrategy getMockApiStrategy() {        return new WheelApiStrategy();    }    protected void giveErrorResult(final DataApiCallback<?> callback, Response response) {        Action1<Object> onNext = null;        AndroidSchedulers.mainThread().createWorker().schedule(new Action0() {            @Override            public void call() {                callback.onStart();            }        });        switch (response.state) {            case Response.STATE_NETWORK_ERROR:                onNext = new Action1<Object>() {                    @Override                    public void call(Object o) {                        callback.onError(new IOException("mock network error."));                    }                };                break;            case Response.STATE_SERVER_ERROR:                onNext = new Action1<Object>() {                    @Override                    public void call(Object o) {                        callback.onError(new IOException("mock server error."));                    }                };                break;        }        if (onNext != null) {            Observable.just(10086)                    .delay(response.delayMillis, TimeUnit.MILLISECONDS)                    .subscribeOn(Schedulers.io())                    .observeOn(AndroidSchedulers.mainThread())                    .subscribe(onNext);        }    }     public <T> void giveSuccessResult(final Func0<T> dataMethod, final DataApiCallback<T> callback, final Response response) {        AndroidSchedulers.mainThread().createWorker().schedule(new Action0() {            @Override            public void call() {                Observable.create(new Observable.OnSubscribe<T>() {                    @Override                    public void call(Subscriber<? super T> subscriber) {                        Log.d("MOCK", "onNext Thread = " + Thread.currentThread().getName());                        subscriber.onNext(dataMethod.call());                        subscriber.onCompleted();                    }                }).                delay(response.delayMillis, TimeUnit.MILLISECONDS)                .subscribeOn(Schedulers.io())                .observeOn(AndroidSchedulers.mainThread())                .subscribe(new ApiSubcriber(callback));            }        });    }    private static class ApiSubcriber<T> extends Subscriber<T> {        private DataApiCallback<T> callback;        public ApiSubcriber(DataApiCallback<T> callback) {            this.callback = callback;        }        @Override        public void onStart() {            callback.onStart();        }        @Override        public void onCompleted() {}        @Override        public void onError(Throwable e) {            callback.onError(e);        }        @Override        public void onNext(T data) {            callback.onSuccess(data);        }    }}

onResponse()方法onResponse()根據“響應策略”來針對一次請求返回一個“響應結果”,默認的策略由方法getMockApiStrategy()提供,子類可以重寫它提供其它策略。當然策略對象本身也可以作為參數傳遞(此時此方法本身也沒多大意義了)。一個想法是,每一個MockApi類都只需要一個實例,這樣它的callCount就可以在程序運行期間得到保持。此外,大多數情況下策略對象只需要一個就行了——它是無狀態的,封裝算法的一個“函數對象”,為了多態,沒辦法讓它是靜態方法。

giveErrorResult()此方法用來執行錯誤回調,此時是不需要數據的,只需要根據response來執行一定的延遲,然后返回網絡錯誤或服務器錯誤。注意一定要在main線程上執行callback的各個方法,這里算是一個約定,方便UI層直接操作一些View對象。

giveSuccessResult()此方法用來執行成功回調,此時需要提供數據,并執行response中的delayMillis延遲。參數dataMethod用來提供需要的假數據,這里保證它的執行在非main線程中。同樣,callback的方法都在main線程中執行。

上面BaseMockApi中的rxjava的一些代碼都非常簡單,完全可以使用Thread來實現。

提供MockTaskApi

作為示例,這里為ITaskApi提供了一個mock實現類:

public class MockTaskApi extends BaseMockApi implements ITaskApi {    @Override    public void getTasks(DataApiCallback<List<Task>> callback) {        Response response = onResponse();        if (response.state == Response.STATE_SUCCESS) {            Func0<List<Task>> mockTasks = new Func0<List<Task>>() {                @Override                public List<Task> call() {                    // here to give some mock data, you can get it from a json file —— if there is.                    ArrayList<Task> tasks = new ArrayList<>();                    int start = (mCallCount - 1) * 6;                    for (int i = start; i < start + 6; i++) {                        Task task = new Task();                        task.name = "Task - " + i;                        tasks.add(task);                    }                    return tasks;                }            };            giveSuccessResult(mockTasks, callback, response);        } else {            giveErrorResult(callback, response);        }    }}

它的代碼幾乎不用過多解釋,使用代碼提供需要的返回數據是非常簡單的——就像你直接在UI層的Activity中寫一個方法來造假數據那樣。

小結

無論如何,經過上面的一系列的努力,模擬數據接口的代碼已經稍具模塊性質了,它可以被動態的開關,不影響最終的release構建,可以為需要測試的數據接口靈活的提供想要的mock實現。

很值得一提的是,整個MockApi模塊都是建立在純java代碼上的。這樣從UI層請求到數據訪問方法的執行,都最終是直接的java方法的調用,這樣可以很容易獲取調用傳遞的“請求參數”,這些參數都是java類。而如果mock是建立在網絡框架之上的,那么額外的http報文的解析是必不可少的。僅僅是為了測試的目的,分層設計,讓數據訪問層可以在真實接口和mock接口間切換,更簡單直接些。

最后,造假數據當然也可以是直接讀取json文件這樣的方式來完成,如果服務器開發人員有提供這樣的文件的話。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
九九热最新视频//这里只有精品| 亚洲美腿欧美激情另类| 久久视频在线视频| 国产小视频91| 亚州欧美日韩中文视频| 91香蕉国产在线观看| 欧美色xxxx| 久久久精品网站| 亚洲已满18点击进入在线看片| 国产精品自拍偷拍| 欧美电影免费观看电视剧大全| 亚洲欧洲成视频免费观看| 尤物99国产成人精品视频| 美女撒尿一区二区三区| 久久久久久久一区二区三区| 日韩亚洲欧美中文高清在线| 亚洲精品日韩久久久| 成人天堂噜噜噜| 欧美香蕉大胸在线视频观看| 日韩一区二区欧美| 国内免费精品永久在线视频| 福利视频一区二区| 欧美大肥婆大肥bbbbb| 久久久久久噜噜噜久久久精品| 日韩精品在线电影| 国精产品一区一区三区有限在线| 国产精品国模在线| 91精品国产自产在线观看永久| 福利二区91精品bt7086| 精品国内自产拍在线观看| 欧美日韩福利在线观看| 91亚洲精品在线| 国产精品久久久久久久久免费看| 国产专区欧美专区| 高清欧美性猛交xxxx黑人猛交| 91av在线免费观看| 欧美电影在线观看完整版| 欧美亚洲日本网站| 91香蕉嫩草神马影院在线观看| 日韩在线观看免费全集电视剧网站| 欧美伊久线香蕉线新在线| 在线电影欧美日韩一区二区私密| 亚洲国产成人av在线| 77777亚洲午夜久久多人| 国产精品色视频| 伦伦影院午夜日韩欧美限制| 久久久久久尹人网香蕉| 国产精品福利在线观看网址| 亚洲人成电影在线| 国产精品专区一| 欧美日韩电影在线观看| 97免费中文视频在线观看| 久久人人爽国产| 日韩av网站在线| 国产一区二区三区丝袜| 国产精品久久久久久久午夜| 亚洲在线视频观看| 亚洲影院色无极综合| 国产一区二区三区在线播放免费观看| 国产精品色午夜在线观看| 色偷偷av亚洲男人的天堂| 久久久免费高清电视剧观看| 日韩理论片久久| 国产午夜精品美女视频明星a级| 性色av一区二区咪爱| 国产日韩欧美日韩大片| 亚洲裸体xxxx| 久久精品99久久香蕉国产色戒| 日韩高清av一区二区三区| 日韩精品中文字幕久久臀| 欧美一级大胆视频| 日韩女优人人人人射在线视频| 欧美激情精品久久久久久黑人| 日韩欧美中文字幕在线观看| 精品国产一区二区三区久久狼5月| 日韩av中文字幕在线播放| 91在线视频精品| 日本欧美在线视频| 亚洲香蕉成人av网站在线观看| www.欧美精品| 欧美大片在线看免费观看| 少妇精69xxtheporn| 亚洲精品国精品久久99热一| 亚洲国产精品高清久久久| 久久69精品久久久久久国产越南| 日产精品99久久久久久| 国产精品免费小视频| 亚洲国产精品久久久久久| 亚洲mm色国产网站| 成人黄色生活片| 久久韩国免费视频| 欧美性高跟鞋xxxxhd| 97国产精品视频人人做人人爱| 91久久国产精品91久久性色| 亚洲欧美国产精品久久久久久久| 国模吧一区二区| 琪琪第一精品导航| 在线观看精品国产视频| 欧美极品美女视频网站在线观看免费| 久久91亚洲精品中文字幕奶水| 亚洲国产私拍精品国模在线观看| 成人xvideos免费视频| 国产剧情久久久久久| 欧美一级视频一区二区| 国产一区玩具在线观看| 欧美日韩一区二区免费在线观看| 成人免费观看网址| 欧美另类在线观看| 97在线看福利| 中文字幕精品网| 日韩欧美在线视频日韩欧美在线视频| 91精品在线观| 亚洲free性xxxx护士hd| 中文字幕亚洲综合久久| 夜夜躁日日躁狠狠久久88av| 中文字幕日韩免费视频| 色yeye香蕉凹凸一区二区av| 色婷婷av一区二区三区久久| 久久久精品久久| 亚洲精品第一国产综合精品| 日韩美女视频在线观看| 久久香蕉国产线看观看av| 国内精品一区二区三区| 欧美成人四级hd版| 国产一区二区视频在线观看| 成人黄色片在线| 成人乱色短篇合集| 国产欧美亚洲视频| 亚洲欧美中文日韩在线| 国产91九色视频| 国产精品 欧美在线| 91影院在线免费观看视频| 国产97在线观看| 国产成人中文字幕| 久久久之久亚州精品露出| 欧美xxxx做受欧美.88| 亚洲精品自产拍| 欧美日韩国产999| www.欧美精品| 欧美日韩一区二区三区| 久久黄色av网站| 国产精品福利在线观看网址| 亚洲精品一区二区在线| 亚洲人成自拍网站| 亚洲欧美日韩国产中文| 国产精品日韩在线一区| 色中色综合影院手机版在线观看| 91成人国产在线观看| 亚洲欧美激情在线视频| 日韩av在线影院| 日韩免费精品视频| 国产精品一区二区三区毛片淫片| 久久久噜噜噜久久中文字免| 亚洲视频综合网| 欧美大全免费观看电视剧大泉洋| 欧美精品久久久久a| 国产精品久久久久久久一区探花| 国产精品亚洲自拍| 国产999在线观看| 欧美精品精品精品精品免费| 亚洲一级一级97网| 亚洲第一免费播放区| 亚洲国产私拍精品国模在线观看| 亚洲自拍小视频免费观看|