問題代碼分析
ByteArrayOutputStream f = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) > 0) {
f.write(buffer, 0, len); // PDF 較大時 f 無限增長,佔用巨大堆內存
}
洩漏根因
- 全量讀入內存:
ByteArrayOutputStream 將整個 PDF 字節流緩存在堆中,大文件直接 OOM。
- Activity 未正確銷毀:若打開 PDF 的 Activity 在返回時未
finish(),或被單例/靜態變量持有,導致其持有的 f 無法回收。
- 後台線程持有引用:解析 PDF 的異步線程運行中,Activity 已銷毀但線程未結束,繼續持有
f、buffer 等對象。
解決方案
方案一:流式處理,不全量緩存
// 使用 PdfRenderer (API 21+) 頁面級渲染
PdfRenderer renderer = new PdfRenderer(ParcelFileDescriptor.open(file, MODE_READ_ONLY));
for (int i = 0; i < renderer.getPageCount(); i++) {
PdfRenderer.Page page = renderer.openPage(i);
Bitmap bitmap = Bitmap.createBitmap(page.getWidth(), page.getHeight(), Bitmap.Config.ARGB_8888);
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
// 顯示 bitmap 到 ImageView
page.close();
}
renderer.close();
方案三:生命周期綁定取消
// 在 onDestroy 中取消異步任務
@Override
protected void onDestroy() {
super.onDestroy();
if (pdfTask != null) pdfTask.cancel(true);
// 釋放大對象引用
pdfStream = null; buffer = null;
}
檢查清單
- 使用
LeakCanary 或 Android Studio Profiler 定位洩漏對象。
- 確認 Activity
onDestroy 執行,無靜態/單例持有 Context。
- 大文件處理採用流式/分頁,避免一次性載入堆內存。