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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章