Android全版本自定义路径PDF获取及RecyclerView展示问题求助
解决Android自定义路径PDF读取与RecyclerView展示问题
原代码核心问题分析
- 目录路径构造错误:
Environment.DIRECTORY_DOCUMENTS是公共目录的相对标识,直接拼接File对象在Android Q+会导致路径无效,无法正确访问目标目录。 - 作用域存储限制未适配:Android 10(API 29)及以上禁用了直接通过
FileAPI访问外部公共目录的自定义子目录,必须使用MediaStore或存储访问框架(SAF)。 - MediaStore查询逻辑缺陷:
getStatusScope方法未过滤指定的AppName目录,且仅获取了文件ID而非可访问的路径/Uri,导致无法正确加载目标PDF。
解决方案与示例代码
一、权限配置
根据目标Android版本添加对应权限到AndroidManifest.xml:
<!-- Android 10及以下需要 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 11及以上访问公共目录需要(可选,若用SAF则无需) --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
注意:MANAGE_EXTERNAL_STORAGE权限需要引导用户手动开启,建议优先使用应用私有目录。
二、推荐方案:使用应用私有目录(无需额外权限)
将PDF存储在应用私有外部目录,所有Android版本都可直接用File API访问:
1. 创建私有目录
// 获取应用私有Documents子目录 File privatePdfDir = new File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "AppName"); if (!privatePdfDir.exists()) { privatePdfDir.mkdirs(); }
2. 读取私有目录下的PDF并更新RecyclerView
private void loadPrivatePdfs() { new Thread(() -> { List<PDFModel> pdfList = new ArrayList<>(); File privatePdfDir = new File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "AppName"); File[] files = privatePdfDir.listFiles((dir, name) -> name.endsWith(".pdf")); if (files != null) { // 按修改时间倒序排序 Arrays.sort(files, (o1, o2) -> Long.compare(o2.lastModified(), o1.lastModified())); for (File file : files) { pdfList.add(new PDFModel(file.getAbsolutePath(), file.getName())); } } // 主线程更新UI runOnUiThread(() -> { PdfAdapter adapter = new PdfAdapter(pdfList, this); recyclerView.setAdapter(adapter); recyclerView.setHasFixedSize(true); // 无数据时的处理 if (pdfList.isEmpty()) { // noResultSwipeImage.setVisibility(View.VISIBLE); } else { // noResultSwipeImage.setVisibility(View.GONE); } }); }).start(); }
三、适配公共Documents/AppName目录(Android Q+)
若必须访问公共目录下的自定义子目录,需使用MediaStore查询指定路径的PDF:
1. 查询指定目录的PDF
private void loadPublicPdfs() { new Thread(() -> { List<PDFModel> pdfList = new ArrayList<>(); ContentResolver resolver = getContentResolver(); Uri uri = MediaStore.Files.getContentUri("external"); // 构造查询条件:筛选PDF且路径包含Documents/AppName String[] projection = { MediaStore.Files.FileColumns._ID, MediaStore.Files.FileColumns.DISPLAY_NAME, MediaStore.Files.FileColumns.DATA // Android Q以下可用,Q+需用Uri }; String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("pdf"); String selection = MediaStore.Files.FileColumns.MIME_TYPE + "=? AND " + MediaStore.Files.FileColumns.RELATIVE_PATH + " LIKE ?"; String[] selectionArgs = { mimeType, "%Documents/AppName/%" // 匹配相对路径 }; Cursor cursor = resolver.query(uri, projection, selection, selectionArgs, null); if (cursor != null) { int nameIndex = cursor.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME); int idIndex = cursor.getColumnIndex(MediaStore.Files.FileColumns._ID); while (cursor.moveToNext()) { String fileName = cursor.getString(nameIndex); // Android Q+通过ID构造Uri访问文件 Uri fileUri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), cursor.getLong(idIndex)); pdfList.add(new PDFModel(fileUri.toString(), fileName)); } cursor.close(); } runOnUiThread(() -> { PdfAdapter adapter = new PdfAdapter(pdfList, this); recyclerView.setAdapter(adapter); recyclerView.setHasFixedSize(true); if (pdfList.isEmpty()) { // 处理无数据情况 } }); }).start(); }
2. PDFModel适配Uri或路径
确保PDFModel能存储Uri或路径,方便Adapter加载:
public class PDFModel { private String fileUri; // 或private String filePath; private String fileName; public PDFModel(String fileUri, String fileName) { this.fileUri = fileUri; this.fileName = fileName; } // getter方法 public String getFileUri() { return fileUri; } public String getFileName() { return fileName; } }
3. Adapter中加载PDF(示例)
若需预览PDF,可使用第三方库(如AndroidPdfViewer),通过Uri加载:
public class PdfAdapter extends RecyclerView.Adapter<PdfAdapter.PdfViewHolder> { private List<PDFModel> pdfList; private Context context; public PdfAdapter(List<PDFModel> pdfList, Context context) { this.pdfList = pdfList; this.context = context; } @NonNull @Override public PdfViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.item_pdf, parent, false); return new PdfViewHolder(view); } @Override public void onBindViewHolder(@NonNull PdfViewHolder holder, int position) { PDFModel model = pdfList.get(position); holder.tvFileName.setText(model.getFileName()); // 点击跳转PDF预览页面 holder.itemView.setOnClickListener(v -> { Intent intent = new Intent(context, PdfPreviewActivity.class); intent.putExtra("PDF_URI", model.getFileUri()); context.startActivity(intent); }); } @Override public int getItemCount() { return pdfList.size(); } static class PdfViewHolder extends RecyclerView.ViewHolder { TextView tvFileName; public PdfViewHolder(@NonNull View itemView) { super(itemView); tvFileName = itemView.findViewById(R.id.tv_file_name); } } }
内容的提问来源于stack exchange,提问作者Sk Rijaul
相关产品推荐
相关产品推荐

