App.java
// 線程池(處理異步任務(wù))
//避免重復(fù)創(chuàng)建資源(比如每個頁面都新建線程池會浪費(fèi)內(nèi)存)
private static final ExecutorService executorService = Executors.newCachedThreadPool();
public static ExecutorService getExecutorService() {
return executorService;
}
BaseFragment.java
// 2. 執(zhí)行有返回值的異步任務(wù)(比如獲取數(shù)據(jù),返回 Future 可后續(xù)獲取結(jié)果)
public <T> Future<T> runAsync(Callable<T> callable) {
return App.getExecutorService().submit(callable);
}
要理解 public <T> Future<T> runAsync(Callable<T> callable) 的調(diào)用方式,核心是抓住兩點:
Callable<T>:帶返回值的異步任務(wù)(和無返回值的 Runnable 區(qū)別);
Future<T>:異步任務(wù)的 “結(jié)果憑證”,可通過它獲取任務(wù)返回值、取消任務(wù)、判斷任務(wù)狀態(tài)。
下面結(jié)合 LSPosed 的實際場景(比如 “異步統(tǒng)計模塊作用域中已勾選的應(yīng)用數(shù)量”),舉一個完整且貼合源碼的調(diào)用例子:
第一步:明確調(diào)用場景
假設(shè)在 AppListFragment 中,需要異步統(tǒng)計當(dāng)前模塊已勾選的應(yīng)用總數(shù)(耗時操作,避免阻塞 UI),然后在主線程顯示結(jié)果。
第二步:完整調(diào)用代碼(在 AppListFragment 中)
// AppListFragment 繼承自 BaseFragment,可直接調(diào)用 runAsync(Callable<T>)
public class AppListFragment extends BaseFragment implements MenuProvider {
// ... 其他已有代碼(如 scopeAdapter、binding 等)
// 假設(shè)在某個按鈕點擊事件中觸發(fā)統(tǒng)計(也可在 onViewCreated 等生命周期中調(diào)用)
private void countCheckedApps() {
// 1. 調(diào)用 BaseFragment 的 runAsync(Callable<T>),傳入帶返回值的異步任務(wù)
// Callable<Long> 表示:異步任務(wù)返回 Long 類型結(jié)果(已勾選應(yīng)用數(shù))
Future<Long> countFuture = runAsync(() -> {
// -------------- 異步任務(wù)開始(運(yùn)行在子線程,耗時操作放這)--------------
// 從 scopeAdapter 中獲取已勾選的應(yīng)用列表(可能是耗時操作,比如讀取配置)
Set<ScopeAdapter.ApplicationWithEquals> checkedList = scopeAdapter.checkedList;
// 統(tǒng)計數(shù)量(這里可擴(kuò)展更復(fù)雜的邏輯,比如過濾系統(tǒng)應(yīng)用、黑名單應(yīng)用)
long checkedCount = 0;
for (ScopeAdapter.ApplicationWithEquals app : checkedList) {
// 模擬耗時(比如額外查詢應(yīng)用信息,實際開發(fā)中可去掉)
Thread.sleep(10);
checkedCount++;
}
// 返回統(tǒng)計結(jié)果(Callable 必須實現(xiàn) call() 方法,返回泛型 T 類型)
return checkedCount;
// -------------- 異步任務(wù)結(jié)束 --------------
});
// 2. 處理異步結(jié)果(兩種方式:阻塞獲取 / 非阻塞監(jiān)聽,推薦非阻塞)
// 方式1:非阻塞(通過主線程 Handler 延遲查詢 Future,不卡UI)
App.getMainHandler().postDelayed(() -> {
if (countFuture.isDone() && !countFuture.isCancelled()) { // 任務(wù)已完成且未取消
try {
// 通過 Future.get() 獲取異步任務(wù)的返回值(此時已無阻塞,因為 isDone 為 true)
long result = countFuture.get();
// 在UI線程顯示結(jié)果(調(diào)用 BaseFragment 的 showHint 方法)
showHint("已勾選應(yīng)用數(shù):" + result, true);
} catch (Exception e) {
// 處理異常(如任務(wù)執(zhí)行失敗、被中斷)
showHint("統(tǒng)計失?。? + e.getMessage(), false);
}
} else if (countFuture.isCancelled()) {
showHint("統(tǒng)計已取消", true);
}
}, 500); // 延遲 500ms 查詢結(jié)果(可根據(jù)實際任務(wù)耗時調(diào)整)
// 方式2:阻塞獲取(不推薦,會卡UI,僅作對比)
// 注意:絕對不能在主線程直接調(diào)用 countFuture.get()!會導(dǎo)致UI卡死
/*
new Thread(() -> {
try {
long result = countFuture.get(); // 阻塞直到任務(wù)完成
// 切換回主線程顯示結(jié)果
runOnUiThread(() -> showHint("已勾選應(yīng)用數(shù):" + result, true));
} catch (Exception e) {
runOnUiThread(() -> showHint("統(tǒng)計失敗:" + e.getMessage(), false));
}
}).start();
*/
}
// 假設(shè)在某個按鈕點擊時觸發(fā)統(tǒng)計
private void initView() {
binding.someButton.setOnClickListener(v -> countCheckedApps());
}
}
第三步:關(guān)鍵細(xì)節(jié)拆解
- Callable<T> 的實現(xiàn)
這里用 Lambda 表達(dá)式簡化 Callable<Long> 的實現(xiàn)(完整寫法是 new Callable<Long>() { @Override public Long call() throws Exception { ... } });
call() 方法中是異步執(zhí)行的邏輯(子線程),可以做耗時操作(如讀取配置、統(tǒng)計數(shù)據(jù)、網(wǎng)絡(luò)請求);
call() 可以拋出異常,后續(xù)通過 Future.get() 捕獲。 - Future<T> 的核心用法
countFuture.isDone():判斷任務(wù)是否已完成(返回 true 表示執(zhí)行結(jié)束,不管成功失敗);
countFuture.isCancelled():判斷任務(wù)是否被取消;
countFuture.get():獲取任務(wù)返回值(阻塞方法,必須在子線程調(diào)用,或確保 isDone() == true 時調(diào)用);
countFuture.cancel(true):取消任務(wù)(參數(shù) true 表示中斷正在執(zhí)行的任務(wù))。 - 為什么要這么調(diào)用?
異步統(tǒng)計:避免在主線程做循環(huán)統(tǒng)計(如果應(yīng)用數(shù)多,會導(dǎo)致 UI 卡頓);
結(jié)果回調(diào):通過 Future 拿到統(tǒng)計結(jié)果后,切換回主線程更新 UI(符合 Android 主線程更新 UI 的規(guī)范);
異常處理:捕獲 call() 中拋出的異常,避免 App 崩潰。