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
相关产品推荐
相关产品推荐

