萌新处文,水文一篇,大佬轻拍,大佬轻拍,大佬轻拍
需求比较少见,简单来说是可以在app内打开系统文件选择器,取到对应文件并上传(我这边为Excel,比较少)。同时需要支持在其他应用(文件选择器类)中选择发送/分享或者其他应用打开完成后续上传工作。
知识点在于
-
添加自己应用在打开/分享列表
-
兼容大部分返回的uri
Android用其他方式打开文件
此功能较为简单。
反编译市场上有此功能的软件,查看其对应xml配置可得到。下为对xls和xlsx文件响应。
ContentType 类型可百度找到
我的参考链接为:blog.csdn.net/u013749540/…
<!--分享/发送-->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
<data android:mimeType="application/vnd.ms-excel" />
<data android:mimeType="text/comma-separated-values" />
</intent-filter>
<!--在其他应用打开-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
<data android:mimeType="application/vnd.ms-excel" />
<data android:mimeType="text/comma-separated-values" />
</intent-filter>
另外有如声明具体功能的情况,如保存到XX,收藏到XX,可添加
```
<activity-alias
android:name="com.ucpro.file"
android:icon="@mipmap/launcher_ic"
android:label="导入到XX"
android:resizeableActivity="false"
android:targetActivity=".ui.activity.HomeActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*.*" />
</intent-filter>
```
兼容大部分返回的Uri的工具类
我目前看到的不同机子不用情况下返回的uri有:
/external/file/11611
/external_files/Download/xxxx
content://com.android.providers.media.documents/document/document:127686
content://com.android.providers.downloads.documents/document/msf:3A127686
/root/storage/emulated/0/android/data/xxxx
.......
另外,Android11 在QQ浏览器或者微信会话使用没法使用第三方打开,其路径为
/QQBrowser/Android/data/com.tencent.mtt/files/.ReaderTemp/thrdcall/contenturi/xxxx
/external/Android/data/com.tencent.mm/MicroMsg/Download/xxxx
Android11储存更新:developer.android.google.cn/about/versi…
所以无法获取到Android/data/下的文件,目前解决方式为引导用户操作😊
有大佬能适配这种情况的,求指点下
根据Uri获取真实FilePath参考实现:stackoverflow.com/questions/1…
工具类代码:
public class FilePathUtils {
/**
* <p>
* 获取完整文件名(包含扩展名)
*
* @param filePath
* @return
*/
public static String getFilenameWithExtension(String filePath) {
if (filePath == null || filePath.length() == 0) {
return "";
}
int lastIndex = filePath.lastIndexOf(File.separator);
String filename = filePath.substring(lastIndex + 1);
return filename;
}
/**
* 判断文件路径的文件名是否存在文件扩展名 eg: /external/images/media/2283
*
* @param filePath
* @return
*/
public static boolean isFilePathWithExtension(String filePath) {
String filename = getFilenameWithExtension(filePath);
return filename.contains(".");
}
/**
* 判断文件路径的文件名是否存在文件扩展名 eg: /external/images/media/2283
*
* @param context
* @param uri
* @return
*/
@RequiresApi(api = Build.VERSION_CODES.Q)
public static String getPathFromUri(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
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];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
/* 文件浏览器的下载返回的uri为
* content://com.android.providers.downloads.documents/document/msf:3A127686
* Android10以上,getDataColumn中的uri为 MediaStore.Downloads.EXTERNAL_CONTENT_URI
*/
if (id.startsWith("msf:")) {
final String[] split = id.split(":");
final String selection = "_id=?";
final String[] selectionArgs = new String[]{split[1]};
return getDataColumn(context, MediaStore.Downloads.EXTERNAL_CONTENT_URI, selection, selectionArgs);
} else {
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
/*
* 由于是文档类型,所以记得加上document判断,具体的MediaStore项我没有找到,有大佬知道的话,求指点下
* */
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
} else if ("document".equals(type)) {
contentUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri)) {
return uri.getLastPathSegment();
}
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}
工具类的使用:
文件浏览器回调
startActivityForResult(intent, (resultCode, data) -> {
if (resultCode == Activity.RESULT_OK && data != null) {
Uri uri = data.getData();
if (uri != null) {
File file;
/*
*判断有无文件拓展名,有则直接调用 http://blankj.com 大神的工具箱,无则表示为content形式调用自己的工具类
* */
if (FilePathUtils.isFilePathWithExtension(uri.getPath())) {
file = UriUtils.uri2File(uri);
} else {
file = FileUtils.getFileByPath(FilePathUtils.getPathFromUri(getContext(), uri));
}
uploadFile(file);
}
}
});
}
发送/分享,打开也差不多就不反复贴了。
@SuppressLint("ObsoleteSdkInt")
@RequiresApi(api = Build.VERSION_CODES.Q)
private void getShareFromOthers() {
//分享/发送
Intent shareIntent = getIntent();
String action = shareIntent.getAction();
String type = shareIntent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet".equals(type) || "application/vnd.ms-excel".equals(type)) {
Uri uri2 = shareIntent.getParcelableExtra(Intent.EXTRA_STREAM);
if (uri2 != null) {
String host = uri2.getHost();
String dataString = shareIntent.getDataString();
String path = uri2.getPath();
String path1 = uri2.getEncodedPath();
/* 华为文件管理器根目录会以/root/storage/emulated/0/方式传,手动剔除/root*/
if (path.startsWith(getString(R.string.file_root))){
path=path.substring(5);
}
if (excelShareInput) {
File file;
/* 判断是否文件是否有后缀*/
if(FilePathUtils.isFilePathWithExtension(path)){
/* 小米Android11返回为/external_files/xxx,将其替换*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path = path.replace(getString(R.string.file_storage), PathUtils.getExternalStoragePath());
file = FileUtils.getFileByPath(path);
Timber.d("file = %s", file);
} else {
file = FileUtils.getFileByPath(path);
}
}else {
/* 由content获取真实路径*/
file =FileUtils.getFileByPath(FilePathUtils.getPathFromUri(getContext(),uri2));
}
}
}
}
}
写到这里。文件用其他方式打开或者分享打开添加自己App和App内找对文件上传算是完事了。当然后面如果有更多的适配方案还会继续补充。💖
10.19补充
对微信、QQ等第三方APP做兼容
找了一圈,发现了本站大佬关于流的处理,解决了这个问题,整合再工具类里直接使用即可。
大佬链接传送门:超猩猩小队长 # Android 接收微信、QQ其他应用打开,第三方分享
整合位置:
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri)) {
return uri.getLastPathSegment();
}else {
//此方法为超猩猩小队长 [https://juejin.cn/post/6844904104389509134],全拷贝过来即可
return getFilePathFromContentUri(context,uri);
}
// return getDataColumn(context, uri, null, null);
}
目前工具类东部西凑较为混乱,后续整理好后再更新。