对于 App 的分享功能,基本上是一个刚需,本文主要介绍运用系统原生分享功能时候需要注意的一些问题。对于某些特定平台的一些高级分享特性,比如微信或者微博之类的分享来源标注,需要在其开放平台注册应用再接入其 sdk 才可以,这里不予以讨论。打算借助第三方库类似 ShareSDK 实现的同学们,这篇文章可能也帮不上你。

GitHub 项目地址:Share2

什么是 Android 系统的原生分享

直接上图,这是一个典型的调用系统原生分享场景下的界面,相信大家应该都很熟悉。

系统内建的分享机制,参照官方的教程,基本上可以满足你的一般需求:Android-training-building-content-sharing

简单描述下创建分享的主要过程:

  • 创建一个 Intent ,指定其 ActionIntent.ACTION_SEND,这表示要创建一个发送指定内容的隐式意图。
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
复制代码
  • 指定需要发送的内容和类型。
// 比如发送文本形式的数据内容
// 指定发送的内容
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
// 指定发送内容的类型
sendIntent.setType("text/plain");
复制代码
// 比如发送二进制文件数据流内容(比如图片、视频、音频文件等等)
// 指定发送的内容 (EXTRA_STREAM 对于文件 Uri )
shareIntent.putExtra(Intent.EXTRA_STREAM, uriToImage);
// 指定发送内容的类型 (MIME type)
shareIntent.setType("image/jpeg");
复制代码
  • 向系统发送隐式意图,打开系统分享选择器,出现如上图所示界面。
startActivity(Intent.createChooser(shareIntent, “Share to...”));
复制代码

四不四看起来很简单,四不四感觉可以分分钟可以搞定。年轻人,我跟你港,别图样图森破,现在大家没遇几个坑都不好意思出来港,不过做人嘛~最重要的开心。

那下面说一下遇到的一些问题,特别针对是 7.0 以后的系统,以及兼容一些主流 app 时遇到的坑。

1. 获取文件类型(MimeType)

前面说到分享文件时需要知道文件的类型,不然的指定类型为 */* ,这样分享到某些 App 会因为无法判断文件类型而导致失败,所以最好先根据文件路径获取其文件类型。

下面是一些常见文件的mimeType

{".3gp", "video/3gpp"},
{".apk", "application/vnd.android.package-archive"},
{".asf", "video/x-ms-asf"},
{".avi", "video/x-msvideo"},
{".bin", "application/octet-stream"},
{".bmp", "image/bmp"},
{".c", "text/plain"},
{".class", "application/octet-stream"},
{".conf", "text/plain"},
{".cpp", "text/plain"},
{".doc", "application/msword"},
{".exe", "application/octet-stream"},
{".gif", "image/gif"},
{".gtar", "application/x-gtar"},
{".gz", "application/x-gzip"},
{".h", "text/plain"},
{".htm", "text/html"},
{".html", "text/html"},
{".jar", "application/java-archive"},
{".java", "text/plain"},
{".jpeg", "image/jpeg"},
{".jpg", "image/jpeg"},
{".js", "application/x-javascript"},
{".log", "text/plain"},
{".m3u", "audio/x-mpegurl"},
{".m4a", "audio/mp4a-latm"},
{".m4b", "audio/mp4a-latm"},
{".m4p", "audio/mp4a-latm"},
{".m4u", "video/vnd.mpegurl"},
{".m4v", "video/x-m4v"},
{".mov", "video/quicktime"},
{".mp2", "audio/x-mpeg"},
{".mp3", "audio/x-mpeg"},
{".mp4", "video/mp4"},
{".mpc", "application/vnd.mpohun.certificate"},
{".mpe", "video/mpeg"},
{".mpeg", "video/mpeg"},
{".mpg", "video/mpeg"},  {".mpg4", "video/mp4"},
{".mpga", "audio/mpeg"},
{".msg", "application/vnd.ms-outlook"},
{".ogg", "audio/ogg"},
{".pdf", "application/pdf"},
{".png", "image/png"},
{".pps", "application/vnd.ms-powerpoint"},
{".ppt", "application/vnd.ms-powerpoint"},
{".prop", "text/plain"},
{".rar", "application/x-rar-compressed"},
{".rc", "text/plain"},
{".rmvb", "audio/x-pn-realaudio"},
{".rtf", "application/rtf"},
{".sh", "text/plain"},
{".tar", "application/x-tar"},
{".tgz", "application/x-compressed"},
{".txt", "text/plain"},
{".wav", "audio/x-wav"},
{".wma", "audio/x-ms-wma"},
{".wmv", "audio/x-ms-wmv"},
{".wps", "application/vnd.ms-works"},
//{".xml", "text/xml"},
{".xml", "text/plain"},
{".z", "application/x-compress"},
{".zip", "application/zip"},
{"", "*/*"}
复制代码

####获取文件类型的方法: 方式一(方便但不稳定):通过 ContentResolver 查询 Android 系统提供的 ContentProvider 获取

当 targetSdkVersion >= 24 时使用 Uri.fromFile(File file) 获取文件 uri 会报 android.os.FileUriExposedException 异常 ,应该要使用 FileProvider ,具体请参考 Android 7.0 FileProvider 适配相关,这里不再展开说明。关于 FileProvider 推荐一篇总结比较好的文章。

// 获取文件的 url
File shareFile = new File(shareFilePath);
Uri fileUri = Uri.fromFile(shareFile);// 获取系统的提供的 ContentResolver
ContentResolver contentResolver = getApplicationContext().getContentResolver();
// 获取文件MimeType,如 image/png
String fileMimeType = contentResolver.getType(fileUri);// 获取文件Type,如 png
MimeTypeMap mime = MimeTypeMap.getSingleton();
String fileType = mime.getExtensionFromMimeType(fileMimeType);
复制代码

使用这种方法获取文件类型,一定要注意 ContentResolver 获取返回为 null 的情况,不然空指针异常的崩溃率可能会让你笑不出来。实际测试中,发现在某些国产机型下,这个方法可以说直接是不可用,查询返回一直都是空,所以单纯依赖这一个方法会很不可靠。具体问题原因请看:What causes Android's ContentResolver.query() to return null?

方式二 解析文件信息,通过匹配识别判断: 在好用的方法却不可靠的情况下,只能配合看起来蠢一点的方法。目前大致的思路有两种: 1.识别文件后缀,根据后缀名来判断文件类型。 2.获取文件头信息,转成十六进制字符串后判断文件类型。 这两种都是根据特点信息去做匹配,因此需要先保存一份文件特点信息和文件类型的对应参照表。

下面按照第二条思路,按照文件头信息简单实现一个获取文件类型的例子:

  /*** 获取文件类型* @param filePath* @return*/public static String getFileType(String filePath) {  return mFileTypes.get(getFileHeader(filePath));  }  private static final HashMap<String, String> mFileTypes = new HashMap<String, String>();  // judge file type by file header content  static {  mFileTypes.put("ffd8ffe000104a464946", "jpg"); //JPEG (jpg)         mFileTypes.put("89504e470d0a1a0a0000", "png"); //PNG (png)         mFileTypes.put("47494638396126026f01", "gif"); //GIF (gif)         mFileTypes.put("49492a00227105008037", "tif"); //TIFF (tif)         mFileTypes.put("424d228c010000000000", "bmp"); //16色位图(bmp)         mFileTypes.put("424d8240090000000000", "bmp"); //24位位图(bmp)         mFileTypes.put("424d8e1b030000000000", "bmp"); //256色位图(bmp)         mFileTypes.put("41433130313500000000", "dwg"); //CAD (dwg)         mFileTypes.put("3c21444f435459504520", "html"); //HTML (html)    mFileTypes.put("3c21646f637479706520", "htm"); //HTM (htm)    mFileTypes.put("48544d4c207b0d0a0942", "css"); //css    mFileTypes.put("696b2e71623d696b2e71", "js"); //js    mFileTypes.put("7b5c727466315c616e73", "rtf"); //Rich Text Format (rtf)         mFileTypes.put("38425053000100000000", "psd"); //Photoshop (psd)         mFileTypes.put("46726f6d3a203d3f6762", "eml"); //Email [Outlook Express 6] (eml)           mFileTypes.put("d0cf11e0a1b11ae10000", "doc"); //MS Excel 注意:word、msi 和 excel的文件头一样         mFileTypes.put("d0cf11e0a1b11ae10000", "vsd"); //Visio 绘图         mFileTypes.put("5374616E64617264204A", "mdb"); //MS Access (mdb)          mFileTypes.put("252150532D41646F6265", "ps");  mFileTypes.put("255044462d312e350d0a", "pdf"); //Adobe Acrobat (pdf)       mFileTypes.put("2e524d46000000120001", "rmvb"); //rmvb/rm相同      mFileTypes.put("464c5601050000000900", "flv"); //flv与f4v相同      mFileTypes.put("00000020667479706d70", "mp4");  mFileTypes.put("49443303000000002176", "mp3");  mFileTypes.put("000001ba210001000180", "mpg"); //         mFileTypes.put("3026b2758e66cf11a6d9", "wmv"); //wmv与asf相同        mFileTypes.put("52494646e27807005741", "wav"); //Wave (wav)      mFileTypes.put("52494646d07d60074156", "avi");  mFileTypes.put("4d546864000000060001", "mid"); //MIDI (mid)       mFileTypes.put("504b0304140000000800", "zip");  mFileTypes.put("526172211a0700cf9073", "rar");  mFileTypes.put("235468697320636f6e66", "ini");  mFileTypes.put("504b03040a0000000000", "jar");  mFileTypes.put("4d5a9000030000000400", "exe");//可执行文件    mFileTypes.put("3c25402070616765206c", "jsp");//jsp文件    mFileTypes.put("4d616e69666573742d56", "mf");//MF文件    mFileTypes.put("3c3f786d6c2076657273", "xml");//xml文件    mFileTypes.put("494e5345525420494e54", "sql");//xml文件    mFileTypes.put("7061636b616765207765", "java");//java文件    mFileTypes.put("406563686f206f66660d", "bat");//bat文件    mFileTypes.put("1f8b0800000000000000", "gz");//gz文件    mFileTypes.put("6c6f67346a2e726f6f74", "properties");//bat文件    mFileTypes.put("cafebabe0000002e0041", "class");//bat文件    mFileTypes.put("49545346030000006000", "chm");//bat文件    mFileTypes.put("04000000010000001300", "mxp");//bat文件    mFileTypes.put("504b0304140006000800", "docx");//docx文件    mFileTypes.put("d0cf11e0a1b11ae10000", "wps");//WPS文字wps、表格et、演示dps都是一样的    mFileTypes.put("6431303a637265617465", "torrent");  mFileTypes.put("6D6F6F76", "mov"); //Quicktime (mov)      mFileTypes.put("FF575043", "wpd"); //WordPerfect (wpd)       mFileTypes.put("CFAD12FEC5FD746F", "dbx"); //Outlook Express (dbx)         mFileTypes.put("2142444E", "pst"); //Outlook (pst)          mFileTypes.put("AC9EBD8F", "qdf"); //Quicken (qdf)         mFileTypes.put("E3828596", "pwl"); //Windows Password (pwl)             mFileTypes.put("2E7261FD", "ram"); //Real Audio (ram)       mFileTypes.put("null", null); //null  }/*** 获取文件头信息* @param filePath* @return*/public static String getFileHeader(String filePath) {File file=new File(filePath);  if(!file.exists() || file.length()<11){  return "null";  }  FileInputStream is = null;  String value = null;  try {  is = new FileInputStream(file);  byte[] b = new byte[10];  is.read(b, 0, b.length);  value = bytesToHexString(b);  } catch (Exception e) {  } finally {  if(null != is) {  try {  is.close();  } catch (IOException e) {}  }  }  return value;  }/*** 将byte字节转换为十六进制字符串* @param src* @return*/private static String bytesToHexString(byte[] src) {StringBuilder builder = new StringBuilder();if (src == null || src.length <= 0) {return null;}String hv;for (int i = 0; i < src.length; i++) {hv = Integer.toHexString(src[i] & 0xFF).toUpperCase();if (hv.length() < 2) {builder.append(0);}builder.append(hv);}return builder.toString();}
复制代码

2. 获取分享文件的Uri进行分享

前面也有提到,在 Android 7.0 以后,系统对 scheme 为 file:// 的 uri 进行了限制,所以之前进行文件分享的一些接口就不能用了,此时就得使用其他的URI scheme 来代替 file://,比如 MediaStore 的 content:// 或者FileProvider 。

public static void shareFile(Context context, String filePath) {if (context == null || TextUtils.isEmpty(filePath)){LogUtil.e("shareFile context is null or filePath is empty.");return;}File file = new File(filePath);if (file != null && file.exists()){Intent intent = new Intent();intent.setAction(Intent.ACTION_SEND);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.addCategory("android.intent.category.DEFAULT");// 如果需要指定分享到某个app,配置 componentName 即可if (!TextUtils.isEmpty(componentName) && "com.tencent.mm".equals(componentName)){// 分享精确到微信的页面,朋友圈页面,或者选择好友分享页面ComponentName comp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI");intent.setComponent(comp);}intent.putExtra(Intent.EXTRA_STREAM, uri);// 授予目录临时共享权限intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);String fileType;Uri fileUri = getFileUri(context, file);if (fileUri != null && !TextUtils.isEmpty(fileUri.toString())) {ContentResolver contentResolver = context.getContentResolver();fileType = contentResolver.getType(fileUri);}if (TextUtils.isEmpty(fileType)){fileType = getFileType(filePath); // 使用上面的根据文件头信息获取文件类型的方法}if (TextUtils.isEmpty(fileType)){fileType =  "*/*"}LogUtil.d("shareFile fileType " + fileType);LogUtil.d("shareFile uri: " + uri);intent.setDataAndType(uri, fileType);try {context.startActivity(Intent.createChooser(intent, file.getName()));} catch (Exception e) {e.printStackTrace();}}}
复制代码

// 获取文件Uri

public static Uri getFileUri(Context context, File file){Uri uri;// 低版本直接用 Uri.fromFileif (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {uri = Uri.fromFile(file);}else {//  使用 FileProvider 会在某些 app 下不支持(在使用FileProvider 方式情况下QQ不能支持图片、视频分享,微信不支持视频分享)uri = FileProvider.getUriForFile(context,"gdut.bsx.videoreverser.fileprovider",file);ContentResolver cR = context.getContentResolver();if (uri != null && !TextUtils.isEmpty(uri.toString())) {String fileType = cR.getType(uri);
// 使用 MediaStore 的 content:// 而不是自己 FileProvider 提供的uri,不然有些app无法适配if (!TextUtils.isEmpty(fileType)){if (fileType.contains("video/")){uri = getVideoContentUri(context, file);}else if (fileType.contains("image/")){uri = getImageContentUri(context, file);}else if (fileType.contains("audio/")){uri = getAudioContentUri(context, file);}}}}return uri;}
复制代码
  /*** Gets the content:// URI from the given corresponding path to a file** @param context* @param imageFile* @return content Uri*/public 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);if (cursor != null && cursor.moveToFirst()) {int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));Uri baseUri = Uri.parse("content://media/external/images/media");return Uri.withAppendedPath(baseUri, "" + id);} else {if (imageFile.exists()) {ContentValues values = new ContentValues();values.put(MediaStore.Images.Media.DATA, filePath);return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);} else {return null;}}}/*** Gets the content:// URI from the given corresponding path to a file** @param context* @param videoFile* @return content Uri*/public static Uri getVideoContentUri(Context context, File videoFile) {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 && cursor.moveToFirst()) {int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));Uri baseUri = Uri.parse("content://media/external/video/media");return Uri.withAppendedPath(baseUri, "" + id);} else {if (videoFile.exists()) {ContentValues values = new ContentValues();values.put(MediaStore.Video.Media.DATA, filePath);return context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);} else {return null;}}}/*** Gets the content:// URI from the given corresponding path to a file** @param context* @param audioFile* @return content Uri*/public static Uri getAudioContentUri(Context context, File audioFile) {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 && cursor.moveToFirst()) {int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));Uri baseUri = Uri.parse("content://media/external/audio/media");return Uri.withAppendedPath(baseUri, "" + id);} else {if (audioFile.exists()) {ContentValues values = new ContentValues();values.put(MediaStore.Audio.Media.DATA, filePath);return context.getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);} else {return null;}}}
复制代码

要向在 MediaStore 中查询到文件,要不就是通知媒体库更新查询或则往里面插入一条新记录(会比较耗时)

/*** 删除或增加图片、视频等媒体资源文件时 通知系统更新媒体库,重新扫描* @param filePath 文件路径,包括后缀*/public static void notifyScanMediaFile(Context context, String filePath){if (context == null || TextUtils.isEmpty(filePath)){LogUtil.e("notifyScanMediaFile context is null or filePath is empty.");return;}MediaScannerConnection.scanFile(context,new String[] { filePath }, null,new MediaScannerConnection.OnScanCompletedListener() {public void onScanCompleted(String path, Uri uri) {LogUtil.i("notifyScanMediaFile Scanned " + path);LogUtil.i("notifyScanMediaFile -> uri=" + uri);}});}
复制代码

具体实现

可以参考我的另外一篇文章:利用 Android 系统原生 API 实现分享功能(2)

最后安利一波

最近自己开发的这个 App 按照了本篇文章中提到的分享方案进行了实现,实际效果大家直接去 Google Play 或国内酷安市场下载安装试试,欢迎拍砖。 VEditor - 酷安 VEditor - Google Play

转载于:https://juejin.im/post/5a3522e96fb9a04512390548

利用 Android 系统原生 API 实现分享功能相关推荐

  1. 【安卓开发】调用系统原生的文件分享功能将文件分享给QQ或微信好友

    调用系统原生的文件分享功能将文件分享给QQ或微信好友 核心代码: //传入的fileName是文件名的全路径@SuppressLint("QueryPermissionsNeeded&quo ...

  2. 用原生JS实现网页调用系统自带的分享功能

    流量当下的网络大环境,如何让我们的网站更容易出现在网民的视野中?这里我们就用原生JS实现网页调用系统自带的分享功能,为网页增加一个分享功能! 语法参数: url 要共享的 URL( USVString ...

  3. Android系统源码目录及功能介绍

    Android的移植按如下流程:     1.android linux 内核的普通驱动移植,让内核可以在目标平台上运行起来.     2.正确挂载文件系统,确保内核启动参数和 android 源代码 ...

  4. Android系统将内置滤镜功能

    from: http://cblog.cc/2015/09/03/Android-Camera-%E5%AE%9E%E6%97%B6%E6%BB%A4%E9%95%9C/ 一.Android系统将内置 ...

  5. Android 系统(266)---细数利用android系统机制的保活手段

    Android 进程常驻(2)----细数利用android系统机制的保活手段 这是一个轻量级的库,配置几行代码,就可以实现在android上实现进程常驻,也就是在系统强杀下,以及360获取root权 ...

  6. 开启Mac os系统原生的NTFS读写功能

    开启Mac os系统原生的NTFS读写功能 早期的Mac OS是可以通过修改mount_ntfs指令实现的.但是10.5以后的版本都不可以编译了,打开是乱码.只能说微软霸道.后来只能用破解版的Para ...

  7. Android 进程常驻(2)----细数利用android系统机制的保活手段

    这是一个轻量级的库,配置几行代码,就可以实现在android上实现进程常驻,也就是在系统强杀下,以及360获取root权限下,clean master获取root权限下都无法杀死进程 支持系统2.3到 ...

  8. android实现计算器功能吗,利用Android实现一个简单的计算器功能

    利用Android实现一个简单的计算器功能 发布时间:2020-11-20 16:25:01 来源:亿速云 阅读:90 作者:Leah 今天就跟大家聊聊有关利用Android实现一个简单的计算器功能, ...

  9. IOS 系统自带的分享功能之 UIActivityViewController

    关于系统自带的分享功能 主要是在与这个类 UIActivityViewController 先上效果图 然后呢 直接上代码 大家都懂的 ^&^ //分享的标题NSString *textToS ...

最新文章

  1. 激动~这是我看到的最好的目标检测RCNN了!
  2. 微软亚洲研究院开源分布式机器学习工具包
  3. GAIR 2020 工业互联网专场演讲实录:腾讯云人工智能在工业互联网领域的实践
  4. linux上怎么修改hostname
  5. 深入理解java虚拟机一 JAVA运行时内存区域与class文件
  6. 带有Oracle Digital Assistant和Fn Project的会话式UI
  7. Python isinstance() 函数用法及实例另类高级使用(附带classmethod 修饰符、json.dumps)
  8. 003 python接口 cookies
  9. 京沪无人驾驶复兴号高铁_河北高铁走到今天这一步,太不容易了...
  10. 又见The request sent by the client was syntactically incorrect ()
  11. 打包 pyqt5_基于Pyqt5的简单电影搜索工具,Python让你3秒搜电影
  12. CocoStudio 骨骼动画制作过程
  13. Azkaban的安装部署
  14. Java开发二维码扫一扫名片技术
  15. python根据excel数据生成柱状图并导出成图片格式
  16. 简单聊聊为什么说外包不好?
  17. NEYC 1702 排座 问题模型
  18. 《禅与摩托车维修艺术》
  19. 获取微信公众号文章封面图的技巧/网站
  20. python中scale什么意思啊_python数字图像处理(7):图像的形变与缩放

热门文章

  1. arduino使用TM1637制作控制器,可显示时钟和倒计时打开LED
  2. ios使用stream抓包
  3. php使用mpdf将html导出为pdf文件
  4. SSD系列之一:安装,调试(caffe)
  5. SOPC教学笔记04——Flash固化
  6. 数据并行和模型并行的区别
  7. export_fig是保存图片更加清晰
  8. ms17-010永恒之蓝漏洞复现
  9. 宝宝吃母乳的一些的谣言辟谣
  10. Java OOP 第一章 封装