如何在Android App的RecyclerView中无卡顿显示PDF缩略图?
解决RecyclerView加载PDF缩略图卡顿问题
现有代码的核心问题
你当前的实现直接在主线程(onBindViewHolder)里执行PDF渲染,这是UI卡顿的根本原因——PDF解析、渲染属于耗时的IO+计算操作,会阻塞主线程导致滑动不流畅。另外代码还存在两个关键错误:
- 渲染顺序颠倒:先把空Bitmap压缩保存,再执行
page.render,导致生成的缩略图是空的 - 存储路径无效:直接在PDF文件路径后拼接时间戳,会生成类似
xxx/test.pdf01012024.jpg的无效路径(test.pdf是文件,不能作为目录) - 生成原尺寸Bitmap:PDF原尺寸通常很大,内存占用过高会加剧卡顿甚至引发OOM
正确生成PDF第一页缩略图
核心原则
- 必须在子线程执行渲染:避免阻塞主线程
- 按UI需求缩放Bitmap:不要用PDF原尺寸,根据RecyclerView Item的ImageView大小生成缩略图,减少内存占用
- 先渲染再保存:确保生成的图片是有效内容
示例代码(子线程渲染)
// 生成PDF缩略图(仅在子线程调用) private boolean generatePdfThumbnail(File pdfFile, File saveFile, int targetWidth) { try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY); PdfRenderer renderer = new PdfRenderer(fd)) { if (renderer.getPageCount() == 0) return false; PdfRenderer.Page page = renderer.openPage(0); // 根据目标宽度计算等比例高度 float scaleRatio = (float) targetWidth / page.getWidth(); int targetHeight = (int) (page.getHeight() * scaleRatio); // 创建缩放后的Bitmap Bitmap bitmap = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888); Matrix matrix = new Matrix(); matrix.setScale(scaleRatio, scaleRatio); // 渲染PDF页面到Bitmap page.render(bitmap, null, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); // 保存到目标文件 try (FileOutputStream fos = new FileOutputStream(saveFile)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos); // 80%压缩率平衡画质和体积 } // 释放资源 bitmap.recycle(); page.close(); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
缩略图存储方案
存储位置选择
优先用App私有目录,避免污染用户图库,且无需额外权限(Android 10+):
- 内部缓存目录:
context.getCacheDir()(系统清理缓存时会自动删除) - 外部私有目录:
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)(空间更大,不会被系统轻易清理)
命名规则
用PDF文件的唯一标识命名,避免重复生成:
- 可选方案1:文件绝对路径的MD5值(保证唯一)
- 可选方案2:文件名+文件最后修改时间(简单易实现)
示例代码(获取缓存文件)
// 获取PDF对应的缓存缩略图文件 private File getThumbCacheFile(Context context, File pdfFile) { // 生成唯一文件名 String fileName = getUniqueFileName(pdfFile); // 创建缓存目录 File cacheDir = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), "pdf_thumbs"); if (!cacheDir.exists()) cacheDir.mkdirs(); return new File(cacheDir, fileName); } // 生成唯一文件名(用文件路径MD5) private String getUniqueFileName(File pdfFile) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); byte[] hash = digest.digest(pdfFile.getAbsolutePath().getBytes(StandardCharsets.UTF_8)); StringBuilder hexStr = new StringBuilder(); for (byte b : hash) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) hexStr.append('0'); hexStr.append(hex); } return hexStr.toString() + ".jpg"; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); // 降级方案:文件名+修改时间 return pdfFile.getName().replace(".pdf", "") + "_" + pdfFile.lastModified() + ".jpg"; } }
结合Glide实现流畅加载
在onBindViewHolder中,优先加载缓存的缩略图,没有缓存则异步生成后再加载:
@Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { PidifyFile currentFile = files.get(position); holder.pdfName.setText(currentFile.getName()); Date lModified = currentFile.getDateCreated(); SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy hh:mm aa"); holder.pdfDate.setText(sdf.format(lModified)); File pdfFile = new File(currentFile.getFilePath()); File thumbFile = getThumbCacheFile(holder.itemView.getContext(), pdfFile); int targetWidth = holder.pdfImg.getWidth() == 0 ? 200 : holder.pdfImg.getWidth(); // 默认200px if (thumbFile.exists()) { // 直接加载缓存图 Glide.with(holder.itemView.getContext()) .load(thumbFile) .placeholder(R.drawable.default_pdf_thumb) // 设置占位图 .error(R.drawable.error_pdf_thumb) // 设置错误图 .into(holder.pdfImg); } else { // 显示占位图,异步生成缩略图 holder.pdfImg.setImageResource(R.drawable.default_pdf_thumb); // 用协程在子线程执行(也可用AsyncTask、Thread等) CoroutineScope(Dispatchers.IO).launch { boolean success = generatePdfThumbnail(pdfFile, thumbFile, targetWidth); if (success) { withContext(Dispatchers.Main) { Glide.with(holder.itemView.getContext()) .load(thumbFile) .into(holder.pdfImg); } } } } }
额外优化建议
- 给Glide开启内存缓存和磁盘缓存,减少重复加载
- 对于大量PDF文件,可使用
WorkManager在后台预生成所有缩略图 - 监听PDF文件的修改事件,当文件更新时删除旧缓存,重新生成缩略图
内容的提问来源于stack exchange,提问作者Shivanshu Raj
相关产品推荐
相关产品推荐

