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
}
};
------------------------------到此結束 -----------------------------------