Android文件管理器選擇文件爽彤,獲得文件路徑URI轉(zhuǎn)File

記一次文件上傳引發(fā)的血案适篙。

解決QQ瀏覽器com.tencent.mtt.fileprovider問題箫爷。

測試Demo


更新列表

日期 修改內(nèi)容
2019年7月2日 更新遇到的問題

前情描述:

使用系統(tǒng)文件管理器虎锚,選擇指定文件類型上傳窜护。

基礎(chǔ)知識
  • MIME
  • 調(diào)起文件管理器
  • 指定瀏覽位置(路徑轉(zhuǎn)URI)
  • 設(shè)置多種文件類型
  • URI轉(zhuǎn)路徑
踩坑
  • com.tencent.mtt.fileprovider 問題

1. MIME

MIME (Multipurpose Internet Mail Extensions) 是描述消息內(nèi)容類型的因特網(wǎng)標(biāo)準(zhǔn)。

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. 調(diào)起文件管理器

  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. 系統(tǒng)的相冊

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

3. 指定瀏覽位置(路徑轉(zhuǎn)URI)

跳轉(zhuǎn)到指定路徑下藏研,涉及到將路徑轉(zhuǎn)為URI蠢挡,考慮Android版本區(qū)別

/**
 * 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

<!--物理路徑相當(dāng)于Context.getFilesDir() + /path/-->
<files-path name="name" path="path" />

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

將文件路徑轉(zhuǎn)(使用微信下載目錄做測試)為URI后伐脖,設(shè)置到Intent中。

/**
 * 根據(jù)類型绎巨,加載文件選擇器
 */
private void chooseFileWithPath() {
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    //跳轉(zhuǎn)指定路徑场勤,如果路徑不存在和媳,切到sdcard
    //需要讀權(quán)限
    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);

特別注意:
指定目錄這種方式調(diào)起留瞳,在原生的管理器沒有其他注意的叹卷,但是如果用戶使用三方的管理器坪它,例如QQ瀏覽器往毡,那么在進(jìn)入到指定目錄下,不可以執(zhí)行返回操作懒震。
例如:
如果路徑設(shè)置的是/sdcard/tencent/MicroMsg/Download,在進(jìn)入download目錄下瓷炮,如果該目錄下沒有文件递宅,那么想返回到其上層目錄MicroMsg办龄,是不可以的。

4. 設(shè)置多種文件類型

通過intent.setType()方式設(shè)置的文件類型安接,只能生效一次盏檐,所以如果想只選擇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);
}

以上是準(zhǔn)備工作偶宫,調(diào)起文件管理器進(jìn)行選擇文件纯趋,對于一個Android開發(fā)者來說吵冒,以上步驟只是相當(dāng)于一小步,不要忘記適配亿汞。

接下來才是長征路留夜。

5. URI轉(zhuǎn)路徑

在這一步主要解決的是將返回的URI轉(zhuǎn)換為File图甜,大坑也往往就在這一步出現(xiàn)。

從網(wǎng)上能找到File轉(zhuǎn)File的代碼嚼摩,但是百度出來的內(nèi)容枕面,10篇有8篇是一樣的缚去,剩下2篇不能看易结。

但是這8篇中幾乎都是相同的,沒有解決一個至關(guān)重要的問題QQ文件管理器躏精。

也可能是不會用搜索引擎吧矗烛。

上代碼

/**
 * 專為Android4.4設(shè)計的從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)) {

        //一些三方的文件瀏覽器會進(jìn)入到這個方法中,例如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;
}

發(fā)現(xiàn)部手機(jī)會有第三方的文件管理器歪架,如ES牡拇,QQ等目前只接觸到這兩種穆律,不排除其他APP,相信大部分都是好同志剔蹋,但不限于鵝廠大佬泣崩。

通過QQ瀏覽器選擇到的文件洛口,則會報出不存在_data字段這個錯誤。

WTF

懵逼.jpg

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

這時候,前面搜索到的文章杀赢,幾乎都沒有解決這個問題脂崔。

鑒于水平有限梧喷,被坑了半天后。

通過分析uri.getPath();的值绊困。

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

寫下來如下代碼秤朗,如各位大佬有更好的解決方案,望轉(zhuǎn)告取视。

//判斷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;
}

測試通過機(jī)型: 魅族15作谭,三星9300奄毡,mi6,oppoR9, 華為mate9

缺失代碼自行補(bǔ)齊锐秦,
主要類代碼FileUtil


其他問題

1. 文件類型設(shè)置

產(chǎn)品需求:

╔══════════════════════════════
║
║   上傳文件       上傳視頻
║
╚══════════════════════════════

要求: 1. 點擊視頻選擇mp4酱床,2. 點擊文件選擇pdf、word等office文件昧捷。

在設(shè)置Intent的時候分為2種:

  • intent.setType("*/*")
    這種情況下罐寨,本意是想選擇office文檔鸯绿。通過Intent.EXTRA_MIME_TYPES來限制文件類型,但是這種情況下幔烛,會出現(xiàn)第三方的文件管理器饿悬,而三方的一些情況下不會生效,所有文件都可以選擇狡恬。

我做的是弟劲,通過觀察MIME類型姥芥,我設(shè)置的是application/*第三方的文件管理圖標(biāo)隱藏掉了,只能通過系統(tǒng)的文件管理選擇文件庸追。

image
image
  • intent.setType("video/mp4);
  1. 這種會顯示三方文件管理器淡溯,但是會過濾掉其他的文件咱娶,只有video類型的,如果有avi類型屈糊,那么還需要在onActivityResult中判斷文件后綴名喻喳。
  2. 系統(tǒng)的文件管理器會生效表伦,只能選擇Intent.EXTRA_MIME_TYPES設(shè)置的類型慷丽。

2. 返回URI的問題

  • 從文件管理器選擇文件,返回的URI是content://com.android.externalstorage.documents/document/primary/update/A5679B1.mp4
  • 從『視頻』選擇文件纲熏,返回的URI是content://com.android.providers.media.documents/document/video:5188

遇到的問題:

判斷文件格式是否是我設(shè)置的類型局劲,如果intent.setType("video/*");奶赠,但是只想要"mp4"格式的文件,那么在onActivityResult中通過返回的數(shù)據(jù)進(jìn)行判斷苹丸,前期想的是通過uri.getLastPathsegment()苇经,判斷文件的后綴名,但是后來測試遇到了第二種情況商模,從『視頻』里選擇到文件阻桅,這時返回URI不符合規(guī)則了兼都,所以偷懶是不行的,只能通過轉(zhuǎn)換趟章,將源文件的名稱,判斷后綴名宏侍。

image

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谅河,一起剝皮案震驚了整個濱河市绷耍,隨后出現(xiàn)的幾起案子褂始,更是在濱河造成了極大的恐慌描函,老刑警劉巖舀寓,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件互墓,死亡現(xiàn)場離奇詭異轰豆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)骂租,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門渗饮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宿刮,“玉大人僵缺,你說我怎么就攤上這事】某保” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵之景,是天一觀的道長。 經(jīng)常有香客問我满力,道長油额,這世上最難降的妖魔是什么桐磁? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任我擂,我火速辦了婚禮校摩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阶淘。我一直安慰自己衙吩,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布溪窒。 她就那樣靜靜地躺著坤塞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪澈蚌。 梳的紋絲不亂的頭發(fā)上摹芙,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音宛瞄,去河邊找鬼浮禾。 笑死,一個胖子當(dāng)著我的面吹牛份汗,可吹牛的內(nèi)容都是我干的盈电。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼荡澎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了或衡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柄瑰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年杨蛋,在試婚紗的時候發(fā)現(xiàn)自己被綠了曙寡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡镀琉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钓试,到底是詐尸還是另有隱情,我是刑警寧澤挽鞠,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布狮杨,位于F島的核電站到忽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏翩迈。R本人自食惡果不足惜喂链,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望迟杂。 院中可真熱鬧,春花似錦火架、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吮炕。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拂盯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人两嘴。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像贰您,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容