我在使用我们的App的时候发现发送文件的时候,从进入文件选择界面到文件完全加载出来,时间过长,大概10s左右,然后我想这个是否可以优化一下,然后进过自己查资料,瞎搞,弄出啦了。下面不多说,先上效果图(实现的UI可以各位自行实现,我这里只是提供文件扫描功能的实现)。
- 说明:
我们这个文件扫描是基于 https://github.com/DroidNinja/Android-FilePicker 这个开源框架的(这个框架还不错,可以对文件进行归类),这里只是进行了优化。
2.1 剔除原有的扫描文件方案,找一个更优的框架进行替换
该方案被第一个排除:
i、因为之前框架涉及的代码比较多,改动可能比较大,担心影响功能
ii、看了一些框架,对比于原有的,不符合原有设计需求
2.2 在不改变原有框架的前提下,寻找替代原有扫描文件方法的方案
我在网上看了一个大牛的扫描方案 https://blog.csdn.net/bingjianit/article/details/78822490,大家可以看一看。他的大体思路如下:
i、扫描全盘文件,剔除隐藏文件或者文件夹。
ii、将文件夹存入专门存放文件夹的队列中,并且为每个文件夹创建一个线程,将当前的文件夹中的指定文件存入集合中
iii、开启线程池,执行所有线程,执行效果类似于第二步,直到所有文件夹都遍历完(直到所有线程执行完毕),将所有文件输出。
我将该方案替代我之前的扫描方案的代码中,运行发现扫描的速度与之前方案对比,扫描速度似乎更慢。我猜测问题可能是线程过多。经过查看原方案的代码,并与这个方案进行对比,排除了这个方案。
2.3 基于原有的方案,优化代码
排除了上面两个方案,那只能改进现有代码了(回到原地),通过查看原有方案的扫描文件的源码,发现该方案的实现方式是通过contentprovider进行查询(由此确定可能该实现方法可能是最快的),经过查询资料发现contentprovider存储文件的方式类似于数据库(好久没用这个contentprovider,忘了,哈哈),我后来发现这个查询文件是不是可以通过mime_type这个字段进行查询,然后下面就开始进入正文:
首先先给一个各种文件对应的mime_type的链接 https://blog.csdn.net/mazaiting/article/details/78129532,这里面包好了.doc .mp4等等文件对应的mime_type。
下面看扫描文件的核心方法:
context.getContentResolver().query(uri,
projection,
selection,
selectionArgs,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC");
说明:该方法类似于数据库查询的方法。它有5个参数,这些参数我这里只做简单的描述,具体的大家可以自己自行百度。
参数 | 描述 | 举例 |
---|---|---|
uri | 内容提供者中文件的路径 | 比如:音频(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI) |
projection | 需要查询出来的列 | 文件的_id 文件的大小等 |
selection | 查询的条件 | 指定一个查询的条件,比如 selection = “mime_type = ?”; |
selectionArgs | 查询的条件的值 | 比如:selectionArgs = new String[]{“video/mp4”}; |
sortOrder | 排序 | 比如:MediaStore.Files.FileColumns.DATE_ADDED + ” DESC” |
下面贴实现代码(导包移除):
/**
* TODO 扫描文件
*/
public class DocScannerTask extends AsyncTask<Void, Void, List<Document>> {
List runnables = new ArrayList<>();
final String[] DOC_PROJECTION = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA,
MediaStore.Files.FileColumns.MIME_TYPE,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns.TITLE
};
final String[] projection = DOC_PROJECTION;
private final FileResultCallback resultCallback;
ArrayList allFiles = new ArrayList<>();
//TODO 指定扫描的文件类型
String[] selectionArgs = new String[]{".doc", ".docx", ".xls", ".xlsx", ".pdf", ".txt", ".rar", ".html", ".mp3", ".mp4", ".apk"};
private final Context context;
List list = new ArrayList<>();
public DocScannerTask(Context context, FileResultCallback fileResultCallback) {
this.context = context;
this.resultCallback = fileResultCallback;
}
/**
* TODO 子线程请求数据
*
* @param voids
* @return
*/
@Override
protected List doInBackground(Void... voids) {
final ArrayList documents = new ArrayList<>();
for (int i = 0; i < 4; i++) {
final int j = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
documents.addAll(getAllFiles(j));
}
};
runnables.add(runnable);
}
final ExecutorService executorService = Executors.newFixedThreadPool(3);
for (Runnable runnable : runnables) {
executorService.execute(runnable);
}
executorService.shutdown();
//等待线程池中的所有线程运行完成
while (true) {
if (executorService.isTerminated()) {
break;
}
}
return documents;
}
private ArrayList getAllFiles(int i) {
String selection = null;
String[] selectionArgs = null;
Uri uri = MediaStore.Files.getContentUri("external");
if (i == 0) { //一些能通过mime_type查询出来的文档 .doc .pdf .txt .apk
selection = "mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? ";
selectionArgs = new String[]{"text/html", "application/msword", "application/pdf", "text/plain"};
} else if (i == 1) { //一些不能通过mime_type查询出来的文档 .docx .xls .xlsx .rar
selection = "(" + MediaStore.Files.FileColumns.DATA + " LIKE '%.xls'" +
" or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.docx'" +
" or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.apk'" +
" or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.xlsx'" +
" or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.rar'" + ")";
selectionArgs = null;
} else if (i == 2) { //视频文件
uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
selection = "mime_type = ?";
selectionArgs = new String[]{"video/mp4"};
} else if (i == 3) { //音频文件
uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
selection = "mime_type = ? or mime_type = ?";
selectionArgs = new String[]{"audio/mpeg", "audio/ogg"};
}
ArrayList documents = new ArrayList<>();
final Cursor cursor = context.getContentResolver().query(uri,
projection,
selection,
selectionArgs,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC");
if (cursor != null) {
documents = getDocumentFromCursor(cursor);
cursor.close();
}
return documents;
}
@Override
protected void onPostExecute(List documents) {
super.onPostExecute(documents);
if (resultCallback != null) {
resultCallback.onResultCallback(documents);
}
}
/**
* TODO 从Cursor中获取数据
*
* @param data
* @return
*/
private ArrayList getDocumentFromCursor(Cursor data) {
ArrayList documents = new ArrayList<>();
while (data.moveToNext()) {
int imageId = data.getInt(data.getColumnIndexOrThrow(_ID));
String path = data.getString(data.getColumnIndexOrThrow(DATA));
String title = data.getString(data.getColumnIndexOrThrow(MediaStore.Files.FileColumns.TITLE));
//TODO 判断文件路径是否是指定的文件类型
if (path != null && contains(selectionArgs, path)) {
Document document = new Document(imageId, title, path);
String mimeType = data.getString(data.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE));
if (mimeType != null && !TextUtils.isEmpty(mimeType))
document.setMimeType(mimeType);
else {
document.setMimeType("");
}
document.setSize(data.getString(data.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE)));
if (!documents.contains(document))
documents.add(document);
}
}
return documents;
}
/**
* TODO 判断文件路径是否是types结尾
*
* @param types 扫描的文件类型
* @param path 文件路径
* @return
*/
boolean contains(String[] types, String path) {
for (String string : types) {
if (path.endsWith(string)) {
return true;
}
}
return false;
}
}
代码实现说明:我一开始实现是完全用一个selection 和 selectionArgs 进行查询,发现速度很快,只需要大概1s就能完全加载完(从进入到显示)。查询的两个字段如下:
selection = "mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ?";
selectionArgs = new String[]{"application/x-rar-compressed", "text/html","audio/mpeg", "audio/ogg", "video/mp4","application/msword", "application/pdf", "application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"text/plain","application/vnd.android.package-archive"};
额,很长,哈哈,后面再测试的过程中发现一个问题,那就是有一些文件(.docx .xls .xlsx .rar)按照mime_type 来查询的话无法加载出来。这就尴尬了,抓耳挠腮,搞了一下午,硬是没找着,并且马上要提测了,干脆骗测试,手机了没有这个文件?哈哈,显然不行,工作要紧。然后在查找资料的过程中发现了以为大牛的方法不错,可以解决我的这个难题https://blog.csdn.net/zocki33250/article/details/48112669,他的方案是在条件那个字段那里写一个数据库查询的语句
selection = "(" + MediaStore.Files.FileColumns.DATA + " LIKE '%.xls'" +
" or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.docx'" +
" or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.xlsx'" +
" or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.rar'" + ")";
selectionArgs = null;
经过测试,完美解决,但是时间上又稍微慢了个1-2s,不过相对于之前的10s,已经快很多了。
先写优点:
1、明显提高了查询的速度
2、个人认为该实现方案应该是比较好的(嘿嘿~)
缺点:
1、有局限性,需要知道所查询的文件的类型,然后再找到对应的mime_type
2、有些文件查询不出来,得利用其他方法,比如查询.xls等文件,如果加入该方法,查询速度明显降低,经过测试,如果这个方法里面的类型增多,那么该方案将没有啥有事,打开依然很慢。
我这个实现的方法只为了实现而实现,灵活性可能降低了一些,若各位大神有什么好的建议,欢迎指正,交流
之前我用的手机是小米手机,其他机型没有进行适配,后来经过测试发现,部分厂商的手机(华为、oppo)会出现apk文件扫描不到的情景,代码已在上面进行修改
各位看官大佬,我想问一个问题,为什么有些厂商的手机的有些文件(.xls、.rar etc.)无法根据mime_type进行扫描呢?contentprovider里面文件存储的mime_type不是唯一的吗?不是与文件类型一一对应的吗?