1、概述
项目中可能会有对android系统文件进行选择查看等操作,比如修改图像,文件浏览器等场景。前不久因为项目需要,需要实现一个文件浏览器,所以在此分享一下项目中的一些经验。
2、权限获取
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
//6.0以后
ActivityCompat.requestPermissions(this,new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE},100);
//10 以后 application标签中添加
android:requestLegacyExternalStorage="true"
//11(api30) 获取管理文件权限 需要发送一个意图 让用户来授权
Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
3、扫描获取文件
扫描获取文件时,特别是对全盘扫描时,会是一个非常耗时的操作,通常手机中的文件会在几万到几十万个不等,通过 递归逐个获取文件对象(new 一个文件对象),来获取文件信息,异步时间可能在10秒左右扫描完毕,开启多个线程一起 扫描不经不会提速很多,还要控制多线程问题,也不可取,最后选择使用android系统提供的MediaStore来获取文件,MediaStore这类主要就是给你提供获取各种文件所存放的表名也就是内容提供者的URI,我们就可以通过contentresolver来查询数据。但是还需要扫描单独某个文件夹下的文件怎么办了,因为是单独的一个文件夹,文件数量不会太多,我们就可以使用递归的方式扫描,比如微信和qq文件。
3.1mediaStore获取文件列表
ContentResolver contentResolver = MyApplication.getInstance().getContentResolver();
queryVideo(contentResolver);
queryImage(contentResolver);
queryByMime(contentResolver);
queryOther(contentResolver);
private void queryOther(ContentResolver contentResolver) {
String 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 '%.zip'" +
" or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.rar'" + ")";
Cursor cursor = contentResolver.query(MediaStore.Files.getContentUri("external"), null, selection, null, MediaStore.Files.FileColumns.DATE_ADDED + " DESC");
parseCursor(cursor);
}
private void queryByMime(ContentResolver contentResolver) {
String selection = "mime_type = ? or mime_type = ? or mime_type = ? or mime_type = ? ";
String[] selectionArgs = new String[]{"text/html", "application/msword", "application/pdf", "text/plain"};
Cursor cursor = contentResolver.query(MediaStore.Files.getContentUri("external"), null, selection, selectionArgs, MediaStore.Files.FileColumns.DATE_ADDED + " DESC");
parseCursor(cursor);
}
private void queryImage(ContentResolver contentResolver) {
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, MediaStore.Images.Media.MIME_TYPE + "= ? or " +
MediaStore.Images.Media.MIME_TYPE + "= ? ", new String[]{"image/jpeg", "image/png"}, MediaStore.Images.Media.DATE_MODIFIED);
parseCursor(cursor);
}
private void queryVideo(ContentResolver contentResolver) {
Cursor cursor = contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, MediaStore.Video.Media.MIME_TYPE + "= ? or " +
MediaStore.Video.Media.MIME_TYPE + "= ?", new String[]{"video/mp4", "video/x-flv"}, MediaStore.Images.Media.DATE_MODIFIED);
parseCursor(cursor);
}
private void parseCursor(Cursor cursor) {
if (cursor == null) {
return;
}
while (cursor.moveToNext()) {
//String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE));
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA));
long modifyTimed = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_MODIFIED));
modifyTimed = modifyTimed * 1000;
String displayName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME));
int size = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE));
LogUtil.i("lihui" ,"size " + size + "dispaly " + displayName + "path " + path);
}
cursor.close();
}
上面通过传入一个contentResolver对象就可以获取数据,其实就是sql查询,有些不能通过媒体类型查询的文件就通过指定SQL语句来过滤记录,注意写selection语句的字符串时最后一个?后的空格不能去掉,不然会查询失败,另外获取到cursor后也就是获取到一条记录后,需要先根据字段名获取下标,然后根据下标获取改字段的内容,DATA表示内容也就是该文件的路径、DATE_MODIFIED表示最后被修改时间(单位时秒不是毫秒)、DISPALY_NAME表示文件名、SIZE表示文件大小(单位时字节),
3.2递归获取文件
private void internalIterator(File rootDirectoryFile) {
if (rootDirectoryFile == null) {
return;
}
File[] listFiles = rootDirectoryFile.listFiles();
if (listFiles == null || listFiles.length == 0) {
return;
}
for (final File listFile : listFiles) {
if (listFile == null) {
return;
}
//TODO something
if (listFile.isDirectory()) {
internalIterator(listFile);
}
}
}
3.3获取人类能理解的大小
public static String getHumanSize(long totalSpace) { //字节
double oneG = 1024 * 1024 * 1024; //一g多少字节
double oneM = 1024 * 1024;
double oneK = 1024;
DecimalFormat df = new DecimalFormat("#.00");
if (totalSpace >= oneG) {
String result = df.format(totalSpace / oneG);
return result + "GB";
}
if (totalSpace >= oneM) {
String result = df.format(totalSpace / oneM);
return result + "MB";
}
if (totalSpace >= oneK) {
String result = df.format(totalSpace / oneK);
return result + "KB";
}
return totalSpace + "B";
}
4、删除和重命名文件
1、删除文件我们直接可以调用delete方法
2、重命名我们可以renameTo方法,传入一个新文件对象
3、更新MediaStore,因为删除或者重命名后需要更新列表,如果不手动触发更新,系统只会在重启后更新,显然不合理,所以需要用到MediaScannerConnection
4、如果只是需要删除文件,也可用用contentresolver的delete,删除字条记录
//参数一为上下文 参数二为路径数组,经过测试可以是文件夹,参数三是媒体类型,传入null会自动推断,参数四为更新回调,如果路径文件不存在会删除记录。
MediaScannerConnection.scanFile(context.getApplicationContext(),path,null,completedListener);
//通过contentresolver删除
int res = 0;
if (filePath.endsWith(".mp4") || filePath.endsWith(".flv")) {
res = mContext.getContentResolver().delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
MediaStore.Video.Media.DATA + "= \"" + filePath + "\"",
null);
} else if (filePath.endsWith(".jpg") || filePath.endsWith(".png") || filePath.endsWith(".bmp") || filePath.endsWith(".jpeg")) {
res = mContext.getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
MediaStore.Images.Media.DATA + "= \"" + filePath + "\"",
null);
} else {
res = mContext.getContentResolver().delete(MediaStore.Files.getContentUri("external"),
MediaStore.Files.FileColumns.DATA + "= \"" + filePath + "\"",
null);
}