利用 Android 系统原生 API 实现分享功能(2)

看过上一篇文章的同学应该知道,要调用 Android 系统内建的分享功能,主要有三步流程:

  • 创建一个 Intent ,指定其 ActionIntent.ACTION_SEND,表示要创建一个发送指定内容的隐式意图。

  • 然后指定需要发送的内容和类型,即设置分享的文本内容或文件的 Uri ,以及声明文件的类型,便于支持该类型内容的应用打开。

  • 最后向系统发送隐式意图,开启系统分享选择器,分享完成后收到结果返回。

更多相关内容请参考上一篇,这里就不再重复赘述了。


知道大致的实现流程后,其实只要解决下面几个问题后就可以具体实施了。

确定要分享的内容类型

分享的内容类型,这其实是直接决定了最终的实现形态。我们知道常见的使用场景中,是为了在应用间分享图片和一些文件,而对于那些只是分享文本的产品而言,两者实现起来要考虑的问题完全不同。

所以为了解决这个问题,我们可以预先定好支持的分享内容类型,针对不同类型可以进行不同的处理。

@StringDef({ShareContentType.TEXT, ShareContentType.IMAGE, ShareContentType.AUDIO, ShareContentType.VIDEO, ShareContentType.File})
@Retention(RetentionPolicy.SOURCE)
@interface ShareContentType {
/**

  • Share Text
    */
    final String TEXT = “text/plain”;

/**

  • Share Image
    /
    final String IMAGE = "image/
    ";

/**

  • Share Audio
    /
    final String AUDIO = "audio/
    ";

/**

  • Share Video
    /
    final String VIDEO = "video/
    ";

/**

  • Share File
    /
    final String File = "
    /*";
    }`

在 Share2 中,一共定义了 5 种类别的分享内容,基本能覆盖常见的使用场景。在调用分享接口时可以直接指定内容类型,比如像文本、图片、音视频、以及其他各种类型文件。

确定分享的内容来源

对于不同类型的内容,可能会有不同的来源。比如文本可能就只是一个字符串对象。而对于分享图片或其他文件,我们通常需要一个 Uri 来标识一个资源。这其实就引出了在具体实施时的一个关键问题:如何获取被分享文件的 Uri,并且这个 Uri 可以被接收的应用处理?

再把这个问题进一步细化,转化为需要解决的具体问题时就是:

  1. 如何获取要分享内容文件的 Uri
  2. 如何才能让接收方也能够根据 Uri 获取到文件?

要回答上面这些问题,我们先来看看分享文件的来源。通常我们在应用中获取一个文件的具体方式有:

  • 用户通过打开文件选择器或图片选择器来获取一个指定的文件;
  • 用户通过拍照或录制音视频来获取一个媒体文件;
  • 用户通过下载或直接通过本地文件路径来获取一个文件。

那下面我们就按照获取文件来源把文件的 Uri 划分为下面几种类型:

1. 系统返回的 Uri

常见场景:通过文件选择器获取一个文件的 Uri

private static final int REQUEST_FILE_SELECT_CODE = 100;
private @ShareContentType String fileType = ShareContentType. File;

/**

  • 打开文件管理选择文件
    /
    private void openFileChooser() {
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("
    /*");
    intent.addCategory(Intent.CATEGORY_OPENABLE);

try {
startActivityForResult(Intent.createChooser(intent, “Choose File”), REQUEST_FILE_SELECT_CODE);
} catch (Exception ex) {
// not install file manager.
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_SELECT_CODE && resultCode == RESULT_OK) {
// 获取到的系统返回的 Uri
Uri shareFileUrl = data.getData();
}
}

通过这种方式获取到的 Uri 是由系统 ContentProvider 返回的,在 Android 4.4 之前的版本和之后的版本有较大的区别,我们后面再说怎么处理。只要先记住这种系统返回给我们的 Uri 就行了。

系统返回的文件 Uri 中的一些常见样式: content://com.android.providers.media.documents… content://com.android.providers.downloads… content://media/external/images/media/… content://com.android.externalstorage.documents…

2. 自定义 FileProvider 返回的 Uri

常见场景:比如调用系统相机进行拍照或录制音视频,要传入一个生成目标文件的 Uri,从 Android 7.0 开始我们需要用到 FileProvider 来实现。

private static final int REQUEST_FILE_SELECT_CODE = 100;
/**

  • 打开系统相机进行拍照
    */
    private void openSystemCamera() {
    //调用系统相机
    Intent takePhotoIntent = new Intent();
    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();
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

如何做好面试突击,规划学习方向?

面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。

学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。

同时我还搜集整理2020年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节

image

在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。

image

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

节**。

[外链图片转存中…(img-4AjsqCPz-1712380758115)]

在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。

[外链图片转存中…(img-3fYj8HPa-1712380758115)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 16
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值