Android中使用PdfRenderer渲染Downloads目录本地PDF的方法
看起来你遇到的核心问题是文件路径错误加上文件不存在时的逻辑错误,导致pdfRenderer实例化失败,最终在closeRenderer()里调用pdfRenderer.close()时触发空指针异常。咱们一步步来修复:
问题分析
硬编码路径不可靠且拼接错误
直接写"storage/emulated/0/Download/"是很不规范的,不同设备的外部存储路径可能有差异;而且你原来的FILENAME定义是Environment.DIRECTORY_DOWNLOADS+"/a.pdf"(也就是"Download/a.pdf"),拼接后路径变成了"storage/emulated/0/Download/Download/a.pdf",这显然不是你要的文件位置。文件不存在时的逻辑完全错误
当检测到文件不存在时,你居然尝试创建FileInputStream去读取这个不存在的文件,这会直接抛出FileNotFoundException,导致后续parcelFileDescriptor和pdfRenderer都没被初始化,自然在关闭时触发空指针。
修正后的实现
1. 正确定义文件路径
首先修改FILENAME的定义,只保留文件名:
private static final String FILENAME = "a.pdf";
然后用系统API获取Downloads目录的正确路径,避免硬编码:
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File file = new File(downloadsDir, FILENAME);
2. 修复openRenderer方法逻辑
去掉文件不存在时的错误复制逻辑(因为你的文件是下载到Downloads的,不存在的话应该提示用户或者重新下载,而不是尝试从不存在的文件复制),同时确保路径正确:
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void openRenderer(Context context) throws IOException { // 获取Downloads目录的正确路径 File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File file = new File(downloadsDir, FILENAME); if (!file.exists()) { Log.e("testit", "PDF文件不存在,请检查下载是否完成"); // 这里可以添加提示用户的逻辑,比如Toast return; // 直接返回,避免后续初始化失败 } // 打开文件描述符 parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); if (parcelFileDescriptor != null) { pdfRenderer = new PdfRenderer(parcelFileDescriptor); } else { Log.e("testit", "无法打开文件描述符"); } }
3. 权限处理(关键!)
Android 6.0(API 23)及以上版本,读取公共Downloads目录需要动态申请READ_EXTERNAL_STORAGE权限。你需要在代码中添加权限申请逻辑:
在onCreate方法中先检查权限:
private static final int REQUEST_READ_STORAGE = 1001; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pdf_render); ButterKnife.bind(this); pageIndex = 0; // 检查读取存储权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_READ_STORAGE); } }
然后处理权限申请结果:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_READ_STORAGE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限通过,初始化渲染器 try { openRenderer(getApplicationContext()); showPage(pageIndex); } catch (IOException e) { e.printStackTrace(); } } else { // 权限拒绝,提示用户 Toast.makeText(this, "需要读取存储权限才能显示PDF", Toast.LENGTH_SHORT).show(); } } }
4. 修复closeRenderer的空指针问题
为了避免即使pdfRenderer为null时触发异常,修改closeRenderer方法:
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void closeRenderer() throws IOException { if (null != currentPage) { currentPage.close(); } if (null != pdfRenderer) { // 添加空判断 pdfRenderer.close(); } if (null != parcelFileDescriptor) { // 添加空判断 parcelFileDescriptor.close(); } }
额外注意事项
- 确保下载完成后再调用渲染逻辑,比如可以在下载任务的回调中启动
PdfRenderActivity,或者传递文件路径过去,避免文件还没下载完就尝试打开。 - Android 10(API 29)及以上,使用
Environment.getExternalStoragePublicDirectory会被标记为deprecated,你可以改用MediaStore来访问公共Downloads目录,或者使用Context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)(但这个目录是应用私有,卸载后会被删除)。如果要兼容Android 10+,推荐用MediaStore读取文件。
内容的提问来源于stack exchange,提问作者hiteshchopra11

