Flutter实现从根存储扫描并获取所有PDF文件列表的方法
实现扫描设备PDF文件并在ListView展示的方案
一、权限准备
首先要获取存储访问权限,不同Android版本权限要求不同:
- Android 10及以下:需
READ_EXTERNAL_STORAGE权限 - Android 11及以上:推荐用
MANAGE_EXTERNAL_STORAGE权限(需引导用户到系统设置开启),或通过分区存储的MediaStore接口访问文件
在AndroidManifest.xml中声明权限:
<!-- Android 10及以下 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 11+ --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
动态申请权限(以READ_EXTERNAL_STORAGE为例):
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 100); }
二、扫描PDF文件
推荐用MediaStore查询,比直接遍历文件系统更高效且符合系统规范:
private List<PDFItem> scanPDFs() { List<PDFItem> pdfList = new ArrayList<>(); ContentResolver resolver = getContentResolver(); Uri uri = MediaStore.Files.getContentUri("external"); // 要查询的字段:文件名、大小、路径 String[] projection = { MediaStore.Files.FileColumns.DISPLAY_NAME, MediaStore.Files.FileColumns.SIZE, MediaStore.Files.FileColumns.DATA }; // 筛选PDF文件:匹配mime类型或后缀 String selection = MediaStore.Files.FileColumns.MIME_TYPE + "=? OR " + MediaStore.Files.FileColumns.DATA + " LIKE ?"; String[] selectionArgs = {"application/pdf", "%.pdf"}; Cursor cursor = resolver.query(uri, projection, selection, selectionArgs, null); if (cursor != null) { while (cursor.moveToNext()) { String fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)); long fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE)); String filePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)); // 二次过滤确保是PDF文件 if (fileName.endsWith(".pdf")) { pdfList.add(new PDFItem(fileName, formatFileSize(fileSize), filePath)); } } cursor.close(); } return pdfList; } // 格式化文件大小为易读的KB/MB格式 private String formatFileSize(long size) { if (size < 1024) { return size + " B"; } else if (size < 1024 * 1024) { return String.format("%.2f KB", (float) size / 1024); } else { return String.format("%.2f MB", (float) size / (1024 * 1024)); } }
三、创建PDF数据模型
public class PDFItem { private String fileName; private String fileSize; private String filePath; public PDFItem(String fileName, String fileSize, String filePath) { this.fileName = fileName; this.fileSize = fileSize; this.filePath = filePath; } // getter方法 public String getFileName() { return fileName; } public String getFileSize() { return fileSize; } public String getFilePath() { return filePath; } }
四、ListView自定义适配器
先创建列表项布局item_pdf.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:orientation="vertical"> <TextView android:id="@+id/tv_file_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp" android:textStyle="bold"/> <TextView android:id="@+id/tv_file_size" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="#666666" android:layout_marginTop="4dp"/> </LinearLayout>
编写适配器代码:
public class PDFAdapter extends BaseAdapter { private Context context; private List<PDFItem> pdfList; public PDFAdapter(Context context, List<PDFItem> pdfList) { this.context = context; this.pdfList = pdfList; } @Override public int getCount() { return pdfList.size(); } @Override public Object getItem(int position) { return pdfList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.item_pdf, parent, false); holder = new ViewHolder(); holder.tvFileName = convertView.findViewById(R.id.tv_file_name); holder.tvFileSize = convertView.findViewById(R.id.tv_file_size); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } PDFItem item = pdfList.get(position); holder.tvFileName.setText(item.getFileName()); holder.tvFileSize.setText(item.getFileSize()); return convertView; } static class ViewHolder { TextView tvFileName; TextView tvFileSize; } }
五、绑定数据到ListView
扫描操作耗时,必须放在子线程执行,这里用AsyncTask示例:
private ListView listView; private PDFAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = findViewById(R.id.list_view); // 权限通过后启动扫描 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { new ScanPDFTask().execute(); } } private class ScanPDFTask extends AsyncTask<Void, Void, List<PDFItem>> { @Override protected List<PDFItem> doInBackground(Void... voids) { return scanPDFs(); } @Override protected void onPostExecute(List<PDFItem> pdfItems) { adapter = new PDFAdapter(MainActivity.this, pdfItems); listView.setAdapter(adapter); // 添加列表项点击事件,可在此处实现打开PDF的逻辑 listView.setOnItemClickListener((parent, view, position, id) -> { PDFItem item = (PDFItem) parent.getItemAtPosition(position); // 这里写打开PDF的代码,比如调用系统阅读器或自定义阅读组件 }); } }
注意事项
- Android 11+使用
MANAGE_EXTERNAL_STORAGE时,需通过Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)引导用户开启权限 - 也可以用Coroutine、RxJava替代AsyncTask实现异步扫描
- 若需实时监听PDF文件变化,可注册
ContentObserver监听MediaStore的更新
内容的提问来源于stack exchange,提问作者Jackson
相关产品推荐
相关产品推荐

