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

转自 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时,你就不知道如何去做。
--------------------- 

猜你喜欢

转载自blog.csdn.net/hnlgzb/article/details/88597970