转自 https://blog.csdn.net/weixin_33698823/article/details/87269955

浅谈Android文件管理器的几种实现方式

为了完成毕业设计,我花费了一个多月的时间来研究并实现文件管理器。由于之前没有实现过文件管理器的经验(只是偶尔为了方便自己操作电脑上的文件,临时用Java写几个函数来处理一下文件),因此,我对于文件管理器的某些实现没有什么思路。为了明白一个完整的文件管理器的具体实现,我决定借助开源的力量——阅读一些开源的文件管理器源码。阅读源码最重要的一点就是要有目的性,不然有可能你花费很长时间也没办法得到你想要的答案。而我阅读开源文件管理的源码是为了大致了解文件管理器的实现方式,最终对不同的实现方式进行比较,找出比较不错的实现方式来实现文件管理器。

通过研究,我大致得出Android文件管理器以下几种实现方式,有兴趣的读者可以去尝试一下:

Android存储访问框架(因为没有时间没有仔细研究,有兴趣地可以去研究documentsui和DownloadProvider的实现源码)
Java.io.File API
Android媒体数据库
/sytem/bin下的shell命令,如ls、find、grep等命令的组合。
以上几种实现方式各有优劣,可以根据需求自由组合搭配。比如通过Android媒体数据库来实现查询比较简单,快速,然而存在数据不实时更新的问题,而通过java.io.File API来获取的数据准确,但是存在查询慢的问题。这就是我这一个多月来研究所得出的Android文件管理器的实现方式。

Android媒体数据库

Android系统在每次开机启动的时候都会通过媒体扫描器MediaScanner扫描手机中特定目录下所有的文件并更新媒体数据库(internal.db和external.db),其中

/data/data/com.android.providers.media/databases/internal.db记录了/system/media目录下的所有文件信息, /system/media目录下的文件主要是一些系统的通知提示音、闹铃等。
/data/data/com.android.providers.media/databases/external.db记录了/storage/sdcard目录下的所有文件信息, /system/media目录下的文件主要是一些系统的通知提示音、闹铃等。
其他应用可以通过媒体内容提供者MediaProvider来访问媒体数据库中的数据。

Android手机存储分为手机内存和内部存储设备(内置SD卡)。
手机内存保存的是一些系统数据,一般不允许访问和修改,除非获取到Root权限。
内部存储设备保存的则是用户数据,比如我们安装的应用数据,下载的文件、音乐、视频、图片等。文件管理器主要就是为了让用户方便管理和访问这些数据。该内部存储设备挂载在Environment.getExternalStorageDirectory().getAbsolutePath()目录下。

接下来大致使用Android媒体数据库的方式实现列出指定目录下的文件的功能以及使用shell命令实现文件分类的功能,至于不介绍java.io.File API和Android存储访问框架的原因,则是前者比较简单,而后者我也没有深入去研究分析。

Android媒体数据库(列出文件)

通过Android媒体数据库实现文件管理器的好处是方便,快速。坏处就是Android媒体数据库的数据不是实时更新。
通过下面这行代码即可返回Android媒体数据库中files表中所有行的所有字段:

Cursor query = getContentResolver().query(MediaStore.Files.getContentUri("external"), null, null, null, null);
但是,我突然发现一个问题,就是MediaProvider不提供返回指定目录的文件的Uri。而当用户点击指定目录的时候,需要通过MediaProvider查询指定目录下的文件并显示出来。通过在adb shell研究external.db中files数据表各个字段的属性值,最终我发现可以通过parent字段来解决这个问题。files表中每条记录就代表一个文件或者目录,而其parent字段就表示该文件的父目录的索引。位于/storage/sdcard目录下的所有文件的parent字段均为0,因此可以通过下面这条sql查询语句来返回位于/storage/sdcard目录下的所有文件:

select * from files where parent = 0
以此类推,比如当用户点击/storage/emulated/0/Android目录时,首先得到/storage/emulated/0/Android这个目录文件在文件表中的索引,比如11,然后通过下面这条sql语句就可以查询位于/storage/sdcard/Android目录下的所有文件

select * from files where parent = 11
通过下面代码即可返回SD卡根目录下的所有文件:

private static final Uri EXTERNAL_STORAGE_URI_FILES = MediaStore.Files.getContentUri("external");
  ContentProviderClient client = SimpleApplication.acquireUnstableProviderOrThrow(getActivity().getContentResolver(), MediaStore.Files.getContentUri("external").getAuthority());
        String projection[] = {
                MediaStore.Files.FileColumns.PARENT,
                MediaStore.Files.FileColumns._ID,
                MediaStore.Files.FileColumns.MIME_TYPE,
                MediaStore.Files.FileColumns.DATA,
        };
        String selection = MediaStore.Files.FileColumns.PARENT + "= ?";
        String selectionArgs[] = {
                "0"
        };
        Cursor query = client.query(EXTERNAL_STORAGE_URI_FILES, projection, selection, selectionArgs, null);
shell命令(文件分类)

获取SD卡根目录所有的zip文件和apk文件,原理很简单,因为Android是基于Linux内核的,所以在Android系统/system/bin目录下包含一些shell命令。通过BufferedReader来读取ls命令的输出,通过正则表达式匹配过滤,最终得到你想要的结果。

List<String> zipFile= new ArrayList<>();
       List<String> apkFile = new ArrayList<>();
        BufferedReader bufferedReader = null;
        LogUtil.START_TIME = System.currentTimeMillis();
        try {
            java.lang.Process process = Runtime.getRuntime().exec("/system/bin/ls -R " + Utils.EXTERNAL_STORAGE_PATH);
            bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String read = null;
            String strNowDir = null;
            String nowExName = null;
            Pattern p = Pattern.compile(Utils.EXTERNAL_STORAGE_PATH + "(.+?):", Pattern.CASE_INSENSITIVE);
            Pattern exp = Pattern.compile("(.zip$|.apk$)", Pattern.CASE_INSENSITIVE);
            while((read = bufferedReader.readLine()) != null) {
                // 判断是否为目录行
                Matcher m = p.matcher(read);
                // 如果为目录行, 则记录为当前目录
                if(m.find()) {
                    strNowDir = read.replaceAll(":", "/");
                    continue;
                }
                // 判断文件类型
                Matcher exM = exp.matcher(read.toLowerCase());
                if(exM.find()) {
                    nowExName = exM.group();
                    if(".zip".equalsIgnoreCase(nowExName)) {
                        zipFile.add(read);
                    } else if(".apk".equalsIgnoreCase(nowExName)) {
                        apkFile.add(read);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
我所采用的实现方式

由于时间的关系,对于文件管理器的研究,我只是简单地了解了一下,就开始匆忙地选择java.io.File API配合Android媒体数据库来实现文件管理器了。之所以采用这种方式,是因为我觉得这种方式实现起来比较简单粗暴,并且数据的加载和查询数据也比较快。然而在基本完成文件管理器应有的基本功能之际,我发现了采用java.io.File API配合Android媒体数据库来实现文件管理器的不足。
Android媒体数据库的数据获取和刷新时机:

在Android刚开机启动的时候,MediaScannerService会去扫描Android文件系统两个目录(/system/media和内置sd所在的目录)的文件并更新到Android媒体数据库
应用通过广播或者aidl主动触发MediaScannerService扫描文件,从而更新指定文件的数据到Android媒体数据库。由于Android媒体数据库更新不及时,从而导致通过Android媒体数据库获得的数据显示不太准确。
我大致知道两种方式来解决Android媒体数据库不实时更新的问题(治标不治本):

在你对文件进行操作之后,通过调用MediaScannerConnection.scanFile方法或者发送广播来重新扫描指定文件(从Android4.4之后无法再通过广播扫描目录),从而更新Android媒体数据库。也可以通过MediaProvider直接对Android媒体数据库进行增删改查。这种方式存在缺陷就是如果存在第三方软件,第三方软件对文件的操作,你的应用是不知情的。也就是在第三方软件(不仅仅是文件管理器)的文件变化是无法实时更新到Android媒体数据库的。
通过FileProvider监听所有目录下的文件变化,通过调用MediaScannerConnection.scanFile方法或者发送广播来重新扫描指定文件(从Android4.4之后无法再通过广播扫描目录),从而更新Android媒体数据库。
然而,开弓没有回头箭,我也不想再花费时间精力去尝试其他实现方式来从头实现文件管理器了。

总结

最后,简单谈一下我的毕设题目为什么选择实现一个Android文件管理器。Android文件管理器已经很成熟了,比如我个人觉得现有的两个不错的Android文件管理器——ES文件浏览器和SolidExplorer2。我做不出比这两个更好的文件管理器(团队和个人的差距),那我为什么还要去实现文件管理器呢?因为人们的日常生活离不开文件,我想通过实现文件管理器来更加深入地去了解文件以及文件的管理和分类。说白了,就是满足我的求知欲和好奇心。而且用一件东西和你亲手去做一件东西的体验是不一样的,你对这件东西的理解程度也是不一样的。除此之外,任何东西都有它的不足。你仅仅只是会用文件管理器,那你对文件管理器只有一些很粗浅的了解,你了解最多的就是怎么用,而没有办法对它进行修改,进行优化定制,从而满足你的需求。而实现文件管理器你就需要更深入地去了解它,分析它的原理,从而实现出比较不错的文件管理器。并且因为是你做的,所以你可以随意修改优化定制以满足你的需求。比如某个API,你如果仅仅只是会用(停留在表面层次),而没有去深入分析它的源码,分析它的实现原理。任何API都是有局限性的,当你想要扩展这个API时,你就不知道如何去做。
---------------------

浅谈Android文件管理器的几种实现方式(原理篇)--对我有帮助相关推荐

  1. 浅谈POE供电系统中PSE两种供电方式——终端跨度、中间跨度

    标准的五类网线有四对双绞线但是在10M BASE-T和100M BASE-T中只用到其中的两对. IEEE80 2.3af允许两种用法: 1. 中间跨度法,信号线(1,2,3,6).电源线(4,5,7 ...

  2. 浅谈Android onTouchEvent 与 onInterceptTouchEvent的区别详解

    浅谈Android onTouchEvent 与 onInterceptTouchEvent的区别详解 本篇文章小编为大家介绍,Android onTouchEvent 与 onInterceptTo ...

  3. android获取存储设备根目录,浅谈android获取存储目录(路径)的几种方式和注意事项...

    通常, 我们创建文件/目录, 或者存储图片什么的, 我们都需要拿到手机的存储路径, 现在我们就来看一下获取手机存储路径的几种方式(作为工具类方法调用即可): 第一种: 获取 /storage/emul ...

  4. 浅谈Android引用计数(2)

    在浅谈Android引用计数(1)中讲了LightRefBase实现对象计数管理的原理,这篇文章将要分析重量级的引用基类:RefBase的实现和它的作用. 下面是RefBase和相关类的类图: 图中可 ...

  5. 浅谈Android保护技术__代码混淆

    浅谈Android保护技术__代码混淆 浅谈Android保护技术__代码混淆 代码混淆 代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读 ...

  6. android fps 垂直同步,浅谈Android流畅度

    原标题:浅谈Android流畅度 哈哈 讲个故事 白 1 流畅度 关于流畅度谷歌官方给出的解释为:running at a consistent 60 frames per second, witho ...

  7. 浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路

    原文地址: http://blog.csdn.net/luoshengyang/article/details/6627260 在前面一篇文章浅谈Service Manager成为Android进程间 ...

  8. android 存储空间监控,浅谈 Android 内存监控(中)

    前言 在上篇 浅谈 Android 内存监控(上) 中,我们聊了 LeakCanary,微信的 Matirx 和美团的 Probe,它们各自有不同的应用场景,例如,在开发测试环境,我们会偏向用 Lea ...

  9. 浅谈Android Architecture Components

    浅谈Android Architecture Components 浅谈Android Architecture Components 简介 Android Architecture Componen ...

最新文章

  1. LeetCode简单题之通过翻转子数组使两个数组相等
  2. 关于团队建设,穆帅能教我们什么?
  3. spring security源码分析之web包分析
  4. 学习动态性能表(10)--v$session_longops
  5. 多视图几何总结——三角形法
  6. 让我们一起摇摆 Turnipbit体感遥控车
  7. JavaEE课程目标、个人目标、互联网应用和企业级应用的区别
  8. .NET 5.0 RC 2 发布,正式版将在 11 月 .NET Conf 大会上发布
  9. 关于在不同版本和平台之间进行还原或复制的常见问题
  10. 持续集成部署Jenkins工作笔记0021---21.关闭防止跨站点请求伪造
  11. 你熟知的开源项目,幕后推手竟然是他们?
  12. 【原创】Nginx+PHP-FPM优化技巧总结
  13. 【多标签文本分类】层次多标签文本分类方法
  14. 支付宝第三方登录接口 php,PHP调用支付宝支付接口操作步骤
  15. matlab 变速不变调,使用GoldWave 轻松实现变速不变调
  16. 小米手机通过USB连接MAC电脑
  17. UVA815 洪水Flooded
  18. HTML-内嵌框架-00
  19. iPhone开发之SQLite 实现中文排序的教程
  20. shader镜子效果错误

热门文章

  1. “多点”开花,独立走向新零售
  2. 应广单片机的建表方式
  3. 仙剑奇侠传四服务器维护,仙剑奇侠传四手游无法登陆游戏怎么办
  4. 什么软件可以将flac转换mp3
  5. 寒假学习心得--从0开始学破解
  6. HBuilder常用快捷键
  7. 叶子云桌面虚拟化解决方案100-200用户
  8. 性能测试报告内容参考
  9. 为何使用人工智能软件?
  10. 小白的测试人生(二)——软件测试行业发展现状及前景