确定数据接口的请求参数和返回数据格式,然后服务器人员会尽快提供给客户端可调试的假数据接口。不过有时候就算是假数据接口也来不及提供,或者是接口数据格式来回变动——很可能是客户端展示的原因,这个是产品设计决定的,总之带来的问题就算服务器端的开发进度会影响客户端。
模拟数据接口功能的方式,希望能减少一些开发中的烦恼。
分层设计、可开关模拟模块、不同网络请求结果的制造这几个方面来阐述下模拟接口模块的设计。
List<Task> getTasks(),UI层一个界面展示任务列表,那么它使用ITaskApi来获取数据,而具体ITaskApi的实现类可以由DataApi层的一个工厂类DataApiManager来统一提供。
List<Task>。但这种代码只能是开发阶段有,最终apk不应该存在。
public class Task {
public String name;
}
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("请求数据成功: ");
for (int i = 0; i < data.size(); i++) {
sb.append(data.get(i).name).append(" ");
}
tv_data.setText(sb.toString());
requesting = false;
}
@Override
public void onError(Throwable e) {
// 显示错误
tv_data.setText("错误: " + e.getMessage());
requesting = false;
}
@Override
public void onStart() {
// 显示loading
tv_data.setText("正在加载...");
}
});
}
}
public void onRefreshClick(View view) {
getData();
}
}
DataApiCallback<T>回调对象,T是将返回的数据类型。
public interface DataApiCallback<T> {
void onSuccess(T data);
void onError(Throwable e);
void onStart();
}
public interface ITaskApi {
void getTasks(DataApiCallback<List<Task>> callback);
}
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();
}
}
public class NetTaskApi implements ITaskApi {
@Override
public void getTasks(DataApiCallback<List<Task>> callback) {
// 暂时没用实际的数据接口实现
callback.onError(new Exception("数据接口未实现"));
}
}
buildVariant使用gradle来构建项目时,可以指定不同的buildType,默认会有debug和release两个“构建类型”。此外,还可以提供productFlavors来提供不同的“产品类型”,如demo版,专业版等。每一种productFlavor和一个buildType组成一个buildVariant(构建变种)。可以为每一个buildType,buildVariant,或productFlavor指定特定的代码资源。
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;
}
}
public class MockApiManager {
public static <T> T getMockApi(Class<T> dataApiClass) {
return null;
}
}
“网络错误、服务器错误、成功”三种状态,而且还提供一定的网络时间延迟的模拟。
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;
}
}
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;
}
}
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线程中执行。
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);
}
}
}
本文为 @ 21CTO 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。