「RSHARE」 一键分享 Android 版

因为公司的项目里集成了一键分享的这个模块, 而在我设计的时候发现国内的官方文档和提供的 Sample 有混乱和容易混淆的地方, 而且除了普通的网页、图片、文字分享到各大 Social 平台以外, 对于视频、文件和其他内容的分享 Demo 在百度或者 Google 几乎搜不到. 自己也是踩了很多坑才把很多问题解决.

测试设备

Samsung S9 Plus, Android 8.0.0.

支持平台

RSHARE 这个 Demo 中支持: 微信、QQ、新浪微博、Facebook、GooglePlus(Google +)、Twitter、WhatsApp、Line、Tumblr、Instagram、Pinterest 11 个 Social 平台.

平台差异

主要罗列了常用的 5 个分享内容的对比菜单(网页、文字、图片、本地视频、文件).
注意菜单内字母以及菜单后面对应字母的注释⚠️

❤️ QQ 微信 微博 Facebook Twitter Instagram WhatsApp Line Tumblr Pinterest Google+
网页 ✓a ✓b
文字 ✗c
图片 ✓d ✓d
视频 ?e
文件

a. QQ 虽支持网页分享, 但是不允许带着网页的 description 字段, 这和 iOS 表现不同;
b. Facebook 的网页分享支持 hashtag;
c. iOS 端分享文字到 QQ 客户端是可行的, 但是 Android 端不允许, 其实 QQ 的分享目地就是让用户自行输入有价值、有意义的文字信息;
d. Pinterest 和 Tumblr 虽支持图片分享, 但仅仅支持分享图片的 URL, 它会自行解析并显示;
e. 只支持分享到 QQ 空间.

⚠️ 1. 对于 QQ 分享 Android 端, 不支持通过 SDK 直接分享到数据线(我的电脑)和我的收藏.
⚠️ 2. 对于 WhatsApp 分享 Android 端, 支持图文并存.

  1. 对比列表得知, 国内的平台分享内容是最丰富的, 但是存在一个主要的问题(主要是 QQ), iOS 和 Android 双端的接口以及实现的功能也并不统一(后面的部分会具体说); 微信双端当分享的图片过大的时候的表现也有不统一的时候, 其余都很完善; 新浪的表现是最统一的, 且没有过多限制; 国内的平台分享最让人头疼的就是官方的 Sample 很混乱, 但是在实践代码的时候还是要以官方为主.
  2. 国外的文档和 Sample 很明了且注释详细, 但也存在双端不统一的情况, Twitter 的双端表现就不一样(后面细说), 但是国外的平台分享几乎没有回调, Facebook 的回调只有 Feed 形式的分享才有效, Instagram、Line、Google+这些是没有 SDK 的, 仅仅是通过打开 Application URL scheme 来分享.

分享总体设计

大概的分享逻辑如下:

Android 分享逻辑.png

Android 的分享是参照各大平台的分享逻辑得到比较统一思路, 即: 先判断是否安装对应应用, 然后初始化 SDK 与官方平台连接, 然后包装分享参数进行分享, 最后处理分享回调, 只不过与 iOS 端不同的是, 初始化 SDK 的工作是在分享逻辑内完成的, 并不对外开放(灵感来自 ShareSDK 的 Android 版), AppIDKey 以及 Secret 信息在 RShare.xml 文件中配置.

  1. 有些平台不存在 SDK, 所以直接判断是否平台安装然后包装分享参数进行分享;
  2. Tumblr 比较特殊, 有 SDK 但是不需要判断应用是否安装就可以分享, Twitter 也如此, 具体情况在 Twitter 的部分说明;
  3. SDK 的初始化, 统一函数名字为 sdkInitialize(但并未对外开放), KeySecret 以及 AppID 等信息在注释中有标明.

详细逻辑

关于各个平台的开发者主页和文档信息以这里为主, 代码注释的可能不准确.

平台分享都是通过单例模式实现.

基类

子平台 Manager 都是继承自 RShare, 这个类中定义了分享 Mode (代码注释中有标明)、分享内容类型枚举(内部使用) ShareContentType 以及分享回调.
Java 中创建监听器来监听分享结果:

public interface RShareListener {
    public abstract void onComplete(RSharePlatform.Platform platform);
    public abstract void onFail(RSharePlatform.Platform platform, String errorInfo);
    public abstract void onCancel(RSharePlatform.Platform platform);
}

Kotlin 中通过 callback 来监听分享结果:

typealias RShareCallback = ((platform : RSharePlatform, state : ShareState, errorInfo : String?) ->
Unit)

Mode(仅对 Facebook、Twitter、Instagram 有效, iOS 亦然):

Java:

public enum Mode {
    /**
     * 默认的分享方式
     * Facebook: 优先客户端分享, 客户端无法分享会转由网页形式分享.
     * Twitter: 优先应用内分享.
     * Instagram: 默认客户端分享.
     * */
    Automatic,
     /**
      * 原生应用分享.
      * Facebook、Twitter: 无回调.
      ** */
    Native,
     /**
      * 网页分享, 有回调, 仅对Facebook生效.
       * */
    Web,
     /**
      * 反馈网页形式分享, 有回调, 仅对Facebook生效.
      * */
    Feed,
     /**
      * 调用 Android 系统分享.
      * */
    System,
        
}

ShareContentType:

Java:

public enum ShareContentType {
    Webpage,
    Photo,
    Video,
    Text,
    Media,
    File,
    Music,
    App
}

配置文件

src/main 目录下新建资源文件夹 assets, 并创建 RShare.xml 配置文件, 文件内容如下:

<Platform>
    <Twitter
        ConsumerKey= yourKey
        ConsumerSecret= yourAppSecret
    />
    <Facebook
        AppID= yourAppID
        AppSecret= yourAppSecret
    />
    <WeChat
        AppId= yourAppID
        AppSecret= yourAppSecret
    />
    <Sina
        AppKey= yourAppKey
        AppSecret= yourAppSecret
        RedirectUrl= yourRedirectUrl
        Scope="all"
    />
    <QQ
        AppID= yourAppID
        AppSecret= yourAppSecret
        />
    <Tumblr
        ConsumerKey= yourKey
        ConsumerSecret= yourAppSecret        
        FlurryKey= yourFlurryKey
    />
    <Pinterest
        AppId= yourAppID
        AppSecret= yourAppSecret
    />

</Platform>

关于哪些字段不需要, 在后面会讲明.

工具类 RPlatformHelper 中封装了解析 RShare.xml 并获取各个平台 Idkey 以及 secret 信息的方法.

图片的处理

与 iOS 不同的是, Android 端平台分享图片的通过图片的 Url 来进行参数的包装到分享. 在此, 我封装的接口传图片参数是以 Bitmap 形式传值的, 然后保存图片数据到一个文件夹, 获取数据的 Url 包装到平台 SDK 或者包装到 Intent 中分享.

⚠️ 假如这个图片是网络请求下来的, 那么完整的分享步骤就是:

image.png

多出一步再保存, 造成不必要的开销.

Java 版本中, 保存图片的接口封装到工具类 RFileHelper 中; Kotlin 版本中, 用顶层函数替代了工具类, 整合到了 RFileHelper.kt 文件中:

保存图片:

Java:

RFileHelper.saveBitmapToExternalSharePath(context, targetImage);

Kotlin:

saveBitmapToExternalSharePath(context, targetImage)

删除图片:

Java:

RFileHelper.deleteExternalShareDirectory(context);

Kotlin:

deleteExternalShareDir(context)

文件共享

从 Android 7.0 开始, 其他应用使用本应用的某文件 file:// 格式的 URI 会抛 FileUriExposedException 的异常(新浪 SDK 就存在这个问题), 所以在 RFileHelper 中封装好了对应方法:

Java:

RFileHelper.detectFileUriExposure();

Kotlin:

detectFileUriExposure()

QQ

准备

分享需要注册平台, 腾讯开发者主页, SDK 下载, QQ SDK 目前不支持 compile 集成, Android API 调用说明文档.

集成

a. 手动添加 SDK 到 libs 文件夹, 并:

image.png

b. 在 AndroidManifest.xml<application> 节点下增加:

<activity
       android:name="com.tencent.tauth.AuthActivity"
       android:noHistory="true"
       android:launchMode="singleTask" >
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="tencentYOURAPPID" />
    </intent-filter>
</activity>

c. 添加以下权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

接口及内部实现

a. 内部初始化 SDK

Java:

private Tencent mTencent;
mTencent = Tencent.createInstance(appId, context);

Kotlin:

private lateinit var mTencent : Tencent
mTencent = Tencent.createInstance(appId, context)

仅做分享功能的话, secret 字段无用.

b. 分享

QQ Android SDK 分享是通过 Bundle 进行数据包装传输, 在这里分享音乐、网页、应用等等的键值对都封装在 QQShare.class 中, 很容易混淆和误会, 完善内容分享的时候一定要参照官方的示例程序!

QQ 客户端和 QQ 空间的分享模型都封装在 RQqHelper 中, 但是分享启动的 Activity 将 QQ 客户端和 QQ 空间的分开, 分别是 RQqActivityRQZoneActivity 在分享 Activity 中实现接口 IUiListener 用来监听分享结果.

另, Android 端不支持直接打开‘我的收藏’和‘数据线(我的电脑)’分享, 需自行选择, 并且不支持纯文字分享.

⚠️最后, QQ 分享一定要在主线程里完成!!! 获取 mainHandler 已封装到了 RThreadManager 中(写法有误或者写法有不足请严厉指出):

Java:

内部:
private static Object mMainHandlerLock = new Object();
public static Handler getMainHandler() {
    if (mManinHandler == null) {
        synchronized (mMainHandlerLock) {
            if (mManinHandler == null) {
                mManinHandler = new Handler(Looper.getMainLooper());
                }
            }
        }
    return mManinHandler;
}
调用:
RThreadManager.getMainHandler().post(new Runnable() {
    @Override
    public void run() {
        /// ...
    }
});

Kotlin:

内部:
private var mMainHandlerLock = Any()
internal val mainHandler : Handler = Handler()
    get() {
        if (field == null) {
            synchronized(mMainHandlerLock) {
                if (field == null) {
                    field = Handler(Looper.getMainLooper())
                }
            }
        }
        return field
    }
调用:
mainHandler.post(
    Runnable {
        kotlin.run {
            /// ...
       }
 )

网页分享:

Java:

RQqManager.getInstance().shareWebpage(context, webapgeUrl, title, description, thumbImage, listener);

Kotlin:

RQqManager.instance.shareWebpage(context, webpageUrl, title, description, thumbImageUrl, callback)

表现:


image.png

图片分享:

Java:

RQqManager.getInstance().shareImage( context, targetImage, listener);

Kotlin:

RQqManager.instance.shareImage(context, targetImage, callback)

表现:


image.png

音频链分享:

大致和网页的分享相同, 但是多了一个 audioStreamUrl 的字段.

Java:

RQqManager.getInstance().shareMusic(context, audioStreamUrl, musicWebapgeUrl, title, description, thumbImage, listener);

Kotlin:

RQqManager.instance.shareMusic(context, audioStreamUrl, musicWebpageUrl, title, description, thumbImageUrl, callback)

⚠️ 1. 在自定义 Bundle 包装分享音频参数的时候, QQShare.SHARE_TO_QQ_TARGET_URL 设置音频网页链接, QQShare.SHARE_TO_QQ_AUDIO_URL 设置音频流链.
⚠️ 2. audioStreamUrl 请设置音频的音频流链接, 不要把音乐平台分享的音乐网链直接赋值在该字段上, 否则点击播放按钮是无法播放的, 音乐网链是放在 webpageUrl 字段上的.

表现:


image.png

应用分享:

Java:

RQqManager.getInstance().shareApp(context, appUrl, title, description, thumbImage, listener);

Kotlin:

RQqManager.instance.shareApp(context, appUrl, title, description, thumbImageUrl, callback)

点击消息对话框会自动跳转到「应用宝」对应 App 主页.

分享网页到空间:

Java:

RQqManager.getInstance().shareWebpageToZone(context, webapgeUrl, title, description, imageUrlList, listener);

Kotlin:

RQqManager.instance.shareWebpageToZone(context, webpageUrl, title, description, imageUrlList, callback)

⚠️ 1. 这里的缩略图数组里面是图片的网络链接.
⚠️ 2. 缩略图图片是以数组包装的, 但是仅仅会显示第一张, 也就是说目前的 SDK (3.3.3 版本) 是不具备网络多图分享的, 但腾讯已说明多网络图后面会完善和补全.

表现:

image.png

分享图片到空间:

Java:

RQqManager.getInstance().publishImagesToZone(context, targetImages, description, listener);

Kotlin:

RQqManager.instance.publishImagesToZone(context, targetImages, description, callback)

description 字段实际是失效的.

表现:

image.png

分享本地视频到空间:

Java:

RQqManager.getInstance().publishVideoToZone(context, localVideoUrl, description, listener);

Kotlin:

RQqManager.instance.publishVideoToZone(context, localVideoUrl, description, callback)

表现:

image.png

微信

准备

分享需要注册平台, 微信开放平台, SDK 下载, 微信 SDK 支持 compile 集成, 分享 & 收藏 API 调用说明.

集成

a.
Application 级 build.gradle 中配置:

dependencies {
    compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+'
}

或者

dependencies {
    compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}

前者包括统计功能.

b. 添加以下权限:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

c. 当需要在分享完毕后接受微信的传值需要在你的包名相应目录下新建一个 wxapi 目录,并在该 wxapi 目录下新增一个 WXEntryActivity 类,该类继承自 Activity, 在 AndroidManifest.xml<application> 节点下增加:

<activity
    android:name=".wxapi.WXEntryActivity"
    android:exported="true"
/>

接口及内部实现

a. 内部初始化 SDK

Java:

IWXAPI mIwxapi = WXAPIFactory.createWXAPI(context, appId);
mIwxapi.registerApp(appId);

Kotlin:

val mIWXApi = WXAPIFactory.createWXAPI(context, appId)
mIWXApi.registerApp(appId)

仅做分享功能的话, secret 字段无用.

b. 分享

微信分享的内容包装同样通过一个 RWechatHelper 进行单独处理, 对于文字、网页、图片、视频网链、小程序、文件的分享内容处理都很简单, 特别注意的就是对于音乐链的分享, 同 QQ 一样, 需要区别两个参数, 一个是音频流链, 一个是音频网页, 播放的是音频流, 点击背景进入的是音频网页:

Java:

WXMusicObject obj = new WXMusicObject();
obj.musicUrl = webpageUrl;
obj.musicDataUrl = streamUrl;

Kotlin:

val obj : WXMusicObject = WXMusicObject()
obj.musicUrl = webpageUrl
obj.musicDataUrl = streamUrl

在程序需要接受微信发送的请求的时候, 需要在 WXEntryActivity 中实现 IWXAPIEventHandler 接口,微信发送的请求将回调到 onReq 方法,发送到微信请求的响应结果将回调到 onResp 方法, 在 WXEntryActivity 中将接收到的 intent 及实现了 IWXAPIEventHandler 接口的对象传递给 IWXAPI 接口的 handleIntent 方法. 为了让 RWechatManager 处理结果, 在 RWechatManager 中封装了 onResp 方法.

文字分享:

Java:

RWechatManager.getInstance().shareText(context, description, scene, listener);

Kotlin:

RWechatManager.instance.shareText(context, description, scene, callback)

表现(分享到好友):

image.png

图片分享:

Java:

RWechatManager.getInstance().shareImage(context, targetImage, scene, listener);

Kotlin:

RWechatManager.instance.shareImage(context, targetImage, scene, callback)

⚠️ 图片过大无法调起微信客户端分享.

表现(分享到好友):

image.png

网页分享:

Java:

RWechatManager.getInstance().shareWebpage(context, webapgeUrl, title, description, thumbImage, scene, listener);

Kotlin:

RWechatManager.instance.shareWebpage(context, webpageUrl, title, description, thumbImage, scene, callback)

⚠️ 1. thumbImage 字段的缩略图大小不能超过 32 Kb, 但实际测试 100 Kb 左右也是完全可行的, 但一定不能过大.
⚠️ 2. Android 对于缩略图的处理相对友好很多, 对于 iOS 无法分享的过大的缩略图数据一般情况下 Android 都能成功启动微信客户端并且成功分享(亲测).
⚠️ 3. 在缩略图没有符合规范的时候, 即使成功分享在 iOS 端也会出现缩略图不显示的情况, Android 几乎都会显示.

表现(分享到好友):


image.png

视频链分享:
实质就是网页的分享, 在此不作代码示例.

音频链分享:

Java:

RWechatManager.getInstance().shareMusic(context, audioStreamUrl, title, description, thumbImage, musicWebapgeUrl, scene, listener);

Kotlin:

RWechatManager.instance.shareMusic(context, audioStreamUrl, title, description, thumbImage, musicWebapgeUrl, scene, callback)

表现(分享到好友):

image.png

小程序分享:

Java:

RWechatManager.getInstance().shareMiniProgram(context, userName, path, MiniProgramType, webapgeUrl, title, description, thumbImage, scene, listener);

Kotlin:

RWechatManager.instance.shareMiniProgram(context, userName, path, MINIPROGRAM_TYPE, webpageUrl,title, description, thumbImage, scene, callback)

⚠️ 1. demo 中笔者并没有编写小程序, 仅仅依照微信的 SDK 进行了参数的设置, 所以无法分享, 但具体的参数设置形式在 demo 中明确标明.
⚠️ 2. 小程序分三种类型, type 字段分: 发布预览以及体验三个版本.

文件分享:

Java:

RWechatManager.getInstance().shareFile(context, localFileUrl, title, thumbImage, scene, listener);

Kotlin:

RWechatManager.instance.shareFile(context, localFileUrl, title, thumbImage, scene, callback)

⚠️ 1. 视频文件无法打开, 需要别的应用支持, 图片和普通文档可以打开.
⚠️ 2. 文件分享不支持分享到朋友圈.
⚠️ 3. 在设置缩略图的情况下, iOS 端的对话框是不显示缩略图的, 但是 Android 可以, 但也仅限于发送方能看见.

表现(分享视频到微信好友):


image.png
image.png

新浪

准备

分享需要注册平台, 新浪开放平台, SDK 下载, 新浪 SDK 支持 compile 集成, Android 接口调用文档.

集成

a.
Project 级 build.gradle 中配置:

allprojects {
    repositories {
        google()
        jcenter()
        maven { url "https://dl.bintray.com/thelasterstar/maven/" }
    }
}

Application 级 build.gradle 中配置:

dependencies {
    compile 'com.sina.weibo.sdk:core:4.3.0:openDefaultRelease@aar'
}

接口及内部实现

a. 内部初始化 SDK

Java:

WbSdk.install(context, new AuthInfo(context, key, redirectUrl, scope));
WbShareHandler handler = new WbShareHandler((Activity) context);
handler.registerApp();

Kotlin:

WbSdk.install(context, AuthInfo(context, key, redirectUrl, scope))
val handler = WbShareHandler(context as Activity)
handler.registerApp()

仅做分享的话, secret 字段无用.

b. 分享

在 demo 中, 发布微博和发布照片/视频到「微博故事」笔者拆分到 RSinaWeiboActivityRSinaWeiboStoryActivity 中分别处理, 避免稍微麻烦的判断.

新浪分享内容模型都继承自 BaseMediaObject: TextObjectMultiImageObjectWebpageObjectVideoSourceObjectTextObject 设置对应属性然后通过 WeiboMultiMessage 发送分享请求消息体, 值得一提的是, 在分享网络链接时候, 赋值到消息体的属性为 mediaObject.

⚠️ 在进行本地视频分享的时候一定一定要先对消息体进行文字分享模型赋值, 否则无论如何都无法分享.

例 (Java):

✅
WeiboMultiMessage message = new WeiboMultiMessage();
message.textObject = textObj;// a TextObject instance
message.videoSourceObject = localVideoObj;
❌
WeiboMultiMessage message = new WeiboMultiMessage();
message.videoSourceObject = localVideoObj;

文字分享:

Java:

RSinaWeiboManager.getInstance().shareText(context, text, listener);

Kotlin:

 RSinaWeiboManager.instance.shareText(context, text, callback)

表现:


image.png

图片分享:

Java:

RSinaWeiboManager.getInstance().sharePhoto(context, targetImages, description, isToStory(true or false), listener);

Kotlin:

RSinaWeiboManager.instance.sharePhoto(context, targetImages, description, isToStory(true or false), callback)

为统一接口, 图片用数组包装; 分享到「微博故事」功能中 descripiton 字段会失效.

⚠️ 1. 开启「分享到微博故事」功能图片只能传一张; 多张图片分享的情况下「分享到微博故事」的功能必须关闭!
⚠️ 2. 图片单张不能超过 10 MB, 最多 9 张图片.
⚠️ 3. 在分享图片的时候最好提前打开微博客户端, 否则会很慢, 或者压根不调起客户端.

表现:
分享到微博:


image.png

分享到「微博故事」:


image.png

本地视频分享:

Java:

RSinaWeiboManager.getInstance().shareLocalVideo(context, localVideoUrl, description, isToStory(true or false), listener);

Kotlin:

RSinaWeiboManager.instance.shareLocalVideo(context, localVideoUrl, description, isToStory(true or false), callback)

表现(分享到微博):

image.png

表现(分享到「微博故事」):


image.png

网页分享:

Java:

RSinaWeiboManager.getInstance().shareWebpage(context, webapgeUrl, title, description, thumbImage, listener);

Kotlin:

RSinaWeiboManager.instance.shareWebpage(context, webpageUrl, title, description, thumbImage,callback)

表现:

image.png

⚠️ 1. 网页 thumbImage 字段的缩略图数据不能大于 550 Kb (新浪的 SDK 控制的很严格).
⚠️ 2. 在这里表现和 iOS 略有不同.

Facebook

准备

分享需要注册平台, Facebook 开发者主页, Facebook SDK 支持 compile 集成, 分享接口调用说明.

集成

a.
Project 级 build.gradle 中配置:

buildscript {
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
}

Application 级 build.gradle 中配置:

dependencies {
    implementation 'com.facebook.android:facebook-share:4.34.0'
}

b. 在 AndroidManifest.xml<application> 节点下增加:

<meta-data android:name="com.facebook.sdk.ApplicationId"
            android:value="@string/facebook_app_id"/>

<provider android:authorities="com.facebook.app.FacebookContentProviderYOURAPPID"
          android:name="com.facebook.FacebookContentProvider"
          android:exported="true" />
<activity android:name="com.facebook.CustomTabActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data android:scheme="fbYOURAPPID" />
    </intent-filter>
</activity>

接口调用及内部实现

a. 内部初始化 SDK

Facebook 分享在配置 AndroidManifest.xml 后无需代码代码初始化.

b. 分享

参数的包装, 借助工具类 RFacebookHelper 来处理, Facebook 已经封装好了各个类型的分享内容载体 (SharePhotoContent、ShareVideoContent ...), 对应设置属性值就可以; 对于图片的分享, 和新浪的图片分享思路相同, 即: 无论是单张还是多图, 一概通过数组包装传递给 RFacebookHelper, 然后它自行处理.

网页分享:

Java:

RFacebookManager.getInstance().shareWebpage(context, webapgeUrl, description, hashTag, mode , listener);

Kotlin:

RFacebookManager.instance.shareWebpage(context, webpageUrl, description, hashTag, mode, callback)

Facebook 的 SDK 在迭代的过程中, 舍弃了很多字段, 分享参数不如新浪那样多样, 但是其分享的表现形式却比国内的要友好很多, 特别是在网页分享这块体现的更加明显, 值得注意的是, 非网页形式的分享回调无效, 努力地在 GithubStack OverflowFacebook Developer 论坛找答案但都没有找到解决办法, 根据 postId 的判断分享结果状态的方法早已失效, 无论是 Android 还是 iOS 端, 目前都没办法.

⚠️ hashTag (话题) 的表现形式不同于国内, 新浪的话题格式: #话题#, 两个 # 之间一切内容都能成为话题, 而 Twitter、Instagram、Facebook 的格式: #话题, 话题内容词组之间不能有任何符号且必须连在一起.

表现:
客户端形式的分享(无回调):


image.png

iOS 端是跳转到 Facebook 客户端分享, 且在通过客户端分享的过程中, quote 字段已经丢失; 且 Android 通过客户端分享需要提前打开 Facebook 客户端, 否则无法弹出分享对话框, 测试过美图秀秀的图片分享, 也是一样, 需要提前打开客户端.

网页形式的分享(有回调):


image.png

图片分享:

Java:

RFacebookManager.getInstance().sharePhoto(context, targetImages);

Kotlin:

RFacebookManager.instance.sharePhoto(context, targetImages)

表现:


image.png

本地视频分享:

Java:

RFacebookManager.getInstance().shareLocalVideo(context, localVideoUrl);

Kotlin:

RFacebookManager.instance.shareLocalVideo(context, localVideoUrl)

表现:


image.png

c. 获取散列

RFacebookManager 提供了获取散列的方法.

Twitter

准备

分享需要注册平台, Twitter 开发者主页, 注册应用主页, Twitter SDK 支持 compile 集成, 分享接口调用说明.

⚠️: Twitter SDK 将于 2018/10/31 后不再进行维护, 但是不影响后续使用, 需自行维护, Twitter 产品经理 Neil Shah 对 Twitter SDK 放弃维护迭代的声明博客.

集成

a. 在 Project 级 build.gradle 中配置:

allprojects {
    repositories {
        google()
        jcenter()
    }
}

Application 级 build.gradle 中配置:

dependencies {
    implementation 'com.twitter.sdk.android:tweet-composer:3.1.1'
    implementation 'com.twitter.sdk.android:twitter-core:3.1.1'
}

接口调用及内部实现

a. 内部初始化 SDK

Java:

TwitterConfig config = new TwitterConfig.Builder(context)
                .logger(new DefaultLogger(Log.DEBUG))
                .twitterAuthConfig(new TwitterAuthConfig(key, secret))
                .debug(true)
                .build();
Twitter.initialize(config);

Kotlin:

val config = TwitterConfig.Builder(context)
                .logger(DefaultLogger(Log.DEBUG))
                .twitterAuthConfig(TwitterAuthConfig(key, secret))
                .debug(true)
                .build()
Twitter.initialize(config)

b. 授权 Twitter 客户端
与其他平台分享不同的是, Twitter 在进行发推(分享)的时候会先进行检测本地的 activeSession 的标记判断是否登录(授权)过, 所以在进行发推的时候需要进行这一步的判断, 在未登录的情况下需进行授权, 在此使用 RTwitterAuthHelper 进行处理, 登录(授权监听接口以及 callback):

Java:

public interface RTwitterAuthCallback {
    public abstract void onComplete();
    public abstract void onFail(String errorInfo);
}

Kotlin:

internal typealias RTwitterAuthCallback = ((state : Int) -> Unit)

Kotlin 的 callback state 包括成功失败两种结果.

判断是否登录过:

Java:

RTwitterAuthHelper.getInstance().hasLogged()

Kotlin:

RTwitterAuthHelper.instance.hasLogged

登录授权:

Java:

RTwitterAuthHelper.getInstance().authorizeTwitter(context, new RTwitterAuthCallback() {
    @Override
    public void onComplete() {
        // ...
    }

    @Override
    public void onFail(String errorInfo) {
       // ...                    }
});

Kotlin:

RTwitterAuthHelper.instance.authorizeTwitter(context) { state ->
    when (state) {
        1 -> {
            // ...
        }
        0 -> {
            // ...     
        }
    }
}

这一步目前的情况就是把 Twitter SDK 提供的授权方法重新包装写了遍, 但是考虑到未来可能用到 sessiontoken 等信息并处理, 所以单独写了类讲授权和分享隔离.

c. 分享

在本人写的 demo 中, 分享登录授权是衔接的, 即: 若未登录过 -> 登录授权 -> 分享.
Twitter 能分享的内容相对较少, 所以关于文字、网页、图片的分享, 统一到一个分享接口里, 三者不能同时为空.
另, Twitter Android 分享包括两种模式: 直接构建 Intent 分享和跳转 Twitter 客户端分享, 前者有回调(Demo 中注册 RTwitterTweetResultReciver 监听回调), 后者无回调且支持 hashTag.

Java:

RTwitterManager.getInstance().share(context, webapgeUrl, description, targetImage, hashTag, mode, listener);

Kotlin:

RTwitterManager.instance.share(context, webpageUrl, description, targetImage, hashTag, mode, callback)

⚠️ 测试多次发推内容不要重复, 否则会分享失败.

表现:

应用内分享(有回调):


image.png

跳转 Twitter 客户端分享(无回调):

image.png

Instagram

准备

分享无需注册平台无需 SDK, Instagram 开发者主页, 构建 Intent 方式分享.

接口调用及内部实现

分享

Intent 构建(以 Java 为例):

图片:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM, targetImageUrl);
intent.setPackage("com.instagram.android");
context.startActivity(intent);

图片分享:

Java:

RInstagramManager.getInstance().shareImage(context, targetImage);

Kotlin:

RInstagramManager.instance.shareImage(context, targetImage)

表现:


image.png

本地视频:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, localVideoUrl);
intent.setPackage("com.instagram.android");
context.startActivity(intent);

本地视频分享:

Java:

RInstagramManager.getInstance().shareVideo(context, localVideoUrl);

Kotlin:

RInstagramManager.instance.shareVideo(context, localVideoUrl)

表现:

image.png

Tumblr

准备

分享需要注册平台, Tumblr 开发者主页, 注册应用主页, Tumblr SDK 支持 compile 集成, 分享接口调用说明.

集成

a. 此 Demo 中是手动集成的 6.1.0 版本的 SDK.

⚠️: 一定是这个版本的, 最新版本的 SDK 我没有找到分享的接口.

接口调用及内部实现

a. 内部初始化 SDK

FlurryAgent.setLogEnabled(true);
FlurryAgent.init(context, flurryKey);
TumblrShare.setOAuthConfig(key, secret);

⚠️ 在 iOS 端初始化 SDK 仅需要 keysecret 两个参数.

b. 分享

Tumblr 分享体只包括图片文字两种, 并且图片还是网络图片的链接, 并不能分享本地图片, 所以 Tumblr 的局限性很大, 这两种分享体的模型通过 SDK 中 PhotoPostTextPost 来构建;
Tumblr 分享是通过当前界面弹出对话框分享的, 和 Twitter 类似, 所以不需要判断 Tumblr 程序是否安装;
Tumblr 的分享流程是: 登录 -> 分享, 但是登录的逻辑不需要在代码中实现, 他会自动呈现浏览器的登录界面.

文字分享:

Java:

RTumblrManager.getInstance().shareText(context, description, title, webapgeUrl, listener);

Kotlin:

RTumblrManager.instance.shareText(context, description, title, webpageUrl, callback)

表现:

image.png

图片链接分享:

Java:

RTumblrManager.getInstance().shareImage(context, targetImageUrl, description, webapgeUrl, listener);

Kotlin:

RTumblrManager.instance.shareImage(context, targetImageUrl, description, webpageUrl, callback )

表现:

image.png

Pinterest

集成

Android 端无需集成 SDK, 仅通过 Intent 方式就可以分享, 但是 iOS 需要.

b. 分享

Pinterest 只能分享图片链接.

Intent 构建(以 Java 为例):

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setPackage("com.pinterest");
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse(targetImageUrl));
intent.setType("*/*");
context.startActivity(intent);

图片链接分享:

Java:

RPinterestManager.getInstance().shareImage(context, targetImageUrl);

Kotlin:

RPinterestManager.instance.shareImage(context, targetImageUrl)

表现:


image.png

Line

集成

无需集成 SDK, 仅通过 Intent 方式就可以分享.

b. 分享

Intent 构建(以 Java 为例):

文字:

Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, text);
intent.setType("text/*");
intent.setPackage("jp.naver.line.android");
context.startActivity(intent);

文字分享:

Java:

RLineManager.getInstance().shareText(context, text);

Kotlin:

RLineManager.instance.share(context, text)

表现:


image.png

图片:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM, targetImageUrl);
intent.setPackage("jp.naver.line.android");
context.startActivity(intent);

图片分享:

Java:

RLineManager.getInstance().shareImage(context, targetImage);

Kotlin:

RLineManager.instance.share(context, targetImage)

表现:

image.png

WhatsApp

准备

分享无需注册平台.

Android 端无需集成 SDK, 仅通过 Intent 方式就可以分享, 但是 iOS 需要.

b. 分享

WhatsApp 支持同时分享文字和图片, iOS 端不可以.

Intent 构建(以 Java 为例):

Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, description);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_STREAM, targetImageUrl);
intent.setType("image/jpeg");
intent.setPackage("com.whatsapp");
context.startActivity(intent);

图文分享:

Java:

RWhatsAppManager.getInstance().share(context, targetImage, description);

Kotlin:

RWhatsAppManager.instance.share(context, targetImage, description)

表现:

image.png

GooglePlus

准备

需要注册平台, Google Plus 开发者主页
, 创建流程.

集成

需要 Google Service 的支持, 在 Application 级 build.gradle 中配置:

dependencies {
    implementation 'com.google.android.gms:play-services-plus:15.0.1'
}

接口调用及内部实现

Google Plus 分享只支持分享网页, 通过 PlusShare.Builder 来构建网页分享模型, Java 为例:

Intent shareIntent = new PlusShare.Builder(context)
                .setType("text/plain")
                .setText(text)
                .setContentUrl(Uri.parse(webpageUrl)
                .getIntent();

分享网页:

Java:

RGooglePlusManager.getInstance().share(context, webapgeUrl, description);

Kotlin:

RGooglePlusManager.instance.share(context, webpageUrl, description)

表现:

image.png

统一分享接口

缺陷

缺陷说在前面, 其实本来不打算统一接口的.

  • 主分享 Manager 和子平台分享 Manager 存在耦和(没有参考 ShareSDK 的 jar 包方式去做);
  • 分享接口优化受限制, 由于前面的平台分享对比表格可知, 国外的平台分享很多都没有回调, 而国内的平台分享内容又存在多种形式, 无法实现高度统一;
  • 添加平台没有做去重处理, 造成不必要的开销;

类图

Android 统一接口类图
  • RShareManger: 主分享 Manager, 子平台 Manager 的初始化、分享、应用跳转和一些其他操作都在此进行;
  • RImageContent、RVideoContent、RTextContent、RWebpageContent 为四种对应分享内容模型.

详细设计

分享频道

以 Java 为例, 在 RShareManager 种定义了分享通道枚举:

enum class ShareChannel {
    QQSession,
    QQFavorite,
    QQDataLine,
    QZone,
    WechatSession,
    WechatFavorite,
    WechatTimeline,
    FacebookClient,
    FacebookBroswer,
    TwitterInnerApp,
    TwitterClient,
    SinaWeibo,
    SinaWeiboStory,
    Line,
    Instagram,
    Tumblr,
    Pinterest,
    GooglePlus,
    WhatsApp
}

接口

构建分享模型

RImageContent 为例:

Java:

RImageContent content = new RImageContent.Builder(targetImage, targetImageUrl)
                 // ...
                .build();

Kotlin:

val content : RImageContent = RImageContent().apply { 
            // ...
}

分享:

以分享 RImageContent 为例:

Java:

RShareManager.getInstance().shareImage(context, content, channel, new RShareListener() {
            @Override
            public void onComplete(RSharePlatform.Platform platform) {
            // ...  
            }

            @Override
            public void onFail(RSharePlatform.Platform platform, String errorInfo) {
            // ...
            }

            @Override
            public void onCancel(RSharePlatform.Platform platform) {
            // ...
            }
        });

Kotlin:

 RShareManager.instance.shareImage(context, content, channel) { platform, state, errorInfo ->
           // ...
}

源码

Java 版本源码Kotlin 版本源码, 还在学习中, 请多指教.

无关

个人博客, 还在学习中, 请多指教.

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 122,762评论 15 534
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 6,087评论 1 35
  • 因为公司的项目里集成了一键分享的这个模块, 而在我设计的时候发现国内的官方文档和提供的 Sample 有混乱和容易...
    valentizx阅读 973评论 4 1
  • 1、将系统提示文字改为中文 很多情况下,双击UITextField系统会默认弹出Paste,copy等,还有打开相...
    ibiaoma阅读 39评论 0 0
  • 初冬,天黑得早。我在长青树休闲餐厅临街窗边的小桌旁等女友。蒙蒙的细雨打湿了路面,凄清的街道上人们行色匆匆。 我不知...
    陶北1966阅读 22评论 0 1