Android实现一个简单的文件浏览器
有时候会碰到一些文件浏览的需求,但是App本身又并非一个文件管理相关的App,可能只是某个功能,需要让用户来选择文件进行后续操作。所以这里只是为了实现一个简单的文件浏览的功能,如果需要实现一整套文件管理的功能,请换方案或者自行添加功能。
文件浏览的常用实现
文件浏览的功能,实现方式有好几种,这里介绍几种常用的方式:
- 使用MediaStore方式,直接获取到所有的有效文件,再进行文件夹组合。
- 使用Runtime.getRuntion().exec("cmdline")直接执行命令行命令,然后解析返回结果。
- 使用File类的方式来访问目录之下所有的文件。
现在来分析分析三种有什么优缺点:
MediaStore
MediaStore会在系统内部维护一张SQLite表,然后通过ContentResolver的方式就可以获取到SQLite表中记录的文件。通过MediaStore可以很方便的写类似SQL语句来进行查询,并且文件的格式、名称、尺寸等,都有特定的字段去标识。
下面给出一个external.db的SQLite表结构:

从图中可以看到,external.db中包含了一个“files”的Table,内部维护了外部存储器里所有需要被维护的文件。而它还有对特定类型的文件类型进行了视图显示,例如:Images、video。
如果仅仅是需要做到文件浏览,可以直接通过MediaStore分别查询内部存储器和外部存储器的files表即可。
file表的结构如下图:

使用MediaStore使用起来非常的方便,详细的使用方式这里就不介绍了。但是使用MediaStore还是有一些问题的,例如第三方App保存的文件,如果第三方App没有处理的话,可能你需要等到下次重启之后,才能通过MediaStore扫描到,它会在重启、Sdcard装载等时机,重新扫描刷新。当然也有办法自动去刷新,只需要发送一个对应的Broadcast即可,但是也是有坑的,大概是在Android 4.0+之后,只能指定目录进行刷新,所以需要维护大量的目录来做。这里就不展开讨论了,具体的可以自行百度。
CmdLine
使用Runtion.getRuntion.exec()的方式直接执行例如:cd、ls等命令行命令,然后读取输出结果进行解析,就可以拿到对应目录下的文件信息。这种方式使用起来其实也非常的方便,而且效率也是有保障的,但是解析数据上,操作的数据是大量的String,可能在开发的时候,不够直观需要处理各种占位、符换行符等问题上耗时。
至于如何使用Runtime,同样不展开讨论了。

File类
对的,上面两个不展开讨论的方案,都不是本片的主题,下面就要进入主题了。本片主要就是用一个File类来实现文件浏览的功能。
仔细阅读File类的源码,可以发现,它核心的方法全是native,在Android源码中,使用到native的方法,在效率上都不会太差。而File类同样也提供了一些文件浏览相关的方法,当然最终还是调用到native里了。使用File自己提供的一些方法,完全可以实现一个对简单的文件浏览器的
使用File实现文件浏览器
既然要用带File类,这里介绍先几个File类里的相关方法:
- String getName() :得到当前文件名
- String getParent():得到当前文件上一级目录的文件名
- File getParentFile():得到当前文件上一级的文件File对象
- String getAbsolutePath():得到当前文件的绝对路径
- boolean isDirectory() : 判断当前文件是否是一个目录
- boolean isFile(): 判断当前文件是否是一个文件
- long lastModified():得到文件最后修改时间
- long length():得到文件的长度。
- File[] listFiles(FileFilter fileter):查询此文件夹内的文件和目录
上面介绍的方法里,大部分看描述就可以知道用途了,但是用来做文件管理器,主要的一个方法就是listFiles(),它是可以得到查询的目录的内的文件和目录的。并且提供一个FileFilter的类去用来过滤需要的文件,这个FileFilter处理的是一个File对象,如果仅对文件名有过滤要求,可以使用FIlenameFilter来过滤,但是FileFilter使用起来会更灵活一些。
从源码里可以看到,FileFilter是一个接口类,它内部只需要实现accept()方法,这个方法会在得到子目录的列表的时候,遍历列表去过滤掉不符合条件File对象。
package java.io;
/**
* An interface for filtering {@link File} objects based on their names
* or other information.
*
* @see File#listFiles(FileFilter)
*/
public interface FileFilter {
/**
* Indicating whether a specific file should be included in a pathname list.
*
* @param pathname
* the abstract file to check.
* @return {@code true} if the file should be included, {@code false}
* otherwise.
*/
public abstract boolean accept(File pathname);
}
既然基于File的文件浏览的主要是需要实现FileFilter这个接口,那么这里给出一个简单的实现示例,只是用来扫描目录下所有的APK文件。
public class PFileFilter implements FileFilter {
@Override
public boolean accept(File pathname) {
if (pathname.exists()) {
if (pathname.isDirectory() && pathname.canRead() && pathname.canWrite()) {
// 文件夹只要可读可写 就返回
return pathname.listFiles().length > 0;
}
if (pathname.isFile() && pathname.canRead() && pathname.canWrite()) {
// 文件还需要满足固定后缀
if (pathname.getName().toLowerCase().endsWith(".apk")) {
return true;
}
}
}
return false;
}
}
按业务需求实现了FileFilter,接下来只需要调用调用File.listFiles(),就可以拿到符合需求的File[]了。
完整的帮助类
虽然使用File.listFiles()配合FileFilter方法就可以实现文件的简单浏览。但是可能还需要一些简单的帮助类,例如拿到文件最后修改时间、得到文件的MimeType、得到文件的后缀等等需求。这里简单的对File进行了一个封装类,就不一一介绍了,直接上代码了,有需要可以直接拿来用。
/**
* Created by 承香墨影 on 2016/5/5.
*/
public class FileMamagerHelper {
/**
* 得到特定路径下有效文件
*/
public static List<File> getFilesByFile(File file) {
if (file != null && file.exists()) {
File[] files = file.listFiles(new PFileFilter());
List<File> filterFile = new ArrayList<>();
if (Collections.addAll(filterFile, files)) {
return filterFile;
}
}
return null;
}
/**
* 得到特定路径下有效文件
*/
public static List<File> getFilesByPath(String path) {
if (TextUtils.isEmpty(path)) {
return null;
}
File file = new File(path);
return getFilesByFile(file);
}
/**
* 检查是否存在上一级目录
*
* @return
*/
public static boolean hasParent(String filePath) {
if (TextUtils.isEmpty(filePath)) {
return false;
}
File file = new File(filePath);
return hasParent(file);
}
/**
* 得到上一级目录
* @param filePath
* @return
*/
public static String getParent(String filePath) {
if (TextUtils.isEmpty(filePath)) {
return "";
}
File file = new File(filePath);
return file.getParent();
}
/**
* 检查是否存在上一级目录
*
* @return
*/
public static boolean hasParent(File file) {
if (file != null && file.exists()) {
return file.getParentFile() != null;
}
return false;
}
/**
* 得到文件名
*/
public static String getFileName(File file) {
if (file != null) {
return file.getName();
}
return "";
}
/**
* 返回文件最后修改日期
*/
public static String getFileLastDate(File file) {
if (file == null) {
return "";
}
long date = file.lastModified();
if (date == 0) {
return "";
}
@SuppressLint("SimpleDateFormat")
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
return simpleDateFormat.format(new Date(date));
}
public static String getFileSize(File file) {
if (file.isFile()) {
float size = file.length() / 1024f;
if (size < 1024) {
if (size < 0.01) {
size = 0.01f;
}
return String.format("%.2fKB", size);
}
size = size / 1024f;
return String.format("%.2fMB", size);
}
return "";
}
/**
* 获得文件的mimeType
*
* @param file
* @return
*/
public static String getMimeType(File file) {
String suffix = getSuffix(file);
if (suffix == null) {
return "file/*";
}
String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix);
if (type != null || !type.isEmpty()) {
return type;
}
return "file/*";
}
/**
* 获得文件的后缀
* @param file
* @return
*/
private static String getSuffix(File file) {
if (file == null || !file.exists() || file.isDirectory()) {
return null;
}
String fileName = file.getName();
if (fileName.equals("") || fileName.endsWith(".")) {
return null;
}
int index = fileName.lastIndexOf(".");
if (index != -1) {
return fileName.substring(index + 1).toLowerCase(Locale.US);
} else {
return null;
}
}
}
有这个工具类,完全可以使用ListView加Adapter的方式,实现一个简单的文件浏览器,就不再提供单独的示例了。
结尾
虽然用File类可以完全可以实现文件浏览器的功能,当然这个并不是最优的解决方案,但是只要不是专业的文件管理器App,这种方式完全是满足需求的。
