百万日活游戏InApp Billing V3调用getSkuDetails触发ANR求助
getSkuDetails导致的ANR问题 我之前在维护一款千万级DAU的游戏时,刚好遇到过和你几乎一模一样的ANR问题!先帮你拆解下问题根源,再给你可行的解决办法。
问题根源:你以为在后台线程,实际还是跑在了主线程
从你贴的ANR堆栈能明显看出来——getSkuDetails的Binder跨进程调用最终是在主线程的Looper循环里执行的!你看堆栈里的Handler.dispatchMessage和ActivityThread.main,说明你所谓的“独立线程”其实没真正把billing调用放到后台,大概率是你的线程逻辑里误用了主线程的Handler,把getSkuDetails的执行任务又post回了主线程(比如那个GameActivity$$Lambda$2.run是通过主线程Handler分发的)。
跨进程调用Google Play服务本身就可能有延迟,一旦在主线程执行,很容易因为超过系统的ANR阈值(一般是5秒)触发无响应。
具体解决办法
1. 把getSkuDetails彻底放在后台线程执行
别用主线程Handler来跑billing接口调用,直接用后台线程池或者异步任务执行,只有更新UI的逻辑才回到主线程。给你个Java的实现示例:
// 初始化一个后台线程池,全局复用就行 private ExecutorService billingExecutor = Executors.newSingleThreadExecutor(); // 调用getSkuDetails的方法 private void loadSkuDetails() { billingExecutor.execute(() -> { if (mService == null) { // 先确保IInAppBillingService绑定成功 return; } try { Bundle params = new Bundle(); params.putStringArrayList("ITEM_ID_LIST", Arrays.asList("your_sku_id_here")); // 这里才是真正在后台执行getSkuDetails Bundle skuDetailsBundle = mService.getSkuDetails(3, getPackageName(), "inapp", params); // 拿到结果后,只把UI更新逻辑切回主线程 runOnUiThread(() -> { ArrayList<String> responseList = skuDetailsBundle.getStringArrayList("DETAILS_LIST"); // 解析价格、更新UI等操作 }); } catch (RemoteException e) { e.printStackTrace(); // 异常处理也切回主线程提示用户 runOnUiThread(() -> { Toast.makeText(this, "加载商品失败,请稍后重试", Toast.LENGTH_SHORT).show(); }); } }); }
2. 给后台调用加超时保护
跨进程调用可能因为网络差、Google Play服务异常卡住,给后台任务加个超时,避免线程一直挂着:
Future<?> task = billingExecutor.submit(() -> { // 执行getSkuDetails调用逻辑 }); try { // 设置5秒超时,根据你的业务调整 task.get(5, TimeUnit.SECONDS); } catch (TimeoutException e) { task.cancel(true); runOnUiThread(() -> { Toast.makeText(this, "加载超时,请检查网络", Toast.LENGTH_SHORT).show(); }); } catch (Exception e) { e.printStackTrace(); }
3. 检查Service绑定逻辑
确保你绑定IInAppBillingService是异步的,不要在主线程同步等待绑定结果。正确的绑定方式是用bindService,在ServiceConnection的onServiceConnected回调里拿到服务实例,再执行后续的billing操作。
4. 兼容旧版Google Play服务
部分老版本的Google Play服务可能存在getSkuDetails的性能问题,建议在启动时检查服务版本:
int playServiceStatus = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this); if (playServiceStatus != ConnectionResult.SUCCESS) { // 提示用户更新Google Play服务 GoogleApiAvailability.getInstance().getErrorDialog(this, playServiceStatus, 1001).show(); }
最后总结
你现在的核心问题就是getSkuDetails没真正在后台跑,只要把这个跨进程调用彻底放到后台线程,再加上超时和异常处理,ANR问题就能解决。我当初就是因为图省事,用了主线程Handler来执行billing调用,结果导致大量ANR,调整线程逻辑后就再也没出现过了。
内容的提问来源于stack exchange,提问作者DevSherif

