記一次文件上傳引發(fā)的血案适篙。
解決QQ瀏覽器com.tencent.mtt.fileprovider問題箫爷。
更新列表
日期 | 修改內(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)起文件管理器
-
所有類型文件
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/*”);
-
系統(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
文件如下:
<!--物理路徑相當(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)的文件管理選擇文件庸追。
intent.setType("video/mp4);
- 這種會顯示三方文件管理器淡溯,但是會過濾掉其他的文件咱娶,只有video類型的,如果有avi類型屈糊,那么還需要在
onActivityResult
中判斷文件后綴名喻喳。 - 系統(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)換趟章,將源文件的名稱,判斷后綴名宏侍。