利用 Android 系统原生 API 实现分享功能(2),androidstudio开发语言

takePhotoIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);

if (takePhotoIntent.resolveActivity(getPackageManager()) == null) {
Toast.makeText(this, “当前系统没有可用的相机应用”, Toast.LENGTH_SHORT).show();
return;
}

String fileName = “TEMP_” + System.currentTimeMillis() + “.jpg”;
File photoFile = new File(FileUtil.getPhotoCacheFolder(), fileName);

// 7.0 和以上版本的系统要通过 FileProvider 创建一个 content 类型的 Uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
currentTakePhotoUri = FileProvider.getUriForFile(this, getPackageName() + “.fileProvider”, photoFile);
takePhotoIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|);
} else {
currentTakePhotoUri = Uri.fromFile(photoFile);
}

//将拍照结果保存至 outputFile 的Uri中,不保留在相册中
takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, currentTakePhotoUri);
startActivityForResult(takePhotoIntent, TAKE_PHOTO_REQUEST_CODE);
}

// 调用系统相机进行拍照与上面通过文件选择器获得文件 uri 的方式类似
// 在 onActivityResult 进行回调处理,此时 Uri 是自定义 FileProvider 中指定的,注意与文件选择器获取的系统返回 Uri 的区别。

如果用到了 FileProvider 就要注意跟系统 ContentProvider 返回 Uri 的区别,比如我们在 Manifest 中对 FileProvider 配置 android:authorities="com.xx.xxx.fileProvider" 属性,那这时系统返回的 Uri 格式就变成了:content://com.xx.xxx.fileProvider...,对于这种类型的 Uri 我们姑且叫自定义 FileProvider 返回的 Uri

3. 通过文件的路径获取到的 Uri

这其实不能单独作为一种文件 Uri 类型,但这是很常见的一种调用场景,所以单独拿出来进行说明。

我们调用 new File(String path) 时需要传入指定的文件路径,这个绝对路径通常是:/storage/emulated/0/... 这种样式,那么如何把一个文件路径变成一个文件 Uri 的形式?要回答这个问题,其实就需要对分享文件进行处理。

分享文件 Uri 的处理

处理访问权限

前面提到了文件 Uri 的三种来源,对应不同类型处理方式也不同,不然你最先遇到的问题就是:

java.lang.SecurityException: Uid xxx does not have permission to uri 0 @ content://com.android.providers…

这是由于对系统返回的 Uri 缺失访问权限导致,所以要对应用进行临时访问 Uri 的授权才行,不然会提示权限缺失。

对于要分享系统返回的 Uri 我们可以这样进行处理:

// 1. 可以对发起分享的 Intent 添加临时访问授权
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

// 2. 也可以这样:由于不知道最终用户会选择哪个app,所以授予所有应用临时访问权限
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
List resInfoList = activity.getPackageManager().queryIntentActivities(shareIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
activity.grantUriPermission(packageName, shareFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}

处理 FileProvider 返回 Uri

需要注意的是对于自定义 FileProvider 返回 Uri 的处理,即使是设置临时访问权限,但是分享到第三方应用也会无法识别该 Uri

典型的场景就是,我们如果把自定义 FileProvider 的返回的 Uri 设置分享到微信或 QQ 之类的第三方应用时提示文件不存在,这是因为他们无法识别该 Uri

关于这个问题的处理其实跟下面要说的把文件路径变成系统返回的 Uri 一样,我们只需要把自定义 FileProvider 返回的 Uri 变成第三方应用可以识别系统返回的 Uri 就行了。

创建 FileProvider 时需要传入一个 File 对象,所以直接可以知道文件路径,那就把问题都转换成了:如何通过文件路径获取系统返回的 Uri

通过文件路径获取系统返回的 Uri

对于 Android 7.0 以下版本的系统,要回答这个问题很简单:

Uri uri = Uri.fromFile(file);

但在 Android 7.0 及以上系统处理起来就要繁琐许多,下面就来说说如何在不同系统版本下的进行适配。下面的 getFileUri 方法实现了通过传入的 File 对象和类型来查询系统 ContentProvider 的方式获取相应的文件 Uri

public static Uri getFileUri (Context context, @ShareContentType String shareContentType, File file){

if (context == null) {
Log.e(TAG,“getFileUri current activity is null.”);
return null;
}

if (file == null || !file.exists()) {
Log.e(TAG,“getFileUri file is null or not exists.”);
return null;
}

Uri uri = null;

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
uri = Uri.fromFile(file);
} else {

if (TextUtils.isEmpty(shareContentType)) {
shareContentType = “/”;
}

switch (shareContentType) {
case ShareContentType.IMAGE :
uri = getImageContentUri(context, file);
break;
case ShareContentType.VIDEO :
uri = getVideoContentUri(context, file);
break;
case ShareContentType.AUDIO :
uri = getAudioContentUri(context, file);
break;
case ShareContentType.File :
uri = getFileContentUri(context, file);
break;
default: break;
}
}

if (uri == null) {
uri = forceGetFileUri(file);
}

return uri;
}

private static Uri getFileContentUri(Context context, File file) {
String volumeName = “external”;
String filePath = file.getAbsolutePath();
String[] projection = new String[]{MediaStore.Files.FileColumns._ID};
Uri uri = null;

Cursor cursor = context.getContentResolver().query(MediaStore.Files.getContentUri(volumeName), projection,
MediaStore.Images.Media.DATA + "=? ", new String[] { filePath }, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID));
uri = MediaStore.Files.getContentUri(volumeName, id);
}
cursor.close();
}

return uri;
}

private static Uri getImageContentUri(Context context, File imageFile) {
String filePath = imageFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID }, MediaStore.Images.Media.DATA + "=? ",
new String[] { filePath }, null);
Uri uri = null;

if (cursor != null) {
if (cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse(“content://media/external/images/media”);
uri = Uri.withAppendedPath(baseUri, “” + id);
}

cursor.close();
}

if (uri == null) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, filePath);
uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}

return uri;
}

private static Uri getVideoContentUri(Context context, File videoFile) {
Uri uri = null;
String filePath = videoFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Video.Media._ID }, MediaStore.Video.Media.DATA + "=? ",
new String[] { filePath }, null);

if (cursor != null) {
if (cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse(“content://media/external/video/media”);
uri = Uri.withAppendedPath(baseUri, “” + id);
}

cursor.close();
}

if (uri == null) {
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, filePath);
uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
}

return uri;
}

private static Uri getAudioContentUri(Context context, File audioFile) {
Uri uri = null;
String filePath = audioFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Audio.Media._ID }, MediaStore.Audio.Media.DATA + "=? ",
new String[] { filePath }, null);

if (cursor != null) {
if (cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse(“content://media/external/audio/media”);
uri = Uri.withAppendedPath(baseUri, “” + id);
}

cursor.close();
}
if (uri == null) {
ContentValues values = new ContentValues();
values.put(MediaStore.Audio.Media.DATA, filePath);
uri = context.getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
}

return uri;
}

private static Uri forceGetFileUri(File shareFile) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
@SuppressLint(“PrivateApi”)
Method rMethod = StrictMode.class.getDeclaredMethod(“disableDeathOnFileUriExposure”);
rMethod.invoke(null);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}

return Uri.parse(“file://” + shareFile.getAbsolutePath());
}

其中 forceGetFileUri 方法是通过反射实现的,Android 7.0 开始不允许 file:// Uri 的方式在不同的 App 间共享文件,但是如果换成 FileProvider 的方式依然是无效的,我们可以通过反射把该检测干掉。

通过 File Path 转成 Uri 的方式,我们最终统一了调用系统分享时传入内容 Uri 的三种不同场景,最终全部转换为传递系统返回的 Uri,让第三方应用能够正常的获取到分享内容。

最终实现

Share2 按照上述方法进行了具体实施,可以通过下面的方式进行集成:

// 添加依赖
compile ‘gdut.bsx:share2:0.9.0’

根据 FilePath 获取 Uri

public Uri getShareFileUri() {
return FileUtil.getFileUri(this, ShareContentType.FILE, new File(filePath));;
}

最后,面试前该准备哪些资源复习?

其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

这里再分享一下我面试期间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)

《Android开发七大模块核心知识笔记》

利用 Android 系统原生 API 实现分享功能(2),androidstudio开发语言

利用 Android 系统原生 API 实现分享功能(2),androidstudio开发语言

《960全网最全Android开发笔记》

利用 Android 系统原生 API 实现分享功能(2),androidstudio开发语言

《379页Android开发面试宝典》

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

ndroid开发面试宝典》**

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

上一篇:web开发


下一篇:联想电脑打开pdf文件总是会跳转到 https://www.baidu.com/?tn=02003390_62_hao_pg 网址