You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android开发中如何非阻塞获取子线程返回值供主线程使用

问题分析

你当前的写法存在严重的性能问题和崩溃风险:

  • 主线程中执行while(opt == 0) {}是空循环死等,会完全占用主线程资源,轻则导致界面卡顿无响应,重则直接触发系统ANR强制退出应用
  • 跨线程共享的opt和phone变量没有加volatile修饰,还存在内存可见性问题,极端情况下即使子线程修改了值,主线程也感知不到,会一直死循环

实现方案(Java版 无第三方依赖)

核心思路是异步回调+主线程切换,不需要主动等待子线程执行,把爬取完成后的逻辑放到回调里,子线程爬完后主动触发回调,切回主线程执行跳转逻辑。
首先在你当前的类里创建一个绑定主线程的Handler,用于切换到主线程执行UI相关操作(startActivity必须在主线程执行):

// 类成员变量
private final Handler mainHandler = new Handler(Looper.getMainLooper());

然后修改AdAdapter的点击逻辑,去掉死循环等待,把跳转逻辑放到子线程执行完成后的回调里:

adAdapter = new AdAdapter(new AdDiffer(), ad -> {
    // 启动子线程执行爬取任务
    new Thread(() -> {
        int localOpt = 1;
        String localPhone = "";
        try {
            Document doc = Jsoup.connect(ad.getUrl()).get();
            Element elem = doc.getElementsByClass("hGYViW").get(0);
            Elements e = null;
            try {
                e = elem.getElementsByTag("div").get(0).getElementsByClass("hMvGEj");
            } catch (Exception ex) {
                // 建议补充异常日志打印,不要空吞异常
            }
            if (e != null && e.size() != 0) {
                localPhone = elem.getElementsByTag("div").get(0).getElementsByClass("hMvGEj").get(0).getElementsByTag("a").get(0).html();
                localOpt = 2;
            } else {
                localOpt = 1;
            }
        } catch (IOException e) {
            e.printStackTrace();
            localOpt = 1;
        }
        // 爬取完成,切回主线程执行后续跳转逻辑
        int finalLocalOpt = localOpt;
        String finalLocalPhone = localPhone;
        mainHandler.post(() -> {
            if (finalLocalOpt == 2) {
                Intent intent = new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:" + finalLocalPhone));
                startActivity(intent);
            } else {
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(ad.getUrl())));
            }
        });
    }).start();
    return null;
});

该方案优势:

  • 完全不阻塞主线程,主线程启动子线程后就回归正常处理UI事件,不会出现卡顿
  • 用局部变量存储爬取结果,不需要跨线程共享全局变量,避免了内存可见性问题
  • 所有UI操作都切回主线程执行,符合Android线程规范

优化可选方案

如果你的项目已经支持Kotlin,推荐用协程实现,代码更简洁,还能自动绑定页面生命周期避免内存泄漏:

// 点击事件内启动协程
lifecycleScope.launch(Dispatchers.IO) {
    // IO线程执行爬取逻辑
    var opt = 1
    var phone = ""
    try {
        val doc = Jsoup.connect(ad.url).get()
        val elem = doc.getElementsByClass("hGYViW")[0]
        val e = try {
            elem.getElementsByTag("div")[0].getElementsByClass("hMvGEj")
        } catch (ex: Exception) {
            null
        }
        if (e?.isNotEmpty() == true) {
            phone = elem.getElementsByTag("div")[0].getElementsByClass("hMvGEj")[0].getElementsByTag("a")[0].html()
            opt = 2
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
    // 自动切回主线程执行跳转
    withContext(Dispatchers.Main) {
        if (opt == 2) {
            val intent = Intent(Intent.ACTION_CALL)
            intent.data = Uri.parse("tel:$phone")
            startActivity(intent)
        } else {
            startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(ad.url)))
        }
    }
}

内容的提问来源于stack exchange,提问作者snazzy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 12:15:05