跳到主要内容Android 原生分享功能实现详解 | 极客日志JavaWeChatjava
Android 原生分享功能实现详解
Android 原生分享功能通过 Intent 机制实现,支持文本、图片、音频、视频等多种类型分享。基于 Intent.ACTION_SEND 的实现方案,涵盖 ShareUtil 工具类封装、Activity 调用逻辑及布局配置。针对 Android 7.0+ 系统限制,补充了 FileProvider 配置及存储权限申请说明,确保在微信、QQ 等主流应用间稳定分享文件,同时规避了 FileUriExposedException 安全异常,提供了一套完整的原生分享集成指南。
XiaoPingzi2 浏览 Android 原生分享功能实现详解
在 Android 开发中,利用系统原生的 Intent 机制调用其他应用进行分享是一种轻量且兼容性较好的方案。相比第三方 SDK(如 ShareSDK、友盟),原生方式无需引入额外依赖,但需要处理不同应用的特定组件名称及权限配置。
一、核心原理与准备
1.1 权限配置
在 AndroidManifest.xml 中声明必要的权限,包括网络访问和外部存储读写权限(针对 Android 6.0+ 还需动态申请):
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
1.2 FileProvider 配置
从 Android 7.0 (API 24) 开始,直接使用 Uri.fromFile() 会抛出 FileUriExposedException。必须使用 FileProvider 生成内容 URI。
在 res/xml/file_paths.xml 中添加配置:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external" path="." />
<files-path name="files" path="." />
</paths>
在 AndroidManifest.xml 的 Application 节点下注册 Provider:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities=
=
=>
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
"${applicationId}.fileprovider"
android:exported
"false"
android:grantUriPermissions
"true"
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
二、ShareUtil 工具类封装
创建一个 ShareUtil.java 类来统一管理分享逻辑,支持文本、图片、音频、视频等多种类型。
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import android.widget.Toast;
import java.io.File;
public class ShareUtil {
private Context context;
public ShareUtil(Context context) {
this.context = context;
}
public void shareText(String packageName, String className, String content, String title, String subject) {
if (!stringCheck(packageName) && !stringCheck(className)) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, content);
if (!TextUtils.isEmpty(title)) intent.putExtra(Intent.EXTRA_TITLE, title);
if (!TextUtils.isEmpty(subject)) intent.putExtra(Intent.EXTRA_SUBJECT, subject);
context.startActivity(Intent.createChooser(intent, "分享到:"));
return;
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("text/plain");
if (stringCheck(className) && stringCheck(packageName)) {
ComponentName componentName = new ComponentName(packageName, className);
intent.setComponent(componentName);
} else if (stringCheck(packageName)) {
intent.setPackage(packageName);
}
intent.putExtra(Intent.EXTRA_TEXT, content);
if (!TextUtils.isEmpty(title)) intent.putExtra(Intent.EXTRA_TITLE, title);
if (!TextUtils.isEmpty(subject)) intent.putExtra(Intent.EXTRA_SUBJECT, subject);
context.startActivity(Intent.createChooser(intent, "分享到:"));
}
public void shareImg(String packageName, String className, File file) {
if (!file.exists()) {
Toast.makeText(context, "文件不存在", Toast.LENGTH_SHORT).show();
return;
}
Uri uri = Uri.fromFile(file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("image/*");
if (stringCheck(packageName) && stringCheck(className)) {
intent.setComponent(new ComponentName(packageName, className));
} else if (stringCheck(packageName)) {
intent.setPackage(packageName);
}
intent.putExtra(Intent.EXTRA_STREAM, uri);
context.startActivity(Intent.createChooser(intent, "分享到:"));
}
public void shareAudio(String packageName, String className, File file) {
if (!file.exists()) {
Toast.makeText(context, "文件不存在", Toast.LENGTH_SHORT).show();
return;
}
Uri uri = Uri.fromFile(file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("audio/*");
if (stringCheck(packageName) && stringCheck(className)) {
intent.setComponent(new ComponentName(packageName, className));
} else if (stringCheck(packageName)) {
intent.setPackage(packageName);
}
intent.putExtra(Intent.EXTRA_STREAM, uri);
context.startActivity(Intent.createChooser(intent, "分享到:"));
}
public void shareVideo(String packageName, String className, File file) {
setIntent("video/*", packageName, className, file);
}
private void setIntent(String type, String packageName, String className, File file) {
if (!file.exists()) {
Toast.makeText(context, "文件不存在", Toast.LENGTH_SHORT).show();
return;
}
Uri uri = Uri.fromFile(file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType(type);
if (stringCheck(packageName) && stringCheck(className)) {
intent.setComponent(new ComponentName(packageName, className));
} else if (stringCheck(packageName)) {
intent.setPackage(packageName);
}
intent.putExtra(Intent.EXTRA_STREAM, uri);
context.startActivity(Intent.createChooser(intent, "分享到:"));
}
public boolean checkInstall(String packageName) {
try {
context.getPackageManager().getPackageInfo(packageName, 0);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private static boolean stringCheck(String str) {
return str != null && !TextUtils.isEmpty(str);
}
}
三、Activity 调用示例
在 MainActivity.java 中初始化工具类并绑定按钮事件。
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import java.io.File;
public class MainActivity extends Activity implements View.OnClickListener {
Button btnQQ, btnWX, btnMore;
Button btnWxFriendText, btnQQFriendText;
Button btnWxFriendImg, btnQQFriendImg;
Button btnWxFriendAudio, btnQQFriendAudio;
Button btnWxFriendVideo, btnQQFriendVideo;
Button btn_wxCircle_img;
ShareUtil shareUtil;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
shareUtil = new ShareUtil(this);
}
private void initViews() {
btnQQ = findViewById(R.id.btn_qq);
btnWX = findViewById(R.id.btn_wx);
btnMore = findViewById(R.id.btn_more);
btnWxFriendText = findViewById(R.id.btn_wxFriend);
btnQQFriendText = findViewById(R.id.btn_qqFriend);
btnWxFriendImg = findViewById(R.id.btn_wxFriend_img);
btnQQFriendImg = findViewById(R.id.btn_qqFriend_img);
btnWxFriendAudio = findViewById(R.id.btn_wxFriend_audio);
btnQQFriendAudio = findViewById(R.id.btn_qqFriend_audio);
btnWxFriendVideo = findViewById(R.id.btn_wxFriend_video);
btnQQFriendVideo = findViewById(R.id.btn_qqFriend_video);
btn_wxCircle_img = findViewById(R.id.btn_wxCircle_img);
btnQQ.setOnClickListener(this);
btnWX.setOnClickListener(this);
btnMore.setOnClickListener(this);
btnWxFriendText.setOnClickListener(this);
btnQQFriendText.setOnClickListener(this);
btnWxFriendImg.setOnClickListener(this);
btnQQFriendImg.setOnClickListener(this);
btnWxFriendAudio.setOnClickListener(this);
btnQQFriendAudio.setOnClickListener(this);
btnWxFriendVideo.setOnClickListener(this);
btnQQFriendVideo.setOnClickListener(this);
btn_wxCircle_img.setOnClickListener(this);
}
@Override
public void onClick(View v) {
String testImgPath = "/storage/emulated/legacy/display-client/picture/my.png";
File fileImage = new File(testImgPath);
switch (v.getId()) {
case R.id.btn_qq:
shareUtil.shareText("com.tencent.mobileqq", null, "这是一条分享信息", "分享标题", "分享主题");
break;
case R.id.btn_wx:
shareUtil.shareText("com.tencent.mm", null, "这是一条分享信息", "分享标题", "分享主题");
break;
case R.id.btn_more:
shareUtil.shareText(null, null, "这是一条分享信息", "分享标题", "分享主题");
break;
case R.id.btn_wxFriend:
if (shareUtil.checkInstall("com.tencent.mm")) {
shareUtil.shareText("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI", "http://www.example.com/", "分享标题", "分享主题");
}
break;
case R.id.btn_qqFriend:
if (shareUtil.checkInstall("com.tencent.mobileqq")) {
shareUtil.shareText("com.tencent.mobileqq", "com.tencent.mobileqq.activity.JumpActivity", "http://www.example.com/", "分享标题", "分享主题");
}
break;
case R.id.btn_wxFriend_img:
shareUtil.shareImg("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI", fileImage);
break;
case R.id.btn_qqFriend_img:
shareUtil.shareImg("com.tencent.mobileqq", "com.tencent.mobileqq.activity.JumpActivity", fileImage);
break;
case R.id.btn_wxFriend_audio:
shareUtil.shareAudio("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI", fileImage);
break;
case R.id.btn_qqFriend_audio:
shareUtil.shareAudio("com.tencent.mobileqq", "com.tencent.mobileqq.activity.JumpActivity", fileImage);
break;
case R.id.btn_wxFriend_video:
shareUtil.shareVideo("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI", fileImage);
break;
case R.id.btn_qqFriend_video:
shareUtil.shareVideo("com.tencent.mobileqq", "com.tencent.mobileqq.activity.JumpActivity", fileImage);
break;
case R.id.btn_wxCircle_img:
shareUtil.shareImgToWXCircle("狗狗图片", "com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI", fileImage);
break;
}
}
}
四、布局文件 activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<Button
android:id="@+id/btn_qq"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="QQ" />
<Button
android:id="@+id/btn_wx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="微信"
android:layout_below="@+id/btn_qq" />
<Button
android:id="@+id/btn_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="更多"
android:layout_below="@+id/btn_wx" />
<Button
android:id="@+id/btn_wxFriend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_more"
android:text="微信好友文字" />
<Button
android:id="@+id/btn_qqFriend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_wxFriend"
android:text="QQ 好友文字" />
<Button
android:id="@+id/btn_wxFriend_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_more"
android:layout_toRightOf="@+id/btn_wxFriend"
android:text="微信好友图片" />
<Button
android:id="@+id/btn_qqFriend_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/btn_qqFriend"
android:layout_below="@+id/btn_wxFriend"
android:text="QQ 好友图片" />
<Button
android:id="@+id/btn_wxFriend_audio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_more"
android:layout_toRightOf="@+id/btn_wxFriend_img"
android:text="微信好友音频" />
<Button
android:id="@+id/btn_qqFriend_audio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/btn_qqFriend_img"
android:layout_below="@+id/btn_wxFriend"
android:text="QQ 好友音频" />
<Button
android:id="@+id/btn_wxFriend_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_qqFriend"
android:text="微信好友视频" />
<Button
android:id="@+id/btn_qqFriend_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_wxFriend_video"
android:text="QQ 好友视频" />
<Button
android:id="@+id/btn_wxCircle_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_wxFriend_video"
android:layout_toRightOf="@+id/btn_wxFriend_img"
android:text="微信朋友圈" />
</RelativeLayout>
五、注意事项
- 组件名变更:微信和 QQ 的内部组件名称(如
ShareImgUI)可能会随版本更新而变化,建议通过反射或官方文档确认最新组件名。
- 权限适配:Android 6.0 及以上需要在运行时动态请求存储权限。
- URI 安全:正式项目中请务必使用
FileProvider 替代 Uri.fromFile(),避免崩溃。
- 朋友圈限制:微信朋友圈分享通常仅支持图片和文字,部分旧版本可能不支持单独分享图片或纯文本到朋友圈。