項(xiàng)目地址:Bolts-Android
Task是為了更好的書寫復(fù)雜異步操作而設(shè)計(jì)的,運(yùn)用了Javascript的Promise思想。
如果想要構(gòu)建一個(gè)響應(yīng)迅速的Android應(yīng)用,那么你就不能在UI線程中運(yùn)行任何耗時(shí)操作,避免阻塞UI線程,這也就意味著你需要在后臺中執(zhí)行大量的操作。為了讓這一過程變得更簡單,我們增加了這個(gè)叫做Task的類。一個(gè)Task代表一個(gè)異步操作。通常情況下,我們會寫一個(gè)方法返回一個(gè)Task,這個(gè)Task具有繼續(xù)操作任務(wù)結(jié)果的能力。當(dāng)這個(gè)Task被方法返回時(shí),它已經(jīng)開始執(zhí)行它的任務(wù)了。Task不與特定的線程模型進(jìn)行綁定:它代表要被完成的操作,而不是執(zhí)行操作的地點(diǎn)。Task與其他異步方法(Callbacks、AsyncTask)相比有許多優(yōu)勢。
-
Task占用更少的系統(tǒng)資源,因?yàn)?code>Task在等待其他Tasks的時(shí)候不占用線程。 - 執(zhí)行一系列
Task的時(shí)候不需要像你使用CallBack時(shí)一樣寫出金字塔式的嵌套代碼。 -
Task是可以組合的,允許你執(zhí)行分支、并行和復(fù)合型的錯(cuò)誤處理,不需要用到嵌套的代碼和各種復(fù)雜命名的CallBack。 - 你可以有序的整理基于任務(wù)的代碼并執(zhí)行它們,而不是將你的邏輯分散在凌亂的回調(diào)函數(shù)中。
continueWith方法
每個(gè)Task都有一個(gè)continueWith方法,帶有一個(gè)Continuation參數(shù)。Continuation是一個(gè)接口,你可以實(shí)現(xiàn)它的then方法,then方法會在任務(wù)完成的時(shí)候調(diào)用,你可以在這里檢查任務(wù)的完成狀態(tài)并做相應(yīng)的處理。
saveAsync(obj).continueWith(new Continuation<ParseObject, Void>() {
public Void then(Task<ParseObject> task) throws Exception {
if (task.isCancelled()) {
// the save was cancelled.
} else if (task.isFaulted()) {
// the save failed.
Exception error = task.getError();
} else {
// the object was saved successfully.
ParseObject object = task.getResult();
}
return null;
}
});
Tasks使用了強(qiáng)類型的Java泛型,所有一開始就想要書寫語法正確的代碼可能需要一點(diǎn)點(diǎn)技巧。下面通過一個(gè)例子深入了解一下。
/**
Gets a String asynchronously.
*/
public Task<String> getStringAsync() {
// Let's suppose getIntAsync() returns a Task<Integer>.
return getIntAsync().continueWith(
// This Continuation is a function which takes an Integer as input,
// and provides a String as output. It must take an Integer because
// that's what was returned from the previous Task.
new Continuation<Integer, String>() {
// The Task getIntAsync() returned is passed to "then" for convenience.
public String then(Task<Integer> task) throws Exception {
Integer number = task.getResult();
return String.format("%d", Locale.US, number);
}
}
);
}
在許多情況下,你可能只是想在前一個(gè)任務(wù)成功結(jié)束時(shí)做一點(diǎn)微小的工作,并把發(fā)生錯(cuò)誤和任務(wù)取消的情況留到以后處理,那么你可以使用onSuccess方法代替continueWith方法。
saveAsync(obj).onSuccess(new Continuation<ParseObject, Void>() {
public Void then(Task<ParseObject> task) throws Exception {
// the object was saved successfully.
return null;
}
});
Task鏈?zhǔn)骄幊?/h3>
我們對Task做了一些膜法,支持鏈?zhǔn)秸{(diào)用而不用編寫復(fù)雜的嵌套邏輯。你可以使用continueWithTask來代替continueWith,它會返回一個(gè)新的Task。由continueWithTask返回的Task在新的任務(wù)執(zhí)行結(jié)束之前不會被認(rèn)為結(jié)束。另外,onSuccessTask是能夠返回新Task版的onSuccess,你可以使用onSuccess/continueWith來執(zhí)行更多的同步操作,或者使用onSuccessTask/continueWithTask來執(zhí)行更多的異步操作。
final ParseQuery<ParseObject> query = ParseQuery.getQuery("Student");
query.orderByDescending("gpa");
findAsync(query).onSuccessTask(new Continuation<List<ParseObject>, Task<ParseObject>>() {
public Task<ParseObject> then(Task<List<ParseObject>> task) throws Exception {
List<ParseObject> students = task.getResult();
students.get(0).put("valedictorian", true);
return saveAsync(students.get(0));
}
}).onSuccessTask(new Continuation<ParseObject, Task<List<ParseObject>>>() {
public Task<List<ParseObject>> then(Task<ParseObject> task) throws Exception {
ParseObject valedictorian = task.getResult();
return findAsync(query);
}
}).onSuccessTask(new Continuation<List<ParseObject>, Task<ParseObject>>() {
public Task<ParseObject> then(Task<List<ParseObject>> task) throws Exception {
List<ParseObject> students = task.getResult();
students.get(1).put("salutatorian", true);
return saveAsync(students.get(1));
}
}).onSuccess(new Continuation<ParseObject, Void>() {
public Void then(Task<ParseObject> task) throws Exception {
// Everything is done!
return null;
}
});
異常處理
在書寫你的應(yīng)用時(shí),謹(jǐn)慎的選擇調(diào)用continueWith或onSuccess可以幫助你控制異常的傳遞方式。使用continueWith可以傳遞發(fā)生的異?;?qū)λM(jìn)行某些處理。你可以考慮用拋出異常的方式使一個(gè)Task失敗,事實(shí)上,如果你在continuation中拋出了一個(gè)異常,Task的結(jié)果會顯示失敗并返回這個(gè)異常。
final ParseQuery<ParseObject> query = ParseQuery.getQuery("Student");
query.orderByDescending("gpa");
findAsync(query).onSuccessTask(new Continuation<List<ParseObject>, Task<ParseObject>>() {
public Task<ParseObject> then(Task<List<ParseObject>> task) throws Exception {
List<ParseObject> students = task.getResult();
students.get(0).put("valedictorian", true);
// Force this callback to fail.
throw new RuntimeException("There was an error.");
}
}).onSuccessTask(new Continuation<ParseObject, Task<List<ParseObject>>>() {
public Task<List<ParseObject>> then(Task<ParseObject> task) throws Exception {
// Now this continuation will be skipped.
ParseObject valedictorian = task.getResult();
return findAsync(query);
}
}).continueWithTask(new Continuation<List<ParseObject>, Task<ParseObject>>() {
public Task<ParseObject> then(Task<List<ParseObject>> task) throws Exception {
if (task.isFaulted()) {
// This error handler WILL be called.
// The exception will be "There was an error."
// Let's handle the error by returning a new value.
// The task will be completed with null as its value.
return null;
}
// This will also be skipped.
List<ParseObject> students = task.getResult();
students.get(1).put("salutatorian", true);
return saveAsync(students.get(1));
}
}).onSuccess(new Continuation<ParseObject, Void>() {
public Void then(Task<ParseObject> task) throws Exception {
// Everything is done! This gets called.
// The task's result is null.
return null;
}
});
這有利于書寫很長的只處理成功情況的鏈?zhǔn)秸{(diào)用,只需要在調(diào)用鏈末尾書寫一個(gè)錯(cuò)誤處理即可。
創(chuàng)建Task
一開始,你可能只是使用類似findAsync或saveAsync這種方法返回的簡單的Task。但是,對于更高級的方案,你可能想創(chuàng)建自定義的Task。為了實(shí)現(xiàn)這個(gè)需求,你創(chuàng)建了一個(gè)TaskCompletionSource。這個(gè)對象允許你創(chuàng)建新的Task并且控制它的執(zhí)行結(jié)果是已完成或取消。在你創(chuàng)建了一個(gè)Task之后,你需要調(diào)用setResult,seterror,setCancelled來觸發(fā)它之后的操作。
public Task<String> succeedAsync() {
TaskCompletionSource<String> successful = new TaskCompletionSource<>();
successful.setResult("The good result.");
return successful.getTask();
}
public Task<String> failAsync() {
TaskCompletionSource<String> failed = new TaskCompletionSource<>();
failed.setError(new RuntimeException("An error message."));
return failed.getTask();
}
如果你在一個(gè)Task創(chuàng)建時(shí)就知道他某個(gè)結(jié)果需要執(zhí)行的操作,你可以使用下面這些比較方便的方法。
Task<String> successful = Task.forResult("The good result.");
Task<String> failed = Task.forError(new RuntimeException("An error message."));
創(chuàng)建異步方法
使用如下方法,你可以很輕松的創(chuàng)建你自己的異步任務(wù)并返回一個(gè)Task。
public Task<ParseObject> fetchAsync(ParseObject obj) {
final TaskCompletionSource<ParseObject> tcs = new TaskCompletionSource<>();
obj.fetchInBackground(new GetCallback() {
public void done(ParseObject object, ParseException e) {
if (e == null) {
tcs.setResult(object);
} else {
tcs.setError(e);
}
}
});
return tcs.getTask();
}
我們同樣提供了方法方便你在代碼塊中創(chuàng)建Task。當(dāng)執(zhí)行到call代碼塊時(shí),callInBackground會在后臺線程池中執(zhí)行Task。
Task.callInBackground(new Callable<Void>() {
public Void call() {
// Do a bunch of stuff.
}
}).continueWith(...);
順次執(zhí)行Task
Task允許你執(zhí)行一連串的異步任務(wù),每個(gè)任務(wù)都會在前一個(gè)任務(wù)完成后再執(zhí)行。舉個(gè)例子,你想要?jiǎng)h除你博客上的所有評論。
ParseQuery<ParseObject> query = ParseQuery.getQuery("Comments");
query.whereEqualTo("post", 123);
findAsync(query).continueWithTask(new Continuation<List<ParseObject>, Task<Void>>() {
public Task<Void> then(Task<List<ParseObject>> results) throws Exception {
// Create a trivial completed task as a base case.
Task<Void> task = Task.forResult(null);
for (final ParseObject result : results) {
// For each item, extend the task with a function to delete the item.
task = task.continueWithTask(new Continuation<Void, Task<Void>>() {
public Task<Void> then(Task<Void> ignored) throws Exception {
// Return a task that will be marked as completed when the delete is finished.
return deleteAsync(result);
}
});
}
return task;
}
}).continueWith(new Continuation<Void, Void>() {
public Void then(Task<Void> ignored) throws Exception {
// Every comment was deleted.
return null;
}
});
同時(shí)執(zhí)行多個(gè)Task
你可以調(diào)用whenall方法來同步執(zhí)行多個(gè)Task。Task.whenall會創(chuàng)建一個(gè)新的Task,此Task會在輸入的所有Task都執(zhí)行完畢后再標(biāo)記為完成狀態(tài),此Task只會在所有傳入Task都成功時(shí)標(biāo)記為成功狀態(tài)。同時(shí)執(zhí)行任務(wù)比順次執(zhí)行任務(wù)更快,但可能消耗更多的系統(tǒng)資源和帶寬。
ParseQuery<ParseObject> query = ParseQuery.getQuery("Comments");
query.whereEqualTo("post", 123);
findAsync(query).continueWithTask(new Continuation<List<ParseObject>, Task<Void>>() {
public Task<Void> then(Task<List<ParseObject>> results) throws Exception {
// Collect one task for each delete into an array.
ArrayList<Task<Void>> tasks = new ArrayList<Task<Void>>();
for (ParseObject result : results) {
// Start this delete immediately and add its task to the list.
tasks.add(deleteAsync(result));
}
// Return a new task that will be marked as completed when all of the deletes are
// finished.
return Task.whenAll(tasks);
}
}).onSuccess(new Continuation<Void, Void>() {
public Void then(Task<Void> ignored) throws Exception {
// Every comment was deleted.
return null;
}
});
Task Executors
所有continueWith和onSuccess方法都可將java.util.concurrent.Executor的實(shí)例作為參數(shù)傳入。這讓你可以控制后續(xù)任務(wù)在哪里執(zhí)行。默認(rèn)狀態(tài)下,Task.call()會在當(dāng)前線程執(zhí)行Callable,Task.callInBackgorund會在自己的線程池中執(zhí)行,你也可以提供自己的Executor在其它線程中執(zhí)行任務(wù)。舉個(gè)例子,假如你想要在一個(gè)特別的線程池中執(zhí)行任務(wù)。
static final Executor NETWORK_EXECUTOR = Executors.newCachedThreadPool();
static final Executor DISK_EXECUTOR = Executors.newCachedThreadPool();
final Request request = ...
Task.call(new Callable<HttpResponse>() {
@Override
public HttpResponse call() throws Exception {
// Work is specified to be done on NETWORK_EXECUTOR
return client.execute(request);
}
}, NETWORK_EXECUTOR).continueWithTask(new Continuation<HttpResponse, Task<byte[]>>() {
@Override
public Task<byte[]> then(Task<HttpResponse> task) throws Exception {
// Since no executor is specified, it's continued on NETWORK_EXECUTOR
return processResponseAsync(response);
}
}).continueWithTask(new Continuation<byte[], Task<Void>>() {
@Override
public Task<Void> then(Task<byte[]> task) throws Exception {
// We don't want to clog NETWORK_EXECUTOR with disk I/O, so we specify to use DISK_EXECUTOR
return writeToDiskAsync(task.getResult());
}
}, DISK_EXECUTOR);
對于常用場景,例如分發(fā)至主線程執(zhí)行,我們提供了默認(rèn)的實(shí)現(xiàn):Task.UI_THREAD_EXECUTOR,Task.BACKGROUND_EXECUTOR。
fetchAsync(object).continueWith(new Continuation<ParseObject, Void>() {
public Void then(Task<ParseObject> object) throws Exception {
TextView textView = (TextView)findViewById(R.id.name);
textView.setText(object.get("name"));
return null;
}
}, Task.UI_THREAD_EXECUTOR);
捕獲變量
將代碼重構(gòu)為多個(gè)回調(diào)的難點(diǎn)在于他們具有不同的變量作用域。Java允許你使用外部域的變量,但前提是他必須被聲明為final,這非常的不方便,因?yàn)闀?dǎo)致這些變量不可變。這也是我們添加Capture這個(gè)類的原因,它允許你在各個(gè)回調(diào)之間共享變量。只需要你調(diào)用get,set方法來改變它的值即可。
// Capture a variable to be modified in the Task callbacks.
final Capture<Integer> successfulSaveCount = new Capture<Integer>(0);
saveAsync(obj1).onSuccessTask(new Continuation<ParseObject, Task<ParseObject>>() {
public Task<ParseObject> then(Task<ParseObject> obj1) throws Exception {
successfulSaveCount.set(successfulSaveCount.get() + 1);
return saveAsync(obj2);
}
}).onSuccessTask(new Continuation<ParseObject, Task<ParseObject>>() {
public Task<ParseObject> then(Task<ParseObject> obj2) throws Exception {
successfulSaveCount.set(successfulSaveCount.get() + 1);
return saveAsync(obj3);
}
}).onSuccessTask(new Continuation<ParseObject, Task<ParseObject>>() {
public Task<ParseObject> then(Task<ParseObject> obj3) throws Exception {
successfulSaveCount.set(successfulSaveCount.get() + 1);
return saveAsync(obj4);
}
}).onSuccess(new Continuation<ParseObject, Void>() {
public Void then(Task<ParseObject> obj4) throws Exception {
successfulSaveCount.set(successfulSaveCount.get() + 1);
return null;
}
}).continueWith(new Continuation<Void, Integer>() {
public Integer then(Task<Void> ignored) throws Exception {
// successfulSaveCount now contains the number of saves that succeeded.
return successfulSaveCount.get();
}
});
取消Task
如果想要取消Task,需要先創(chuàng)建一個(gè)CancellationTokenSource,并把token傳給所有你想要取消的創(chuàng)建Task的方法,之后只要調(diào)用cancel()就會結(jié)束所有與該token關(guān)聯(lián)的Task。
CancellationTokenSource cts = new CancellationTokenSource();
Task<Integer> stringTask = getIntAsync(cts.getToken());
cts.cancel();
取消異步任務(wù)需要修改方法接受一個(gè)CancellationToken并調(diào)用isCancellationRequested()來決定什么時(shí)候終止操作。
/**
Gets an Integer asynchronously.
*/
public Task<Integer> getIntAsync(final CancellationToken ct) {
// Create a new Task
final TaskCompletionSource<Integer> tcs = new TaskCompletionSource<>();
new Thread() {
@Override
public void run() {
// Check if cancelled at start
if (ct.isCancellationRequested()) {
tcs.setCancelled();
return;
}
int result = 0;
while (result < 100) {
// Poll isCancellationRequested in a loop
if (ct.isCancellationRequested()) {
tcs.setCancelled();
return;
}
result++;
}
tcs.setResult(result);
}
}.start();
return tcs.getTask();
}