Android文件管理器选择文件,获得文件路径URI转File

记一次文件上传引发的血案。

解决QQ浏览器com.tencent.mtt.fileprovider问题。

测试Demo


更新列表

日期 修改内容
2019年7月2日 更新遇到的问题

前情描述:

使用系统文件管理器,选择指定文件类型上传。

基础知识
  • MIME
  • 调起文件管理器
  • 指定浏览位置(路径转URI)
  • 设置多种文件类型
  • URI转路径
踩坑
  • com.tencent.mtt.fileprovider 问题

1. MIME

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。

final String DOC = "application/msword";
final String XLS = "application/vnd.ms-excel";
final String PPT = "application/vnd.ms-powerpoint";
final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
final String XLSX = "application/x-excel";
final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
final String PDF = "application/pdf";
final String MP4 = "video/mp4";
final String M3U8 = "application/x-mpegURL";

更多文件类型,自行百度

2. 调起文件管理器

  1. 所有类型文件

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    //任意类型文件
    intent.setType("*/*");
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    startActivityForResult(intent,1);
    
    //-------常用类型
        //图片
    //intent.setType(“image/*”);
        //音频
    //intent.setType(“audio/*”);
        //视频
    //intent.setType(“video/*”); 
    //intent.setType(“video/*;image/*”);
    
    
  2. 系统的相冊

     Intent intent= new Intent(Intent.ACTION_PICK, null);
     intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
     startActivityForResult(intent, REQUEST_CODE_FILE); 
    
    

3. 指定浏览位置(路径转URI)

跳转到指定路径下,涉及到将路径转为URI,考虑Android版本区别

/**
 * file --> uri
 * @param context
 * @param file
 *
 * @return
 */
public static Uri getUriFromFile(Context context, File file) {
    if (context == null || file == null) {
        throw new NullPointerException();
    }
    Uri uri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        uri = FileProvider.getUriForFile(context.getApplicationContext(), BuildConfig.APPLICATION_ID + ".fileprovider", file);
    } else {
        uri = Uri.fromFile(file);
    }
    return uri;
}

由于7.0的升级还需要在AndroidManifest.xml中配置FileProvider

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

${applicationId}.fileprovider这个配置要记牢,后期遇到大坑就靠这个值了。

xml/file_paths 文件如下:

参考CSDN

<!--物理路径相当于Context.getFilesDir() + /path/-->
<files-path name="name" path="path" />

 <!--物理路径相当于Context.getCacheDir() + /path/-->
<cache-path name="name" path="path" /> 
 <!-- 物理路径相当于Environment.getExternalStorageDirectory() + /path/-->
<external-path name="name" path="path" />
 <!-- 物理路径相当于Context.getExternalFilesDir(String) + /path/-->
<external-files-path name="name" path="path" />
 <!-- 物理路径相当于Context.getExternalCacheDir() + /path/-->
<external-cache-path name="name" path="path" />

将文件路径转(使用微信下载目录做测试)为URI后,设置到Intent中。

/**
 * 根据类型,加载文件选择器
 */
private void chooseFileWithPath() {
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    //跳转指定路径,如果路径不存在,切到sdcard
    //需要读权限
    String path = getSDPath();
    if (!TextUtils.isEmpty(path)) {
        //使用微信下载目录测试
        path = path + File.separator + "tencent/MicroMsg/Download";
        File file = new File(path);
        if (file.exists()) {
            //主要代码
            intent.setDataAndType(FileUtil.getUriFromFile(this, new File(path)), "*/*");
        } else {
            intent.setType("*/*");
        }
    } else {
        intent.setType("*/*");
    }
    startActivityForResult(intent, REQUEST_CODE_FILE);
}

主要生效代码:

Intent intent = new Intent(action,uri);
intent.setType("*/*");
startActivityForResult(intent, REQUEST_CODE_FILE);

或者

Intent intent = new Intent(action);
intent.setDataAndType(uri, "*/*");
startActivityForResult(intent, REQUEST_CODE_FILE);

特别注意:
指定目录这种方式调起,在原生的管理器没有其他注意的,但是如果用户使用三方的管理器,例如QQ浏览器,那么在进入到指定目录下,不可以执行返回操作。
例如:
如果路径设置的是/sdcard/tencent/MicroMsg/Download,在进入download目录下,如果该目录下没有文件,那么想返回到其上层目录MicroMsg,是不可以的。

4. 设置多种文件类型

通过intent.setType()方式设置的文件类型,只能生效一次,所以如果想只选择doc、excel、pdt、ppt等办公文档,过滤掉其他文件,就不能使用intent .setType()方式,而是使用Intent.EXTRA_MIME_TYPES来传值。


final String DOC = "application/msword";
final String XLS = "application/vnd.ms-excel";
final String PPT = "application/vnd.ms-powerpoint";
final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
final String XLSX = "application/x-excel";
final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
final String PDF = "application/pdf";

/**
 * 多种文件类型
*/
private void chooseMoreTypes() {
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    String[] mimeTypes = {DOC, DOCX, PDF, PPT, PPTX, XLS, XLS1, XLSX};
    intent.setType("application/*");

    intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
    startActivityForResult(intent, REQUEST_CODE_FILE);
}

以上是准备工作,调起文件管理器进行选择文件,对于一个Android开发者来说,以上步骤只是相当于一小步,不要忘记适配

接下来才是长征路。

5. URI转路径

在这一步主要解决的是将返回的URI转换为File,大坑也往往就在这一步出现。

从网上能找到File转File的代码,但是百度出来的内容,10篇有8篇是一样的,剩下2篇不能看。

但是这8篇中几乎都是相同的,没有解决一个至关重要的问题QQ文件管理器

也可能是不会用搜索引擎吧。

上代码

/**
 * 专为Android4.4设计的从Uri获取文件绝对路径,以前的方法已不好使
 */
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {

        //一些三方的文件浏览器会进入到这个方法中,例如ES
        //QQ文件管理器不在此列

        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {
        ...
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
        ...
        }
    } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore (and general)
        return getDataColumn(context, uri, null, null);
    } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File
        ...
    }
    return null;
}

发现部手机会有第三方的文件管理器,如ES,QQ等目前只接触到这两种,不排除其他APP,相信大部分都是好同志,但不限于鹅厂大佬。

通过QQ浏览器选择到的文件,则会报出不存在_data字段这个错误。

WTF

懵逼.jpg

返回的URI中uri.getAuthority()的值并不是自己在Manifest.xml中设置的${applicationId}.fileprovider而是com.tencent.mtt.fileprovider

这时候,前面搜索到的文章,几乎都没有解决这个问题。

鉴于水平有限,被坑了半天后。

通过分析uri.getPath();的值。

content://com.tencent.mtt.fileprovider/QQBrowser/demo.mp4

写下来如下代码,如各位大佬有更好的解决方案,望转告。

//判断QQ文件管理器
if (isQQMediaDocument(uri)) {
    String path = uri.getPath();
    File fileDir = Environment.getExternalStorageDirectory();
    File file = new File(fileDir, path.substring("/QQBrowser".length(), path.length()));
    return file.exists() ? file.toString() : null;
}

测试通过机型: 魅族15,三星9300,mi6,oppoR9, 华为mate9

缺失代码自行补齐,
主要类代码FileUtil


其他问题

1. 文件类型设置

产品需求:

╔══════════════════════════════
║
║   上传文件       上传视频
║
╚══════════════════════════════

要求: 1. 点击视频选择mp4,2. 点击文件选择pdf、word等office文件。

在设置Intent的时候分为2种:

  • intent.setType("*/*")
    这种情况下,本意是想选择office文档。通过Intent.EXTRA_MIME_TYPES来限制文件类型,但是这种情况下,会出现第三方的文件管理器,而三方的一些情况下不会生效,所有文件都可以选择。

我做的是,通过观察MIME类型,我设置的是application/*第三方的文件管理图标隐藏掉了,只能通过系统的文件管理选择文件。

image
image
  • intent.setType("video/mp4);
  1. 这种会显示三方文件管理器,但是会过滤掉其他的文件,只有video类型的,如果有avi类型,那么还需要在onActivityResult中判断文件后缀名。
  2. 系统的文件管理器会生效,只能选择Intent.EXTRA_MIME_TYPES设置的类型。

2. 返回URI的问题

  • 从文件管理器选择文件,返回的URI是content://com.android.externalstorage.documents/document/primary/update/A5679B1.mp4
  • 从『视频』选择文件,返回的URI是content://com.android.providers.media.documents/document/video:5188

遇到的问题:

判断文件格式是否是我设置的类型,如果intent.setType("video/*");,但是只想要"mp4"格式的文件,那么在onActivityResult中通过返回的数据进行判断,前期想的是通过uri.getLastPathsegment(),判断文件的后缀名,但是后来测试遇到了第二种情况,从『视频』里选择到文件,这时返回URI不符合规则了,所以偷懒是不行的,只能通过转换,将源文件的名称,判断后缀名。

image

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容