Android屏幕关闭广播触发ANR:Bitmap压缩方法优化求助
Bitmap压缩在SCREEN_OFF广播时引发ANR的解决方案
问题背景
收到Intent { act=android.intent.action.SCREEN_OFF }系统广播时,getByteArrayFromBitmap方法触发ANR。无法复现该问题,仅能从Google Play Console获取堆栈跟踪信息。
原方法代码:
private byte[] getByteArrayFromBitmap(Bitmap bitmap) { if (bitmap == null) { return null; } ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); return byteArrayOutputStream.toByteArray(); }
堆栈分析
从堆栈跟踪可以看到,ANR卡在Bitmap.compress的native层PNG压缩流程中,具体是JNI的SetByteArrayRegion方法等待锁。核心原因是:
- SCREEN_OFF广播的处理逻辑运行在主线程;
- PNG无损压缩(尤其是100%质量、大尺寸Bitmap)属于CPU密集型操作,会占用主线程大量时间,当系统在锁屏时本身负载较高时,极易触发ANR(主线程阻塞超过5秒)。
优化方案
1. 将压缩操作移至后台线程
广播接收器的onReceive方法默认在主线程执行,必须把所有耗时操作转移到后台线程。可以使用线程池实现:
// 初始化单线程池处理Bitmap压缩 private static final ExecutorService BITMAP_COMPRESS_POOL = Executors.newSingleThreadExecutor(); // 异步压缩Bitmap的方法,通过回调返回结果 private void compressBitmapAsync(Bitmap bitmap, CompressCallback callback) { if (bitmap == null) { callback.onCompressed(null); return; } BITMAP_COMPRESS_POOL.execute(() -> { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); byte[] result = baos.toByteArray(); // 回到主线程触发回调 new Handler(Looper.getMainLooper()).post(() -> callback.onCompressed(result)); }); } // 回调接口定义 public interface CompressCallback { void onCompressed(byte[] bitmapBytes); }
2. 优化压缩参数与Bitmap尺寸
- 替换压缩格式:如果业务允许,将PNG改为JPEG(质量设为80左右),压缩速度会大幅提升;
- 缩小Bitmap尺寸:若最终使用的图片不需要原分辨率,先通过
Bitmap.createScaledBitmap缩放Bitmap,减少压缩的数据量; - 缓存压缩结果:对同一Bitmap的压缩结果进行缓存,避免重复执行压缩操作。
3. 简化广播处理逻辑
SCREEN_OFF是系统高优先级广播,处理逻辑要尽可能轻量化:
- 广播接收器内仅触发后台任务,不执行任何耗时操作;
- 使用
JobIntentService或WorkManager处理后续的通知发送逻辑,确保操作在后台安全执行,且不占用主线程资源。
总结
核心问题是主线程执行耗时的Bitmap压缩操作,结合锁屏时系统的高负载场景,最终引发ANR。通过将耗时操作移至后台、优化压缩参数、简化广播逻辑,即可彻底解决该问题。
内容的提问来源于stack exchange,提问作者faisal ur rehman
相关产品推荐
相关产品推荐

