You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android App的RecyclerView中无卡顿显示PDF缩略图?

解决RecyclerView加载PDF缩略图卡顿问题

现有代码的核心问题

你当前的实现直接在主线程(onBindViewHolder)里执行PDF渲染,这是UI卡顿的根本原因——PDF解析、渲染属于耗时的IO+计算操作,会阻塞主线程导致滑动不流畅。另外代码还存在两个关键错误:

  1. 渲染顺序颠倒:先把空Bitmap压缩保存,再执行page.render,导致生成的缩略图是空的
  2. 存储路径无效:直接在PDF文件路径后拼接时间戳,会生成类似xxx/test.pdf01012024.jpg的无效路径(test.pdf是文件,不能作为目录)
  3. 生成原尺寸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);
                }
            }
        }
    }
}

额外优化建议

  1. 给Glide开启内存缓存和磁盘缓存,减少重复加载
  2. 对于大量PDF文件,可使用WorkManager在后台预生成所有缩略图
  3. 监听PDF文件的修改事件,当文件更新时删除旧缓存,重新生成缩略图

内容的提问来源于stack exchange,提问作者Shivanshu Raj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 16:05:00