参考# Bolts-Android想深入了解的同学可以直接下载下来研究。
举个粟子
在实际的开发过程中,可能会遇到这样的情况,页面的展示需要一些数据,这些数据需要调用不同的接口A、B、C、来获取,且必须按有顺序调用,因为B接口需要A接口的返回数据 ,C接口需要B接口的返回数据。
这个时候,你的代码可能是这样的:
private void fetchData() {
callA("a");
}
private void callA(String param) {
//fetch A data, then call B
callB("b");
}
private void callB(String param) {
//fetch B data, then call C
callC("c");
}
private void callC(String param) {
//fetch c data
//then display
}
这样写逻辑上没有问题,功能上也没有问题。但是看代码的时候就有点难受了,很难从宏观上理解,做这个功能,怎样的一个流程,只能点进各个方法里去看,然后跳转,再跳转......
当你理解我上面在说什么时候,你肯定会想办法,让代码看起来更清晰明了,可能你会有这样的想法:
public interface IResult {
void onResult(T data);
}
private void fetchData() {
callA("a", new IResult() {
@Override
public void onResult(String data) {
callB(data, new IResult() {
@Override
public void onResult(String data) {
callC(data, new IResult() {
@Override
public void onResult(String data) {
//then display
}
});
}
});
}
});
}
private void callA(String param, IResult callback) {
//fetch A data, then callback
callback.onResult("b");
}
private void callB(String param, IResult callback) {
//fetch B data, then callback
callback.onResult("c");
}
private void callC(String param, IResult callback) {
//fetch C data, then callback
callback.onResult("success");
}
这样写之后,就可以在
fetchData()
里清楚明白的知道,需要调用接口A、B、C之后,再进行展示的流程。但是多层的嵌套回调,虽然流程在一个方法里,但是看起来还是有点费劲。可以再作一点优化:
private void fetchData() {
final IResult cCallback = new IResult() {
@Override
public void onResult(String data) {
//then display
}
};
final IResult bCallback = new IResult() {
@Override
public void onResult(String data) {
callC(data, cCallback);
}
};
IResult aCallback = new IResult() {
@Override
public void onResult(String data) {
callB(data, bCallback);
}
};
callA("a", aCallback);
}
看到这里,你可能会想,上面写的一堆跟我最开头参考的东西,有什么关系?
确实也没什么关系。
不过,我想通过上面的粟子,来引出链式编程。
试想,如果我们的代码可以像我们的语言表达那样,先这样,再这样,再这样,最后这样
,这样写代码的话,是不是会更容易理解一些?下面就是一个链式的举例:
Task.call(new Callable() {
@Override
public String call() {
return callA("a");
}
}).continueWith(new Continuation() {
@Override
public String then(Task task) {
return callB(task.getResult());
}
}).continueWith(new Continuation() {
@Override
public String then(Task task) {
return callC(task.getResult());
}
}).continueWith(new Continuation() {
@Override
public Void then(Task task) {
//display
return null;
}
});
根据上面的链式例子,我们分析一下,想要进行链式编程,要编写一个处理各位任务的类
Task
,其中有一个静态方法call()
,这个方法需要传入一个Callable
参数,返回一个Task
实例,方便链式的调用其他方法continueWith()
,这个continueWith
方法需要传入一个Continuation
的实例,返回一个Task
实例。
了解之后,那么我们来自己写一个的简单的链式处理类
定义一个处理类MyTask
,写两个方法
public class MyTask {
private T result;
public static MyTask doSth(Callable callable) {
return new MyTask<>();
}
public MyTask continueDoSth(MyContinuation continuation) {
return new MyTask<>();
}
public T getResult() {
return result;
}
}
可以看到上面的方法里直接
new Task<>()
返回了,因为不着急处理,想先看看效果。
MyTask.doSth(new Callable() {
@Override
public String call() {
return callA("a");
}
}).continueDoSth(new MyContinuation() {
@Override
public void then(MyTask task) {
callB(task.getResult());
}
}).continueDoSth(new MyContinuation() {
@Override
public void then(MyTask task) {
callC(task.getResult());
}
}).continueDoSth(new MyContinuation() {
@Override
public void then(MyTask task) {
//display
}
});
MyContinuation.java
public interface MyContinuation {
TContinuationResult then(MyTask task);
}
现在,在具体使用时的写法上,已经跟 Bolts-Android里的一部分差不多了,就是在具体的处理类的实现上并没有进行实现。
MyTask .doSth
方法,需要执行传入的Callable
并把Callable
的执行结果写到一个实例化的MyTask
里。那么现在我们改造一下MyTask
类里的doSth
方法。
public class MyTask {
private T result;
public static MyTask doSth(Callable callable) {
MyTask task = new MyTask<>();
try {
task.result = callable.call();
} catch (Exception e) {
e.printStackTrace();
task.result = null;
}
return task;
}
public MyTask continueDoSth(MyContinuation continuation) {
MyTask task = new MyTask<>();
task.result = continuation.then(this);
return task;
}
public T getResult() {
return result;
}
}
最后完整的代码
MyTask.java
public class MyTask {
private T result;
public static MyTask doSth(Callable callable) {
MyTask task = new MyTask<>();
try {
task.result = callable.call();
} catch (Exception e) {
e.printStackTrace();
task.result = null;
}
return task;
}
public MyTask continueDoSth(MyContinuation continuation) {
MyTask task = new MyTask<>();
task.result = continuation.then(this);
return task;
}
public T getResult() {
return result;
}
}
MyContinuation.java
public interface MyContinuation {
TContinuationResult then(MyTask task);
}
Test.java
public class Test {
private static final String TAG = "------------------------> ";
public void fetchData() {
MyTask.doSth(new Callable() {
@Override
public String call() {
return callA("a");
}
}).continueDoSth(new MyContinuation() {
@Override
public String then(MyTask task) {
return callB(task.getResult());
}
}).continueDoSth(new MyContinuation() {
@Override
public String then(MyTask task) {
return callC(task.getResult());
}
}).continueDoSth(new MyContinuation() {
@Override
public String then(MyTask task) {
//display
System.out.println(task.getResult());
return null;
}
});
}
private String callA(String param) {
//fetch A data, then callback
System.out.println("callA" + TAG + param);
return param + "b";
}
private String callB(String param) {
//fetch B data, then callback
System.out.println("callB" + TAG + param);
return param + "c";
}
private String callC(String param) {
//fetch C data, then callback
System.out.println("callC" + TAG + param);
return param + "display";
}
}
运行截图:
小结
到这里,算是打开了链接编程的门了。不过,你可能会发现,根据上面写的代码,只能处理单线程的链接编程,至于真正的请求接口,肯定是要开个线程异步请求,而最后的展示又是要在UI线程执行的,所以还要涉及到线程相关的知识,有兴趣的同学自行研究。
补充多线程链式编程的代码
就拿异步请求接口的情况举例吧。
我们期望在编写代码逻辑的时候,也可以像下面一样,链式编程:
public void fetchData(final String param) {
MyTask.callInBackground(new Callable() {
@Override
public String call() throws Exception {
return request(param);
}
}).continueWith(new MyContinuation() {
@Override
public String then(MyTask task) {
display(task.getResult());
return null;
}
}, MyTask.uiExecutor);
}
private String request(String param) throws InterruptedException {
L.info("request start...");
Thread.sleep(5000);
L.info("request complete...");
return "\n" + "request param: " + param + "\n"
+ "response data: " + "this is returned json data";
}
private void display(String content) {
L.info("display start...");
L.info("content: " + content);
}
因为接口请求这种耗时操作不能在主线程里做,所以调用接口需要在异步线程里做,所以需要在MyTask类里定义一个方法public static
为了让这个方法在异步线程里执行,并且执行完之后下一步能在主线程里更新UI,我们还需要得到主线程。方便起见,我们在MyTask类里分别定义出一个异步线程和一个UI线程:
public static Executor backgroundExecutor = Executors.newFixedThreadPool(5);
public static Executor uiExecutor = new UiExecutor();
UIExecutor.java
public class UiExecutor implements Executor {
@Override
public void execute(Runnable command) {
new Handler(Looper.getMainLooper()).post(command);
}
}
接着编写callInBackground
方法和continueWith
方法
public static MyTask callInBackground(final Callable callable) {
final MyTask task = new MyTask<>();
backgroundExecutor.execute(new Runnable() {
@Override
public void run() {
L.info("callInBackground run...");
try {
task.result = callable.call();
} catch (Exception e) {
e.printStackTrace();
L.error("callInBackground exception: " + e.getMessage());
}
L.info("callInBackground complete...");
}
});
return task;
}
public MyTask continueWith(final MyContinuation continuation, Executor executor) {
final MyTask task = new MyTask<>();
executor.execute(new Runnable() {
@Override
public void run() {
L.info("continueWith run...");
task.result = continuation.then(thisTask);
L.info("continueWith complete...");
}
});
return task;
}
运行一下,看到结果 :
可以看到,continueWith里面拿到的请求数据为空。可是我们明明在request(String param)
方法里返回了数据的呀,为什么拿不到呢?
其实这就是因为异步请求接口的时候,请求和返回都是有耗时操作,如果没有相应的同步机制,那么在UI线程的代码是不会等异步线程的请求结果回来之后再执行的。
所以,在这里需要加个同步机制,在callInBackground
方法里的异步操作没执行完之前,不执行continueWith
方法。这里就需要用到synchronized
关键字了。
修改MyTask
类,给其添加一下Object锁
private static final Object lock = new Object();
再修改callInBackground
方法和continueWith
方法
public static MyTask callInBackground(final Callable callable) {
final MyTask task = new MyTask<>();
backgroundExecutor.execute(new Runnable() {
@Override
public void run() {
L.info("callInBackground run...");
try {
synchronized (lock) {
task.result = callable.call();
}
} catch (Exception e) {
e.printStackTrace();
L.error("callInBackground exception: " + e.getMessage());
}
L.info("callInBackground complete...");
}
});
return task;
}
public MyTask continueWith(final MyContinuation continuation, Executor executor) {
final MyTask task = new MyTask<>();
executor.execute(new Runnable() {
@Override
public void run() {
synchronized (lock) {
L.info("continueWith run...");
task.result = continuation.then(thisTask);
L.info("continueWith complete...");
}
}
});
return task;
}
其实只是在
callInBackground
方法里执行task.result = callable.call();
时给其加一个锁,并且在continueWith
方法执行task.result = continuation.then(thisTask);
时为其加锁。
在这种情况下,代码执行到task.result = continuation.then(thisTask);
时就会等task.result = callable.call();
执行完释放锁之后才会执行。
再次运行
可以看到,开始请求之后,过了5秒,请求完成,才开始执行continueWith run里的面的display,展示了请求拿到的数据。流程符合我们的预期,完成!