App.java
// 线程池(处理异步任务)
//避免重复创建资源(比如每个页面都新建线程池会浪费内存)
private static final ExecutorService executorService = Executors.newCachedThreadPool();
public static ExecutorService getExecutorService() {
return executorService;
}
BaseFragment.java
// 2. 执行有返回值的异步任务(比如获取数据,返回 Future 可后续获取结果)
public <T> Future<T> runAsync(Callable<T> callable) {
return App.getExecutorService().submit(callable);
}
要理解 public <T> Future<T> runAsync(Callable<T> callable) 的调用方式,核心是抓住两点:
Callable<T>:带返回值的异步任务(和无返回值的 Runnable 区别);
Future<T>:异步任务的 “结果凭证”,可通过它获取任务返回值、取消任务、判断任务状态。
下面结合 LSPosed 的实际场景(比如 “异步统计模块作用域中已勾选的应用数量”),举一个完整且贴合源码的调用例子:
第一步:明确调用场景
假设在 AppListFragment 中,需要异步统计当前模块已勾选的应用总数(耗时操作,避免阻塞 UI),然后在主线程显示结果。
第二步:完整调用代码(在 AppListFragment 中)
// AppListFragment 继承自 BaseFragment,可直接调用 runAsync(Callable<T>)
public class AppListFragment extends BaseFragment implements MenuProvider {
// ... 其他已有代码(如 scopeAdapter、binding 等)
// 假设在某个按钮点击事件中触发统计(也可在 onViewCreated 等生命周期中调用)
private void countCheckedApps() {
// 1. 调用 BaseFragment 的 runAsync(Callable<T>),传入带返回值的异步任务
// Callable<Long> 表示:异步任务返回 Long 类型结果(已勾选应用数)
Future<Long> countFuture = runAsync(() -> {
// -------------- 异步任务开始(运行在子线程,耗时操作放这)--------------
// 从 scopeAdapter 中获取已勾选的应用列表(可能是耗时操作,比如读取配置)
Set<ScopeAdapter.ApplicationWithEquals> checkedList = scopeAdapter.checkedList;
// 统计数量(这里可扩展更复杂的逻辑,比如过滤系统应用、黑名单应用)
long checkedCount = 0;
for (ScopeAdapter.ApplicationWithEquals app : checkedList) {
// 模拟耗时(比如额外查询应用信息,实际开发中可去掉)
Thread.sleep(10);
checkedCount++;
}
// 返回统计结果(Callable 必须实现 call() 方法,返回泛型 T 类型)
return checkedCount;
// -------------- 异步任务结束 --------------
});
// 2. 处理异步结果(两种方式:阻塞获取 / 非阻塞监听,推荐非阻塞)
// 方式1:非阻塞(通过主线程 Handler 延迟查询 Future,不卡UI)
App.getMainHandler().postDelayed(() -> {
if (countFuture.isDone() && !countFuture.isCancelled()) { // 任务已完成且未取消
try {
// 通过 Future.get() 获取异步任务的返回值(此时已无阻塞,因为 isDone 为 true)
long result = countFuture.get();
// 在UI线程显示结果(调用 BaseFragment 的 showHint 方法)
showHint("已勾选应用数:" + result, true);
} catch (Exception e) {
// 处理异常(如任务执行失败、被中断)
showHint("统计失败:" + e.getMessage(), false);
}
} else if (countFuture.isCancelled()) {
showHint("统计已取消", true);
}
}, 500); // 延迟 500ms 查询结果(可根据实际任务耗时调整)
// 方式2:阻塞获取(不推荐,会卡UI,仅作对比)
// 注意:绝对不能在主线程直接调用 countFuture.get()!会导致UI卡死
/*
new Thread(() -> {
try {
long result = countFuture.get(); // 阻塞直到任务完成
// 切换回主线程显示结果
runOnUiThread(() -> showHint("已勾选应用数:" + result, true));
} catch (Exception e) {
runOnUiThread(() -> showHint("统计失败:" + e.getMessage(), false));
}
}).start();
*/
}
// 假设在某个按钮点击时触发统计
private void initView() {
binding.someButton.setOnClickListener(v -> countCheckedApps());
}
}
第三步:关键细节拆解
- Callable<T> 的实现
这里用 Lambda 表达式简化 Callable<Long> 的实现(完整写法是 new Callable<Long>() { @Override public Long call() throws Exception { ... } });
call() 方法中是异步执行的逻辑(子线程),可以做耗时操作(如读取配置、统计数据、网络请求);
call() 可以抛出异常,后续通过 Future.get() 捕获。 - Future<T> 的核心用法
countFuture.isDone():判断任务是否已完成(返回 true 表示执行结束,不管成功失败);
countFuture.isCancelled():判断任务是否被取消;
countFuture.get():获取任务返回值(阻塞方法,必须在子线程调用,或确保 isDone() == true 时调用);
countFuture.cancel(true):取消任务(参数 true 表示中断正在执行的任务)。 - 为什么要这么调用?
异步统计:避免在主线程做循环统计(如果应用数多,会导致 UI 卡顿);
结果回调:通过 Future 拿到统计结果后,切换回主线程更新 UI(符合 Android 主线程更新 UI 的规范);
异常处理:捕获 call() 中抛出的异常,避免 App 崩溃。