手把手教你实现一个文件浏览器
文件选择功能在很多的应用中都有被用到,用来选择用户手机中的文件或目录。首先我建议你使用官方的API选择手机文件,然后使用dview的方式选择目录。
使用官方API选择手机文件
public static void selectFile(@NonNull Activity activity, int requestCode) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
activity.startActivityForResult(intent, requestCode);
}
使用这个API可以打开手机系统内置的文件选择器,虽然一般来说会比较丑,但功能全,稳定性好。然后这个方法已经被集成到dora framework( https://github.com/dora4/dora )中了。
IntentUtils.selectFile(context, AppConfig.REQUEST_CODE_CHOOSE_FILE)
使用以上方式即可调用。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == AppConfig.REQUEST_CODE_CHOOSE_FILE) {
val uri = data?.data ?: return
val path = IoUtils.getPathFromUri(context, uri)
if (path == null) {
Log.e("dora", "You can't read file.")
return
}
val file = File(path)
// 在此处理文件...
}
}
把所有的request code定义到全局是有好处的,第一个好处呢就是方便查找功能,第二个好处呢则是可以方便保证全局的请求码都不一致,这样避免错误。说到这里,我提一嘴通常初中级程序员喜欢使用的魔法数问题。总是一大堆的数字计算,而不把数字转换成全局的常量。这样做在算法复用并修改时很有可能漏掉,也很容易被忽略,特别是缺少代码评审流程的开发团队。所以,我建议除了你完全可以确认不会被其他地方用到以外,常量一律提到全局。
自定义一个文件浏览器
文件浏览器组件已经被定义到dview框架集合下,https://github.com/dora4/dview-file-browser 。
效果演示
巧克力风格的文件浏览器,目前适配的语言有英语、中文(简体)和中文(繁体)。
Get Started
// 选择目录
FileBrowser.chooseFolder(activity)
// 选择文件
FileBrowser.chooseFile(activity)
// 选择文件和目录
FileBrowser.chooseFileAndFolder(activity)
选择目录通过点击标题栏右上角的选择按钮,而选择文件则点击具体的某一个文件条目。
public static final int REQUEST_CODE_CHOOSE_FILE = 1;
public static final int REQUEST_CODE_CHOOSE_FOLDER = 2;
public static final int REQUEST_CODE_CHOOSE_BOTH_FILE_AND_FOLDER = 3;
通过以上枚举的请求码获取选择的路径。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == FileBrowser.REQUEST_CODE_CHOOSE_FILE) {
val path = data?.getStringExtra(FileBrowser.EXTRA_PATH) ?: return
if (path == null) {
Log.e("dora", "You can't read file.")
return
}
val file = File(path)
// 在此处理文件...
}
}
思路分析
要开发这样一个文件浏览器,首先要注意的一点就是Android 13开始细分了文件管理权限。我们来看看Android 13以后的6种文件权限分别是什么?
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Android 13版本适配,细化存储权限开始 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Android 13版本适配,细化存储权限结束 -->
READ_EXTERNAL_STORAGE
:Android 6以前定义在xml中,Android 6到Android12,使用代码运行时申请权限。
WRITE_EXTERNAL_STORAGE
:同READ_EXTERNAL_STORAGE。
MANAGE_EXTERNAL_STORAGE
:调用系统的文件浏览器API使用。
READ_MEDIA_IMAGES
、READ_MEDIA_IMAGES
、READ_MEDIA_VIDEO
:Android13开始要逐个申请,当然是代码运行时进行权限的申请。需要注意的是,即使你在运行时申请权限,也要在清单文件中定义,这样才能在设置中被用户看到。
阅读源码,我们发现在清单文件中,有这么一段。
<application>
<activity
android:name="dora.widget.filebrowser.FileBrowserActivity"
android:exported="true">
<intent-filter>
<action android:name="dora.widget.filebrowser.action.CHOOSE_FILE"/>
<action android:name="dora.widget.filebrowser.action.CHOOSE_FOLDER"/>
<action android:name="dora.widget.filebrowser.action.CHOOSE_BOTH_FILE_AND_FOLDER"/>
</intent-filter>
</activity>
</application>
将android:exported
属性设置为true,表示此activity可以被外部应用调启。<intent-filter/>
中定义了三种action,用于区分你要调启这个activity的用途。当然调启的逻辑都被封装到了FileBrowser
类中,使用者可以无需知道这些原理即可使用。