区块链技术博客
www.b2bchain.cn

在 Android 中使用分享功能求职学习资料

本文介绍了在 Android 中使用分享功能求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

前言

在 在 Android 中使用存储功能 (1) 中我们阐述了如何使用应用的专属目录,而专属目录的目的是不愿意被其他人任意访问的,尤其是在分区存储上。但是如果我们有部分数据需要分享给其他人怎么办?比如如何让其他应用可以访问我们应用专属目录中的某个文件?本文章就来详细聊聊系统的分享功能。

数据的共享有主动的被动的,主动的去分享数据,或者是被动的提供一个类似于文件选择器那样的功能供其他应用去选择我们共享出来的文件。

现在的应用大多数都有分享功能,分享一张图片,一个网址给朋友,分享到微信,微博等。而且许多设计师都喜欢自定义分享面板,于是开发者们在做分享功能时,就有了如下选择:

  1. 集成第三方分享组件,比如友盟分享等。
  2. 自己对接各大平台的分享 SDK。
  3. 使用系统本身的提供的分享功能。

第一种比较方便快捷,不过就是集成了第三方的东西,对数据敏感的还是慎选。
第二种方式比较麻烦,不过分享目标少的情况下倒是问题不大,可以实现一些平台提供的专有功能。
而第三种,如果对 UI 没有特殊的要求,设计师愿意让步的话,更加方便快捷的方式是选择使用系统原生的分享组件,而且 UI 也挺漂亮的。

不管是哪种方式,本质上都是 Android 系统的数据互相共享,下面我们就来看看,如何使用系统提供的分享功能共享数据给其他应用以及如何接收别人分享的数据。

分享数据给其他应用

通常最常见的就是分享图片,分享视频,还有就是分享一个网址。可以理解为分享一段内存数据,一个文件。

1. 分享一段文本数据

Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, "要分享的文本");  // Android 10 开始,可以通过 Intent.EXTRA_TITLE 添加描述信息,ClipData 添加缩略图 sendIntent.putExtra(Intent.EXTRA_TITLE, "我是标题"); sendIntent.setClipData(ClipData.newUri(MyApp.getApp().getContentResolver(), "我是缩略图", uri));  // 设置分享的类型 sendIntent.setType("text/plain");  Intent shareIntent = Intent.createChooser(sendIntent, null); startActivity(shareIntent);

非常简单,startActivity 后就会弹出如下对话框:

在 Android 中使用分享功能

2. 如何得知用户选择分享到哪里

有时候我们需要统计用户点击了分享面板中的哪个应用,可以通过 IntentSender。如下:

Intent intent = new Intent(); intent.setAction("com.imyyq.intent.sender"); PendingIntent pi = PendingIntent.getBroadcast(getContext(), 10000,         intent,         PendingIntent.FLAG_UPDATE_CURRENT);  Intent shareIntent = Intent.createChooser(sendIntent, null, pi.getIntentSender());

在 createChooser 时,传入 IntentSender。然后在广播中接收:

public class IntentSenderBroadcastReceiver extends BroadcastReceiver {     @Override     public void onReceive(Context context, Intent intent) {         ComponentName extra = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT);         Toast.makeText(context, "点击了 " + extra.getPackageName(), Toast.LENGTH_SHORT).show();     } }

如此一来当我们点击了分享面板中的应用,就可以在广播中收到是选择了谁了。

3. 其他的 Extra

Intent 中定义了很多的 Extra,比如有:

  • 电子邮件相关的:EXTRA_EMAIL、EXTRA_CC、EXTRA_BCC、EXTRA_SUBJECT
  • 排除某些分享目标:EXTRA_EXCLUDE_COMPONENTS
  • 添加自定义目标:EXTRA_CHOOSER_TARGETS

其他的可以自行查阅 Intent 文档。

4. 分享多份数据

除了 Intent.ACTION_SEND 分享一份数据,还可以用 Intent.ACTION_SEND_MULTIPLE 分享多份。如下示例:

ArrayList<CharSequence> imageUris = new ArrayList<>(); imageUris.add("我是文本 1"); imageUris.add("我是文本 2");  Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE); shareIntent.putCharSequenceArrayListExtra(Intent.EXTRA_STREAM, imageUris); shareIntent.setType("text/*"); startActivity(Intent.createChooser(shareIntent, "分享多份数据"));

当然,不仅可以分享多份文本,还可以分享多张图片,多个文件等。下面会专门说分享文件的部分。

5. 分享二进制数据

最常见的就是图片了,示例如下:

Uri fileUri = Uri.fromFile(file);  Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND);  shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION         | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); shareIntent.setType("image/jpeg"); requireActivity().startActivity(Intent.createChooser(shareIntent, null));

上述示例需要接收方具有权限才可以接收到,不仅仅是图片,任何二进制文件都可以通过上述示例分享,但是如果接收方没有相应权限,就无法接收。而且从 Android 7.0 开始,使用 File Uri 可能会抛出 FileUriExposedException 异常。

因此,使用 Uri.fromFile,这不是最佳的做法,分享文件应该使用 FileProvider,或者 MediaStore,前者等会说,而后者不仅仅可以用来存储媒体文件,MediaStore.Files 还可以用来存储文件的。

分享媒体或文档给其他应用

分享文件的最佳做法,就是使用 FileProvider,FileProvider 是 ContentProvider 的一个特殊子类,它通过为文件创建 content:// 而不是 file:/// 在应用之间进行文件的安全共享。

FileProvider 使用临时读写访问权限,授予其他应用临时性的权限,当 Uri 传给某应用程序后,只要它的 Activity 或 Service 还在运行,该权限就一直可用,直到不运行了,该权限下次就不可用了。

使用 content 类型的 Uri 比 file 类型的要安全的多。因此在应用间分享文件,使用 FileProvider 就成了必然的选择。

下面来看看如何使用。

1. 在 Manifest 中注册

首先 FileProvider 是个 ContentProvider,四大组件是必须注册的,如下:

<manifest>     ...     <application>         ...         <provider             android:name="androidx.core.content.FileProvider"             android:authorities="<package name>.fileprovider"             android:exported="false"             android:grantUriPermissions="true">             <meta-data                 android:name="android.support.FILE_PROVIDER_PATHS"                 android:resource="@xml/file_paths" />         </provider>         ...     </application> </manifest>

属性解释:

  • android:name 属性设置为 androidx.core.content.FileProvider。这是固定的。
  • android:authorities 属性设置为 URI 权限,通常是包名 + fileprovider。
  • android:exported 属性设置为 false,因为 FileProvider 不需要公开。
  • android:grantUriPermissions 属性设置为 true,允许授予对组件的临时访问权限。
  • file_paths 是用来声明对应的共享路径的,下面会说到。

当然,你要是想扩展功能,也可以继承 FileProvider。

2. 告知 FileProvider 你要分享的文件的目录

在 res 资源目录创建 xml 目录,再创建 file_paths.xml,内容如下:

<?xml version="1.0" encoding="utf-8"?> <!-- name 属性:是为了隐藏 path 属性而存在的,也就是说,在 Uri 中,用它来表示 path path 属性:真实的目录名称 --> <paths xmlns:android="http://schemas.android.com/apk/res/android">     <!--以下3个是外部专属空间的-->     <!--Context.getExternalMediaDirs().API 21+-->     <external-media-path         name="external-media-path"         path="img/"         />     <!--Context.getExternalCacheDir()-->     <external-cache-path         name="external-cache-path"         path="img/"         />     <!--Context.getExternalFilesDir("img")-->     <external-files-path         name="external-files-path"         path="img/"         />      <!-- 外部存储根目录 -->     <!--Environment.getExternalStorageDirectory().-->     <external-path         name="external-path"         path="img/"         />      <!--以下2个是内部专属空间的-->     <!--getCacheDir().-->     <cache-path         name="cache-path"         path="img/"         />     <!--Context.getFilesDir().-->     <files-path         name="files-path"         path="img/"         /> </paths>

如上注释也解释的很清楚了,所有的可配置标签也列出来了,对应的目录也列出来了。

比如上述的 files-path 标签,就是 Context.getFilesDir() 得到的路径加上 path 定义的 img 就是实际目录了,通过 在 Android 中使用存储功能 (1) 我们知道,Context.getFilesDir() 路径硬编码是:/data/data/包名/files,因此上述 files-path 标签声明的绝对路径就是:/data/data/包名/files/img/

需要注意的是,这里配置的是目录,而不是文件。当然,你可以指定多个相同的标签,指向不同的子目录。

还有一个要注意的点,name 的定义,不可以和其他标签的一致,是唯一的,另外,最好是定义成看不出具体目录的形式。而 path 的定义,只要不同标签的,可以定义成一样的。这也很好理解,不同目录的子目录可以同名,但是 name 是唯一标志该目录的,所以自然不能重复。

3. 生成 Uri

经过上面的步骤,我们已经声明了要共享的路径,我们拿上述的 external-files-path 举例,即目录:/sdcard/Android/data/包名/files/img/,现在假设这个目录下有个文件 test.jpg,我们要分享出去,首先要生成一个 Uri,如下:

File externalFilesDir = requireActivity().getExternalFilesDir("img"); File newFile = new File(externalFilesDir, "test.jpg"); // 我们在 assets 目录中准备了一张图,用来分享的,把它拷贝到指定的目录 if (!newFile.exists()) {     Utils.copyAssetFile(requireActivity(), "img/test.jpg", newFile); }  // 生成 uri Uri contentUri = FileProvider.getUriForFile(requireActivity(), requireActivity().getPackageName() + ".fileprovider", newFile);

得到的 Uri 如下所示:

content://com.imyyq.mediastoretest.fileprovider/external-files-path/test.jpg

uri 解释:

  • com.imyyq.mediastoretest.fileprovider 是在 Manifest 中配置好的 android:authorities
  • external-files-path,是 xml 中配置的 name,注意:不是 path 哦
  • test.jpg 就是 path 目录下对应的文件了。

拿到这个 Uri 就可以用来分享啦。

4. Uri 权限的问题

Intent.createChooser 方法,是主动发起分享的,该方法中,给 Intent 设置了 FLAG_GRANT_READ_URI_PERMISSION,所以接收者是不需要具备声明任何权限就对该文件具备临时可读权限。

如果你是要做一个文件选择器,别人调起你的应用,选择文件后,你通过 setResult 返回一个 Intent,别忘了给你的 Intent 设置临时权限:

Intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

否则对方是无权使用你的文件的。


FileProvider 能共享文件的原理就是使用 ContentProvider 机制,ContentProvider 本身就是用来跨进程跨应用通信的,在 Android 中的应用随处可见,比如查阅联系人,图库,短信等。

接收其他应用分享的内容

上面我们所描述的是如何分享数据给其他的应用,比如分享给微信或 QQ。那么如果我们需要类似于微信和 QQ 这样需要接收其他应用分享的数据呢?我们来看看怎么做。

了解过 Intent 的就会知道,应用是可以对其他应用发起的 action 和 intent-filter 进行过滤的,如上面发起的 action:Intent.ACTION_SENDIntent.ACTION_SEND_MULTIPLE,只需要在我们的清单文件中指定 intent-filter,当用户发起分享时,应用即可出现在系统的分享面板中,如下:

<activity android:name=".TestActivity" >     <intent-filter>         <action android:name="android.intent.action.SEND" />         <category android:name="android.intent.category.DEFAULT" />         <data android:mimeType="image/*" />     </intent-filter>     <intent-filter>         <action android:name="android.intent.action.SEND" />         <category android:name="android.intent.category.DEFAULT" />         <data android:mimeType="text/plain" />     </intent-filter>     <intent-filter>         <action android:name="android.intent.action.SEND_MULTIPLE" />         <category android:name="android.intent.category.DEFAULT" />         <data android:mimeType="image/*" />     </intent-filter> </activity>

action 指定了可以响应 Intent.ACTION_SEND_MULTIPLE 和 Intent.ACTION_SEND,而 mimeType 则声明了可以接收文本和所有格式的图片。mimeType 你也可以设置成:“/”,这样其实就是在声明应用可以处理任何数据了,除非你真的可以处理全部的数据,否则还是只声明你可以处理的数据。

当用户在分享面板中选择了你的应用后,数据就会发到 TestActivity 上,那么如何获取数据呢?可以通过 getIntent,如下:

“`java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityReceiverShareBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

前言

在 在 Android 中使用存储功能 (1) 中我们阐述了如何使用应用的专属目录,而专属目录的目的是不愿意被其他人任意访问的,尤其是在分区存储上。但是如果我们有部分数据需要分享给其他人怎么办?比如如何让其他应用可以访问我们应用专属目录中的某个文件?本文章就来详细聊聊系统的分享功能。

数据的共享有主动的被动的,主动的去分享数据,或者是被动的提供一个类似于文件选择器那样的功能供其他应用去选择我们共享出来的文件。

现在的应用大多数都有分享功能,分享一张图片,一个网址给朋友,分享到微信,微博等。而且许多设计师都喜欢自定义分享面板,于是开发者们在做分享功能时,就有了如下选择:

  1. 集成第三方分享组件,比如友盟分享等。
  2. 自己对接各大平台的分享 SDK。
  3. 使用系统本身的提供的分享功能。

第一种比较方便快捷,不过就是集成了第三方的东西,对数据敏感的还是慎选。
第二种方式比较麻烦,不过分享目标少的情况下倒是问题不大,可以实现一些平台提供的专有功能。
而第三种,如果对 UI 没有特殊的要求,设计师愿意让步的话,更加方便快捷的方式是选择使用系统原生的分享组件,而且 UI 也挺漂亮的。

不管是哪种方式,本质上都是 Android 系统的数据互相共享,下面我们就来看看,如何使用系统提供的分享功能共享数据给其他应用以及如何接收别人分享的数据。

分享数据给其他应用

通常最常见的就是分享图片,分享视频,还有就是分享一个网址。可以理解为分享一段内存数据,一个文件。

1. 分享一段文本数据

Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, "要分享的文本");  // Android 10 开始,可以通过 Intent.EXTRA_TITLE 添加描述信息,ClipData 添加缩略图 sendIntent.putExtra(Intent.EXTRA_TITLE, "我是标题"); sendIntent.setClipData(ClipData.newUri(MyApp.getApp().getContentResolver(), "我是缩略图", uri));  // 设置分享的类型 sendIntent.setType("text/plain");  Intent shareIntent = Intent.createChooser(sendIntent, null); startActivity(shareIntent);

非常简单,startActivity 后就会弹出如下对话框:

在 Android 中使用分享功能

2. 如何得知用户选择分享到哪里

有时候我们需要统计用户点击了分享面板中的哪个应用,可以通过 IntentSender。如下:

Intent intent = new Intent(); intent.setAction("com.imyyq.intent.sender"); PendingIntent pi = PendingIntent.getBroadcast(getContext(), 10000,         intent,         PendingIntent.FLAG_UPDATE_CURRENT);  Intent shareIntent = Intent.createChooser(sendIntent, null, pi.getIntentSender());

在 createChooser 时,传入 IntentSender。然后在广播中接收:

public class IntentSenderBroadcastReceiver extends BroadcastReceiver {     @Override     public void onReceive(Context context, Intent intent) {         ComponentName extra = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT);         Toast.makeText(context, "点击了 " + extra.getPackageName(), Toast.LENGTH_SHORT).show();     } }

如此一来当我们点击了分享面板中的应用,就可以在广播中收到是选择了谁了。

3. 其他的 Extra

Intent 中定义了很多的 Extra,比如有:

  • 电子邮件相关的:EXTRA_EMAIL、EXTRA_CC、EXTRA_BCC、EXTRA_SUBJECT
  • 排除某些分享目标:EXTRA_EXCLUDE_COMPONENTS
  • 添加自定义目标:EXTRA_CHOOSER_TARGETS

其他的可以自行查阅 Intent 文档。

4. 分享多份数据

除了 Intent.ACTION_SEND 分享一份数据,还可以用 Intent.ACTION_SEND_MULTIPLE 分享多份。如下示例:

ArrayList<CharSequence> imageUris = new ArrayList<>(); imageUris.add("我是文本 1"); imageUris.add("我是文本 2");  Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE); shareIntent.putCharSequenceArrayListExtra(Intent.EXTRA_STREAM, imageUris); shareIntent.setType("text/*"); startActivity(Intent.createChooser(shareIntent, "分享多份数据"));

当然,不仅可以分享多份文本,还可以分享多张图片,多个文件等。下面会专门说分享文件的部分。

5. 分享二进制数据

最常见的就是图片了,示例如下:

Uri fileUri = Uri.fromFile(file);  Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND);  shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION         | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); shareIntent.setType("image/jpeg"); requireActivity().startActivity(Intent.createChooser(shareIntent, null));

上述示例需要接收方具有权限才可以接收到,不仅仅是图片,任何二进制文件都可以通过上述示例分享,但是如果接收方没有相应权限,就无法接收。而且从 Android 7.0 开始,使用 File Uri 可能会抛出 FileUriExposedException 异常。

因此,使用 Uri.fromFile,这不是最佳的做法,分享文件应该使用 FileProvider,或者 MediaStore,前者等会说,而后者不仅仅可以用来存储媒体文件,MediaStore.Files 还可以用来存储文件的。

分享媒体或文档给其他应用

分享文件的最佳做法,就是使用 FileProvider,FileProvider 是 ContentProvider 的一个特殊子类,它通过为文件创建 content:// 而不是 file:/// 在应用之间进行文件的安全共享。

FileProvider 使用临时读写访问权限,授予其他应用临时性的权限,当 Uri 传给某应用程序后,只要它的 Activity 或 Service 还在运行,该权限就一直可用,直到不运行了,该权限下次就不可用了。

使用 content 类型的 Uri 比 file 类型的要安全的多。因此在应用间分享文件,使用 FileProvider 就成了必然的选择。

下面来看看如何使用。

1. 在 Manifest 中注册

首先 FileProvider 是个 ContentProvider,四大组件是必须注册的,如下:

<manifest>     ...     <application>         ...         <provider             android:name="androidx.core.content.FileProvider"             android:authorities="<package name>.fileprovider"             android:exported="false"             android:grantUriPermissions="true">             <meta-data                 android:name="android.support.FILE_PROVIDER_PATHS"                 android:resource="@xml/file_paths" />         </provider>         ...     </application> </manifest>

属性解释:

  • android:name 属性设置为 androidx.core.content.FileProvider。这是固定的。
  • android:authorities 属性设置为 URI 权限,通常是包名 + fileprovider。
  • android:exported 属性设置为 false,因为 FileProvider 不需要公开。
  • android:grantUriPermissions 属性设置为 true,允许授予对组件的临时访问权限。
  • file_paths 是用来声明对应的共享路径的,下面会说到。

当然,你要是想扩展功能,也可以继承 FileProvider。

2. 告知 FileProvider 你要分享的文件的目录

在 res 资源目录创建 xml 目录,再创建 file_paths.xml,内容如下:

<?xml version="1.0" encoding="utf-8"?> <!-- name 属性:是为了隐藏 path 属性而存在的,也就是说,在 Uri 中,用它来表示 path path 属性:真实的目录名称 --> <paths xmlns:android="http://schemas.android.com/apk/res/android">     <!--以下3个是外部专属空间的-->     <!--Context.getExternalMediaDirs().API 21+-->     <external-media-path         name="external-media-path"         path="img/"         />     <!--Context.getExternalCacheDir()-->     <external-cache-path         name="external-cache-path"         path="img/"         />     <!--Context.getExternalFilesDir("img")-->     <external-files-path         name="external-files-path"         path="img/"         />      <!-- 外部存储根目录 -->     <!--Environment.getExternalStorageDirectory().-->     <external-path         name="external-path"         path="img/"         />      <!--以下2个是内部专属空间的-->     <!--getCacheDir().-->     <cache-path         name="cache-path"         path="img/"         />     <!--Context.getFilesDir().-->     <files-path         name="files-path"         path="img/"         /> </paths>

如上注释也解释的很清楚了,所有的可配置标签也列出来了,对应的目录也列出来了。

比如上述的 files-path 标签,就是 Context.getFilesDir() 得到的路径加上 path 定义的 img 就是实际目录了,通过 在 Android 中使用存储功能 (1) 我们知道,Context.getFilesDir() 路径硬编码是:/data/data/包名/files,因此上述 files-path 标签声明的绝对路径就是:/data/data/包名/files/img/

需要注意的是,这里配置的是目录,而不是文件。当然,你可以指定多个相同的标签,指向不同的子目录。

还有一个要注意的点,name 的定义,不可以和其他标签的一致,是唯一的,另外,最好是定义成看不出具体目录的形式。而 path 的定义,只要不同标签的,可以定义成一样的。这也很好理解,不同目录的子目录可以同名,但是 name 是唯一标志该目录的,所以自然不能重复。

3. 生成 Uri

经过上面的步骤,我们已经声明了要共享的路径,我们拿上述的 external-files-path 举例,即目录:/sdcard/Android/data/包名/files/img/,现在假设这个目录下有个文件 test.jpg,我们要分享出去,首先要生成一个 Uri,如下:

File externalFilesDir = requireActivity().getExternalFilesDir("img"); File newFile = new File(externalFilesDir, "test.jpg"); // 我们在 assets 目录中准备了一张图,用来分享的,把它拷贝到指定的目录 if (!newFile.exists()) {     Utils.copyAssetFile(requireActivity(), "img/test.jpg", newFile); }  // 生成 uri Uri contentUri = FileProvider.getUriForFile(requireActivity(), requireActivity().getPackageName() + ".fileprovider", newFile);

得到的 Uri 如下所示:

content://com.imyyq.mediastoretest.fileprovider/external-files-path/test.jpg

uri 解释:

  • com.imyyq.mediastoretest.fileprovider 是在 Manifest 中配置好的 android:authorities
  • external-files-path,是 xml 中配置的 name,注意:不是 path 哦
  • test.jpg 就是 path 目录下对应的文件了。

拿到这个 Uri 就可以用来分享啦。

4. Uri 权限的问题

Intent.createChooser 方法,是主动发起分享的,该方法中,给 Intent 设置了 FLAG_GRANT_READ_URI_PERMISSION,所以接收者是不需要具备声明任何权限就对该文件具备临时可读权限。

如果你是要做一个文件选择器,别人调起你的应用,选择文件后,你通过 setResult 返回一个 Intent,别忘了给你的 Intent 设置临时权限:

Intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

否则对方是无权使用你的文件的。


FileProvider 能共享文件的原理就是使用 ContentProvider 机制,ContentProvider 本身就是用来跨进程跨应用通信的,在 Android 中的应用随处可见,比如查阅联系人,图库,短信等。

接收其他应用分享的内容

上面我们所描述的是如何分享数据给其他的应用,比如分享给微信或 QQ。那么如果我们需要类似于微信和 QQ 这样需要接收其他应用分享的数据呢?我们来看看怎么做。

了解过 Intent 的就会知道,应用是可以对其他应用发起的 action 和 intent-filter 进行过滤的,如上面发起的 action:Intent.ACTION_SENDIntent.ACTION_SEND_MULTIPLE,只需要在我们的清单文件中指定 intent-filter,当用户发起分享时,应用即可出现在系统的分享面板中,如下:

<activity android:name=".TestActivity" >     <intent-filter>         <action android:name="android.intent.action.SEND" />         <category android:name="android.intent.category.DEFAULT" />         <data android:mimeType="image/*" />     </intent-filter>     <intent-filter>         <action android:name="android.intent.action.SEND" />         <category android:name="android.intent.category.DEFAULT" />         <data android:mimeType="text/plain" />     </intent-filter>     <intent-filter>         <action android:name="android.intent.action.SEND_MULTIPLE" />         <category android:name="android.intent.category.DEFAULT" />         <data android:mimeType="image/*" />     </intent-filter> </activity>

action 指定了可以响应 Intent.ACTION_SEND_MULTIPLE 和 Intent.ACTION_SEND,而 mimeType 则声明了可以接收文本和所有格式的图片。mimeType 你也可以设置成:“/”,这样其实就是在声明应用可以处理任何数据了,除非你真的可以处理全部的数据,否则还是只声明你可以处理的数据。

当用户在分享面板中选择了你的应用后,数据就会发到 TestActivity 上,那么如何获取数据呢?可以通过 getIntent,如下:

“`java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityReceiverShareBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

前言

在 在 Android 中使用存储功能 (1) 中我们阐述了如何使用应用的专属目录,而专属目录的目的是不愿意被其他人任意访问的,尤其是在分区存储上。但是如果我们有部分数据需要分享给其他人怎么办?比如如何让其他应用可以访问我们应用专属目录中的某个文件?本文章就来详细聊聊系统的分享功能。

数据的共享有主动的被动的,主动的去分享数据,或者是被动的提供一个类似于文件选择器那样的功能供其他应用去选择我们共享出来的文件。

现在的应用大多数都有分享功能,分享一张图片,一个网址给朋友,分享到微信,微博等。而且许多设计师都喜欢自定义分享面板,于是开发者们在做分享功能时,就有了如下选择:

  1. 集成第三方分享组件,比如友盟分享等。
  2. 自己对接各大平台的分享 SDK。
  3. 使用系统本身的提供的分享功能。

第一种比较方便快捷,不过就是集成了第三方的东西,对数据敏感的还是慎选。
第二种方式比较麻烦,不过分享目标少的情况下倒是问题不大,可以实现一些平台提供的专有功能。
而第三种,如果对 UI 没有特殊的要求,设计师愿意让步的话,更加方便快捷的方式是选择使用系统原生的分享组件,而且 UI 也挺漂亮的。

不管是哪种方式,本质上都是 Android 系统的数据互相共享,下面我们就来看看,如何使用系统提供的分享功能共享数据给其他应用以及如何接收别人分享的数据。

分享数据给其他应用

通常最常见的就是分享图片,分享视频,还有就是分享一个网址。可以理解为分享一段内存数据,一个文件。

1. 分享一段文本数据

Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, "要分享的文本");  // Android 10 开始,可以通过 Intent.EXTRA_TITLE 添加描述信息,ClipData 添加缩略图 sendIntent.putExtra(Intent.EXTRA_TITLE, "我是标题"); sendIntent.setClipData(ClipData.newUri(MyApp.getApp().getContentResolver(), "我是缩略图", uri));  // 设置分享的类型 sendIntent.setType("text/plain");  Intent shareIntent = Intent.createChooser(sendIntent, null); startActivity(shareIntent);

非常简单,startActivity 后就会弹出如下对话框:

在 Android 中使用分享功能

2. 如何得知用户选择分享到哪里

有时候我们需要统计用户点击了分享面板中的哪个应用,可以通过 IntentSender。如下:

Intent intent = new Intent(); intent.setAction("com.imyyq.intent.sender"); PendingIntent pi = PendingIntent.getBroadcast(getContext(), 10000,         intent,         PendingIntent.FLAG_UPDATE_CURRENT);  Intent shareIntent = Intent.createChooser(sendIntent, null, pi.getIntentSender());

在 createChooser 时,传入 IntentSender。然后在广播中接收:

public class IntentSenderBroadcastReceiver extends BroadcastReceiver {     @Override     public void onReceive(Context context, Intent intent) {         ComponentName extra = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT);         Toast.makeText(context, "点击了 " + extra.getPackageName(), Toast.LENGTH_SHORT).show();     } }

如此一来当我们点击了分享面板中的应用,就可以在广播中收到是选择了谁了。

3. 其他的 Extra

Intent 中定义了很多的 Extra,比如有:

  • 电子邮件相关的:EXTRA_EMAIL、EXTRA_CC、EXTRA_BCC、EXTRA_SUBJECT
  • 排除某些分享目标:EXTRA_EXCLUDE_COMPONENTS
  • 添加自定义目标:EXTRA_CHOOSER_TARGETS

其他的可以自行查阅 Intent 文档。

4. 分享多份数据

除了 Intent.ACTION_SEND 分享一份数据,还可以用 Intent.ACTION_SEND_MULTIPLE 分享多份。如下示例:

ArrayList<CharSequence> imageUris = new ArrayList<>(); imageUris.add("我是文本 1"); imageUris.add("我是文本 2");  Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE); shareIntent.putCharSequenceArrayListExtra(Intent.EXTRA_STREAM, imageUris); shareIntent.setType("text/*"); startActivity(Intent.createChooser(shareIntent, "分享多份数据"));

当然,不仅可以分享多份文本,还可以分享多张图片,多个文件等。下面会专门说分享文件的部分。

5. 分享二进制数据

最常见的就是图片了,示例如下:

Uri fileUri = Uri.fromFile(file);  Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND);  shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION         | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); shareIntent.setType("image/jpeg"); requireActivity().startActivity(Intent.createChooser(shareIntent, null));

上述示例需要接收方具有权限才可以接收到,不仅仅是图片,任何二进制文件都可以通过上述示例分享,但是如果接收方没有相应权限,就无法接收。而且从 Android 7.0 开始,使用 File Uri 可能会抛出 FileUriExposedException 异常。

因此,使用 Uri.fromFile,这不是最佳的做法,分享文件应该使用 FileProvider,或者 MediaStore,前者等会说,而后者不仅仅可以用来存储媒体文件,MediaStore.Files 还可以用来存储文件的。

分享媒体或文档给其他应用

分享文件的最佳做法,就是使用 FileProvider,FileProvider 是 ContentProvider 的一个特殊子类,它通过为文件创建 content:// 而不是 file:/// 在应用之间进行文件的安全共享。

FileProvider 使用临时读写访问权限,授予其他应用临时性的权限,当 Uri 传给某应用程序后,只要它的 Activity 或 Service 还在运行,该权限就一直可用,直到不运行了,该权限下次就不可用了。

使用 content 类型的 Uri 比 file 类型的要安全的多。因此在应用间分享文件,使用 FileProvider 就成了必然的选择。

下面来看看如何使用。

1. 在 Manifest 中注册

首先 FileProvider 是个 ContentProvider,四大组件是必须注册的,如下:

<manifest>     ...     <application>         ...         <provider             android:name="androidx.core.content.FileProvider"             android:authorities="<package name>.fileprovider"             android:exported="false"             android:grantUriPermissions="true">             <meta-data                 android:name="android.support.FILE_PROVIDER_PATHS"                 android:resource="@xml/file_paths" />         </provider>         ...     </application> </manifest>

属性解释:

  • android:name 属性设置为 androidx.core.content.FileProvider。这是固定的。
  • android:authorities 属性设置为 URI 权限,通常是包名 + fileprovider。
  • android:exported 属性设置为 false,因为 FileProvider 不需要公开。
  • android:grantUriPermissions 属性设置为 true,允许授予对组件的临时访问权限。
  • file_paths 是用来声明对应的共享路径的,下面会说到。

当然,你要是想扩展功能,也可以继承 FileProvider。

2. 告知 FileProvider 你要分享的文件的目录

在 res 资源目录创建 xml 目录,再创建 file_paths.xml,内容如下:

<?xml version="1.0" encoding="utf-8"?> <!-- name 属性:是为了隐藏 path 属性而存在的,也就是说,在 Uri 中,用它来表示 path path 属性:真实的目录名称 --> <paths xmlns:android="http://schemas.android.com/apk/res/android">     <!--以下3个是外部专属空间的-->     <!--Context.getExternalMediaDirs().API 21+-->     <external-media-path         name="external-media-path"         path="img/"         />     <!--Context.getExternalCacheDir()-->     <external-cache-path         name="external-cache-path"         path="img/"         />     <!--Context.getExternalFilesDir("img")-->     <external-files-path         name="external-files-path"         path="img/"         />      <!-- 外部存储根目录 -->     <!--Environment.getExternalStorageDirectory().-->     <external-path         name="external-path"         path="img/"         />      <!--以下2个是内部专属空间的-->     <!--getCacheDir().-->     <cache-path         name="cache-path"         path="img/"         />     <!--Context.getFilesDir().-->     <files-path         name="files-path"         path="img/"         /> </paths>

如上注释也解释的很清楚了,所有的可配置标签也列出来了,对应的目录也列出来了。

比如上述的 files-path 标签,就是 Context.getFilesDir() 得到的路径加上 path 定义的 img 就是实际目录了,通过 在 Android 中使用存储功能 (1) 我们知道,Context.getFilesDir() 路径硬编码是:/data/data/包名/files,因此上述 files-path 标签声明的绝对路径就是:/data/data/包名/files/img/

需要注意的是,这里配置的是目录,而不是文件。当然,你可以指定多个相同的标签,指向不同的子目录。

还有一个要注意的点,name 的定义,不可以和其他标签的一致,是唯一的,另外,最好是定义成看不出具体目录的形式。而 path 的定义,只要不同标签的,可以定义成一样的。这也很好理解,不同目录的子目录可以同名,但是 name 是唯一标志该目录的,所以自然不能重复。

3. 生成 Uri

经过上面的步骤,我们已经声明了要共享的路径,我们拿上述的 external-files-path 举例,即目录:/sdcard/Android/data/包名/files/img/,现在假设这个目录下有个文件 test.jpg,我们要分享出去,首先要生成一个 Uri,如下:

File externalFilesDir = requireActivity().getExternalFilesDir("img"); File newFile = new File(externalFilesDir, "test.jpg"); // 我们在 assets 目录中准备了一张图,用来分享的,把它拷贝到指定的目录 if (!newFile.exists()) {     Utils.copyAssetFile(requireActivity(), "img/test.jpg", newFile); }  // 生成 uri Uri contentUri = FileProvider.getUriForFile(requireActivity(), requireActivity().getPackageName() + ".fileprovider", newFile);

得到的 Uri 如下所示:

content://com.imyyq.mediastoretest.fileprovider/external-files-path/test.jpg

uri 解释:

  • com.imyyq.mediastoretest.fileprovider 是在 Manifest 中配置好的 android:authorities
  • external-files-path,是 xml 中配置的 name,注意:不是 path 哦
  • test.jpg 就是 path 目录下对应的文件了。

拿到这个 Uri 就可以用来分享啦。

4. Uri 权限的问题

Intent.createChooser 方法,是主动发起分享的,该方法中,给 Intent 设置了 FLAG_GRANT_READ_URI_PERMISSION,所以接收者是不需要具备声明任何权限就对该文件具备临时可读权限。

如果你是要做一个文件选择器,别人调起你的应用,选择文件后,你通过 setResult 返回一个 Intent,别忘了给你的 Intent 设置临时权限:

Intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

否则对方是无权使用你的文件的。


FileProvider 能共享文件的原理就是使用 ContentProvider 机制,ContentProvider 本身就是用来跨进程跨应用通信的,在 Android 中的应用随处可见,比如查阅联系人,图库,短信等。

接收其他应用分享的内容

上面我们所描述的是如何分享数据给其他的应用,比如分享给微信或 QQ。那么如果我们需要类似于微信和 QQ 这样需要接收其他应用分享的数据呢?我们来看看怎么做。

了解过 Intent 的就会知道,应用是可以对其他应用发起的 action 和 intent-filter 进行过滤的,如上面发起的 action:Intent.ACTION_SENDIntent.ACTION_SEND_MULTIPLE,只需要在我们的清单文件中指定 intent-filter,当用户发起分享时,应用即可出现在系统的分享面板中,如下:

<activity android:name=".TestActivity" >     <intent-filter>         <action android:name="android.intent.action.SEND" />         <category android:name="android.intent.category.DEFAULT" />         <data android:mimeType="image/*" />     </intent-filter>     <intent-filter>         <action android:name="android.intent.action.SEND" />         <category android:name="android.intent.category.DEFAULT" />         <data android:mimeType="text/plain" />     </intent-filter>     <intent-filter>         <action android:name="android.intent.action.SEND_MULTIPLE" />         <category android:name="android.intent.category.DEFAULT" />         <data android:mimeType="image/*" />     </intent-filter> </activity>

action 指定了可以响应 Intent.ACTION_SEND_MULTIPLE 和 Intent.ACTION_SEND,而 mimeType 则声明了可以接收文本和所有格式的图片。mimeType 你也可以设置成:“/”,这样其实就是在声明应用可以处理任何数据了,除非你真的可以处理全部的数据,否则还是只声明你可以处理的数据。

当用户在分享面板中选择了你的应用后,数据就会发到 TestActivity 上,那么如何获取数据呢?可以通过 getIntent,如下:

“`java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityReceiverShareBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 在 Android 中使用分享功能求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们