Android扫描SD卡指定类型文件+通知系统更新数据库

1:扫描SD卡内容方式一(不推荐)

  • 扫描存储卡的第一种方式是:循环遍历所有SD卡的文件夹。这种方式好处是实时查询最新的内容,但是很慢,不推荐使用这种方式。由于比较耗时,普通用户大概扫描结束大概需要 30s 左右的时间,所以要开启子线程去查询。代码示例如下:

注意首先要申请动态存储权限

static class SearchTask extends AsyncTask<Void, Void, List<TemplateInfoBean>> {
        volatile List<TemplateInfoBean> fileList = new ArrayList<>();

        private WeakReference<SearchTemplateActivity> weakReference;

        SearchTask(WeakReference<SearchTemplateActivity> weakReference) {
            this.weakReference = weakReference;
        }

        @Override
        protected List<TemplateInfoBean> doInBackground(Void... voids) {
            long startTime = System.currentTimeMillis();
            try {
            	// 从根目录开始查询
                File file = Environment.getExternalStorageDirectory();
                File[] files = file.listFiles();
                if (files == null) {
                    return fileList;
                }
                List<File> list = Arrays.asList(files);
                if (list.size() > 100) {
                	// 每100个文件夹开启一个线程,此处线程不用太多
                    List<List<File>> lists = fixedGrouping(list, 100);
                    List<Runnable> runnableList = new ArrayList<>();
                    for (int i = 0; i < lists.size(); i++) {
                        int j = i;
                        Runnable runnable = new Runnable() {
                            @Override
                            public void run() {
                                List<File> list1 = lists.get(j);
                                for (File file1 : list1) {
                                    getAllFiles(file1, fileList);
                                }              
                            }
                        };
                        runnableList.add(runnable);
                    }
                    final ExecutorService executorService = Executors.newFixedThreadPool(lists.size());
                    for (Runnable runnable : runnableList) {
                        executorService.execute(runnable);
                    }
                    executorService.shutdown();
                    //等待线程池中的所有线程运行完成
                    while (true) {
                        if (executorService.isTerminated()) {
                            break;
                        }
                    }
                } else {
                    getAllFiles(file, fileList);
                }
            } catch (Exception e) {
                LogUtils.error(e.toString());
            }
            LogUtils.error("time:" + (System.currentTimeMillis() - startTime));
            return fileList;
        }


        @Override
        protected void onPostExecute(List<TemplateInfoBean> templateInfoBeans) {
        	// 返回数据刷新适配器列表
            weakReference.get().refreshList(templateInfoBeans);
        }

        @Override
        protected void onCancelled() {
           // 取消状态
        }
    }

	/**
     * 遍历扫描
     */
    public static void getAllFiles(File f, List<TemplateInfoBean> fileList) {
        if (!f.exists()) {//判断路径是否存在
            return;
        }
        File[] files = f.listFiles();
        if (files == null) {//判断权限
            return;
        }
        for (File file : files) {
            // xls  xlsx 如果是Excel就添加
            if (file.isFile() && (file.getName().endsWith(".xls") || file.getName().endsWith(".xlsx"))) {
                LogUtils.error(file.getAbsolutePath());
                // 如果是指定文件
                TemplateInfoBean bean = new TemplateInfoBean(file.getAbsolutePath(), file.getName(), showLongFileSize(file.length()), file.lastModified());
                fileList.add(bean);
            } else if (file.isDirectory()) {//查询子目录
                getAllFiles(new File(file.getAbsolutePath()), fileList);
            }
        }
    }

 /**
     * 每组 n 条,等分集合
     */
    public static <T> List<List<T>> fixedGrouping(List<T> source, int n) {

        if (null == source || source.size() == 0 || n <= 0)
            return null;
        List<List<T>> result = new ArrayList<List<T>>();

        int sourceSize = source.size();
        int size = (source.size() / n) + 1;
        for (int i = 0; i < size; i++) {
            List<T> subset = new ArrayList<T>();
            for (int j = i * n; j < (i + 1) * n; j++) {
                if (j < sourceSize) {
                    subset.add(source.get(j));
                }
            }
            result.add(subset);
        }
        return result;
    }

/**
     * 字节转换
     */
    public static String showLongFileSize(long length) {
        if (length >= 1048576) {
            return (length / 1048576) + "MB";
        } else if (length >= 1024) {
            return (length / 1024) + "KB";
        } else {
            return length + "B";
        }
    }

class TemplateInfoBean(val path: String, val name: String, val size: String, val time: Long) : Comparable<TemplateInfoBean> {
    var select = false
    override fun compareTo(other: TemplateInfoBean): Int {
        return (other.time - time).toInt()
    }
}
  • 此方式随着手机app增多会变慢,如果手机性能差会有一些卡顿。不推荐

2:扫描SD卡内容方式二(推荐)

  • 因为上面的方式扫描时间慢,效率低所有为了优化满足需求,进行了优化第二版如下所示:
    原理是:Android系统会在自己内部维护一个数据库,存储了手机内部的各种媒体文件的信息。我们使用ContentResolver去查询数据库的内容。
// 还是这个Task
static class SearchTask extends AsyncTask<Void, Void, List<TemplateInfoBean>> {
		// 要查询的表的列 这里有 地址 大小 时间等
        final String[] DOC_PROJECTION = {
                MediaStore.Files.FileColumns.DATE_MODIFIED,
                MediaStore.Files.FileColumns.DATA,
                MediaStore.Files.FileColumns.SIZE,
                MediaStore.Files.FileColumns.TITLE,
        };
        volatile List<TemplateInfoBean> fileList = new ArrayList<>();

        private WeakReference<SearchTemplateActivity> weakReference;

        SearchTask(WeakReference<SearchTemplateActivity> weakReference) {
            this.weakReference = weakReference;
        }

        @Override
        protected List<TemplateInfoBean> doInBackground(Void... voids) {
            try {
                // 第一步 扫描内部存储的 Excel  MediaStore.Files.getContentUri("external")就是获取外部存储的 Uri
                // query 方法第一个参数是要扫描的 Uri,第二个参数是要查询的列,第三个参数是查询的
                // projection是我们要查询的列;
                // selection使我们自己的查询条件;
                // 最后一个参数order是排序方式,默认可以传null
                String selection1 = "(" + MediaStore.Files.FileColumns.DATA + " LIKE '%.xls%'" +
                        " or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.xlsx%'" + ")";
                final Cursor cursor = weakReference.get().getContentResolver().query(
                        MediaStore.Files.getContentUri("external"),
                        DOC_PROJECTION,
                        selection1,
                        null,
                        null
                );
                if (cursor != null) {
                    fileList.addAll(getDocumentFromCursor(cursor));
                    cursor.close();
                }
            } catch (Exception e) {
                LogUtils.error(e.toString());
            }
            return fileList;
        }

        private ArrayList<TemplateInfoBean> getDocumentFromCursor(Cursor cursor) {
            cursor.moveToFirst();
            ArrayList<TemplateInfoBean> documents = new ArrayList<>();
            while (cursor.moveToNext()) {
                String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA));
                long size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE));
                String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.TITLE));
                // 数据库查询的时间是秒为单位的
                long lastModifiedTime = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_MODIFIED));
                if (path != null) {
                    TemplateInfoBean document = new TemplateInfoBean(path, title, showLongFileSize(size), lastModifiedTime * 1000);
                    if (!documents.contains(document)) {
                        documents.add(document);
                    }
                }
            }
            return documents;
        }

        @Override
        protected void onPostExecute(List<TemplateInfoBean> templateInfoBeans) {
            weakReference.get().refreshList(templateInfoBeans);
        }

        // 作用:将异步任务设置为:取消状态
        @Override
        protected void onCancelled() {
           
        }
    }
  • 此种方式存在的问题

系统只会在开机的时候更新一次数据库,开机以后添加进来的文件是不会出现在数据库记录中,所有有了下面两个点,如何通知系统去更新数据库

3:通知系统更新媒体数据库文件方式一

  • 直接上代码发送广播 (我的手机版本是 8.0的,据说低版本的 uri获取方式需要变一下)
// 我这里默认是扫描所有文件夹,可以具体根据自己的需求更新自己的文件夹  
Uri uri = Uri.parse("file://" + Environment.getExternalStorageDirectory());
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,uri);
sendBroadcast(intent);

这样系统就会去指定地址去查询更新数据库了

4:通知系统更新媒体数据库文件方式二(带回调)

  • 当想监听系统扫描是否结束的时候需要监听怎么办呢?用下面方法:
    此方法需要注意的是:onScanCompleted 是不是在主线程所以更新UI要注意会有如下提示
    Only the original thread that created a view hierarchy can touch its views.
    所以我使用了Handler
  String[] path = new String[]{Environment.getExternalStorageDirectory().getAbsolutePath()};
        String[] mimeType = new String[]{"application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/x-excel", "application/x-msexcel"};
        // 此方法第一个参数最好用 ApplicationContext避免内存泄漏,因为系统是开启了一个服务去查询,如果查询未结束前退出当前页面会发生内存泄漏
        // 第二个参数是需要扫描的路径
        // 第三个参数是 mime_type 根据自己的去求去更改,我的是指定 Excel 文件的 mime_type,如果全部可以 */*。具体可以自行百度
        MediaScannerConnection.scanFile(this.getApplicationContext(), path, mimeType, new MediaScannerConnection.MediaScannerConnectionClient() {
            @Override
            public void onMediaScannerConnected() {
            	// 连接成功回调
            }

            @Override
            public void onScanCompleted(String path, Uri uri) {
            	// 扫描完成回调 此时是在 Binder一个线程中
                handler.sendEmptyMessage(0);
            }
        });
   Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            // todo
        }
    };

------------------------------到此结束 -----------------------------------

发布了120 篇原创文章 · 获赞 28 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章