APP分享功能的实现

  • 说起在APP中添加分享功能,也在项目开发中集成过第三方的社会化组件,功能做出来了之后就发现坑其实挺多的,比如APP体积增加3~4MB,微信、微博等社区集成都需要申请appkey等。当然,一般第三方的社会化组件往往不只是集成分享功能,还会集成账号授权登陆等,而系统的分享功能就比较单纯了。这两天学习了一下系统自带的分享功能,写一篇笔记记录一下。

    通过Intent向其他应用发送分享内容

    先看一下发送邮件的Intent:
1
2
3
4
5
6
7
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("mailto:artharyoungcn@gmail.com"));
intent.putExtra(Intent.EXTRA_SUBJECT, "title");
intent.putExtra(Intent.EXTRA_TEXT, "desc");
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}

如果手机上已经安装了邮件的客户端,这个Intent将匹配action直接拉起邮件客户端。

分享文本的Intent:

1
2
3
4
5
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, "send message to someone");
intent.setType("text/plain");
startActivity(intent);

一般我们使用的手机上能匹配这个action的app都不止一个,QQ、微信、微博,这些社交类应用自不必说。蓝牙,NFC等一般也都会匹配这个action。所以系统会显示一个对话框供用户选择,并且会提示用户设置默认分享的APP,个人认为这个并没有什么用,因为我们无法保证用户每一次都分享到同一个app.我们可以使用Intent.createChooser()来设置弹出框的标题,并保证每次都弹出选择框,即使设置了默认分享。

1
2
3
4
5
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, "send message to someone");
intent.setType("text/plain");
startActivity(Intent.createChooser(intent, "实现分享"));

以上只是分享文本,在实际的开发过程中往往还需要分享图片。

数据流分享(这里以图片分享为例)

1
2
3
4
5
6
7
8
9
Bitmap bm = BitmapFactory.decodeResource(getResources(),R.drawable.chrome);
String path = MediaStore.Images.Media.insertImage(getContentResolver(), bm, "", "desc");

Uri imageUri = Uri.parse(path);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, imageUri);
intent.setType("image/*");
startActivity(Intent.createChooser(intent, "实现分享"));

这里需要提供一个供第三方程序访问的Uri,一般有以下几个方案:

  • 把数据写到外部存储(SD卡)上,使用Uri.fromFile()创建一个file://形式的Uri。
    这个形式的Uri并不是所有程序都能处理。
  • 在自己程序文件夹下用MODE_WORLD_READABLE模式使用openFileOutput()把数据写入文件,
    之后再用getFileStreamPath()返回一个File。用Uri.fromFile()来创建一个file://样式的Uri。
  • 图片,音频,视频等媒体文件可以用scanFile()扫描然后加到系统媒体库(MediaStore)中,
    onScanCompleted()回调方法会返回一个content://样式的Uri。
  • 图片还可以用insertImage()来加到媒体库(MediaStore)中,然后会返回一个content://样式的Uri。
  • 在ContentProvider中保存数据,给其他APP提供访问权限。

这里我们稍微对比下友盟社会化组件的存储方式,在UMImage.class这个类里面可以发现它在SD卡上自己创建了一个缓存文件夹:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public File getCache() throws IOException {
String var1;
if(DeviceConfig.isSdCardWrittenable()) {
var1 = Environment.getExternalStorageDirectory().getCanonicalPath();
} else {
if(TextUtils.isEmpty(this.b)) {
throw new IOException("dirpath is unknow");
}

var1 = this.b;
}

File var2 = new File(var1 + "/umeng_cache/");
if(var2 != null && !var2.exists()) {
var2.mkdirs();
}

return var2;
}

这里需要添加SD卡的读写权限:

1
2
3
4
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

分享多条数据

1
2
3
4
5
6
7
8
ArrayList<Uri> imageUris = new ArrayList<Uri>();
imageUris.add(imageUri); // Add your image URIs here
imageUris.add(imageUri);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
intent.setType("image/*");
startActivity(Intent.createChooser(intent, "分享图片"));

这里为了方便仍然使用上面的uri,只不过add了两次。

以上,都是通过给Intent设置一些其他信息:EXTRA_TEXT,EXTRA_STREAM等,并可以设置MIME(多用途互联网邮件扩展)类型。来达到让第三方客户端进行资源匹配的目的。但是,第三方程序需要能够解析他们,如果我们自定义extras,基本是实现不了分享的。我们可以来看一下如何接收其他APP发过来的分享。

接收其他APP的分享内容

  • 在manifest文件中配置filter。比如我们在demo的MainActivity中配置:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <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>

再次运行的时候会发现分享的列表中多了demo这个APP。

  • 处理传入的数据
    1
    2
    3
    Intent intent = getIntent();
    String action = intent.getAction();
    String type = intent.getType();

拿到action和MIME类型,然后就该干嘛干嘛去了~这里需要注意的是,我们不知道用户传进来的是什么,用户也有可能传错了MIME类型,还有可能传进来的数据非常大,比如一张特别大的图片。所以数据不建议放在UI线程中处理。

使用ShareActionProvider分享数据

ShareActionProvider是在API等级14以后提供的一种分享方式,与上面的分享对比如下:

可以发现在UI的显示上还是有很大区别的,ShareActionProvider将分享放在在ActionBar上。使用方法需要现在menu菜单中添加item:

1
2
3
4
<item android:id="@+id/action_share"
app:showAsAction="never"
android:title="@string/action_share"
app:actionProviderClass="android.support.v7.widget.ShareActionProvider" />

然后在onCreateOptionsMenu回调中获取ShareActionProvider:

1
2
3
4
5
6
7
8
9
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);

MenuItem item = menu.findItem(R.id.action_share);
mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
return true;
}

通过mShareActionProvider.setShareIntent(intent)设置需要分享的内容,intent的设置方式与前面是一样的。

这里有个坑需要注意下,我在demo中使用的是AppCompatActivity,是在appcompat-v7包中的,它使用的也是这个包中的ActionBar。在Menu菜单中需要配置:

1
app:actionProviderClass="android.support.v7.widget.ShareActionProvider"

在Activity中import android.support.v7.widget.ShareActionProvider并使用MenuItemCompat.getActionProvider(item)的方式获取ShareActionProvider。
若不使用appcompat-v7包中的ActionBar,则Menu菜单中设置如下:

1
2
3
4
<item android:id="@+id/action_share"
android:showAsAction="ifRoom"
android:title="Share"
android:actionProviderClass="android.widget.ShareActionProvider" />

然后在onCreateOptionsMenu中,通过一下方式获取ShareActionProvider:

1
mShareActionProvider = (ShareActionProvider) item.getActionProvider();

然后就可以通过setShareIntent(intent)分享了。