前言
提到分享,几乎所有应用都会有的功能,虽然说不是难点,但是还是要有。最近公司也在做分享功能,以前也做过很多次分享,基本都是集成第三方分享SDK,然后按照开发文档步骤完成。隔一段时间就会忘记来龙去脉,再次做分享,还要去查看官方文档。因此,本次准备花点时间封装第三方分享SDK,这样做的好处是方便以后使用,另一方面也可以自己学习到一些新的东西。分享有两种方式,一种是第三方分享SDK,第二种是隐士跳转方式,用于隐士跳转方式有某些局限,一方面是QQ空间分享不对外开放,另一方面是第三方分享SDK额分享监听做的比较全面,不需要再次自己去封装。因此本文主要介绍关于第三方分享SDK的封装,,后面也会把原生的封装带上。本文同样采用建造者模式创建,结构简单清晰,本文省略申请账号过程。基本使用如下:
测试分享.pngShareKeeper.getInstance()
.builder(this)
.setPlatform(ShareKeeper.PLATFORM_QZONE)
.setShareType(ShareKeeper.TYPE_DEFAULT)
.setTitle(title)
.setDesc(desc)
.setImageUrl(imageUrl)
.setImagePath(localPicPath)
.setWebUrl(webUrl)
.setAVdioUrl(videoUrl)
.setAVdioPath(localVideoPath)
.setOnShareListener(this)
.share();
属性介绍
可能看到上面的使用可能会觉得那么多属性,肯定很复杂,其实不然。上面举例是把所有的属性都列举出来了,但是实际用不到那么多属性。下面就来介绍所有的属性。
builder(this) :构建建造者模式
setPlatform(): 设置分享平台,例如:QQ、微信、QQ空间等 (可以不填,默认QQ分享)
setShareType():设置分享的类型,根据形式分为,图文、纯图片、音视频、纯文本(此分类不已第三方分享SDK分类),可以不设置,默认为图文类型
setTitle(title):设置标题,可以不设置(内部已经做了防止空指针,但是建议设置,或者设置appname亦可)
setDesc(desc):设置简介,设置同上
setImageUrl(imageUrl):设置网络图片路径
.setImagePath(localPicPath):设置本地图片路径
setAVdioUrl(videoUrl):设置网络音视频链接
setAVdioPath(localVideoPath):设置本地音视频地址
setWebUrl(webUrl):设置分享的链接(除了分享图片之外,都是必备的属性)
setOnShareListener(this):设置分享监听回调(建议回调)
微信分享
检测客户端
/**
* 手机是否安装微信客户端
*
* @param context
* @return
*/
public static boolean isWeixinAvilible(Context context) {
final PackageManager packageManager = context.getPackageManager();// 获取packagemanager
List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);// 获取所有已安装程序的包信息
if (pinfo != null) {
for (int i = 0; i < pinfo.size(); i++) {
String pn = pinfo.get(i).packageName;
if (pn.equals("com.tencent.mm")) {
return true;
}
}
}
return false;
}
微信属性介绍
WXMediaMessage.IMediaObject :微信分享SDK内部做了分享的封装类,包括文本、图片、视频等
WXMediaMessage :携带IMediaObject 以及标题、描述、缩略图信息
msg.title :标题
msg.description :描述
msg.thumbData :缩略数据(图片内容)
SendMessageToWX.Req:携带WXMediaMessage 和分享数据
transaction :分享携带数据,相当于类型,回调到WXEntryActivity为BaseResp resp中的参数,可以做过滤作用
message :携带WXMediaMessage
scene :场景,即分享平台
创建不同的类型
由于微信分享和朋友圈分享没有区别,所以在一起统一封装如下:
/**
* 创建分享的IMediaObject
*
* @param builder
* @param bitmap
* @return
*/
private static WXMediaMessage.IMediaObject createIMediaObject(ShareKeeper.Builder builder, Bitmap bitmap) {
int mPlatform = builder.mPlatform;
int mShareType = builder.mShareType;
WXMediaMessage.IMediaObject mediaObject = null;
OnShareListener mOnShareListener = builder.mOnShareListener;
if (mShareType == ShareKeeper.TYPE_DEFAULT) {
WXWebpageObject webpageObject = new WXWebpageObject();
String mWebUrl = builder.mWebUrl;
if (!TextUtils.isEmpty(mWebUrl)) {
webpageObject.webpageUrl = mWebUrl;
mediaObject = webpageObject;
} else {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(mPlatform, "分享的链接不能为空!");
}
}
} else if (mShareType == ShareKeeper.TYPE_PICTURE) {
if (bitmap != null) {
WXImageObject imgObj = new WXImageObject(bitmap);
mediaObject = imgObj;
} else {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(mPlatform, "分享的图片不能为空!");
}
}
} else if (mShareType == ShareKeeper.TYPE_AVDIO) {
String videoUrl = builder.mAVdioUrl;
if (!TextUtils.isEmpty(videoUrl)) {
WXVideoObject videoObject = new WXVideoObject();
videoObject.videoUrl = videoUrl;
mediaObject = videoObject;
} else {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(mPlatform, "分享的视频链接不能为空!");
}
}
} else if (mShareType == ShareKeeper.TYPE_TXT) {
String desc = builder.mDesc;
if (!TextUtils.isEmpty(desc)) {
WXTextObject textObj = new WXTextObject();
textObj.text = desc;
mediaObject = textObj;
} else {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(mPlatform, "分享的文本不能为空!");
}
}
}
return mediaObject;
}
说明:WXMediaMessage.IMediaObject封装了微信分享不同,所以在此根据分享类型创建不同的IMediaObject,有些必不可缺的属性都做了分享回调失败,当然也可以做抛异常的形式(不建议,程序会崩溃)。
微信分享
/**
* 执行
*
* @param bitmap
* @param iwxapi
* @param builder
*/
private static void performShare(Bitmap bitmap, IWXAPI iwxapi, ShareKeeper.Builder builder) {
OnShareListener mOnShareListener = builder.mOnShareListener;
WXMediaMessage.IMediaObject iMediaObject = createIMediaObject(builder, bitmap);
if (iMediaObject != null) {
//检测参数可以分享
boolean checkArgs = iMediaObject.checkArgs();
if (checkArgs) {
//共同的部分
WXMediaMessage msg = new WXMediaMessage(iMediaObject);
msg.title = builder.mTitle;//标题
msg.description = builder.mDesc;//描述
//缩略图
if (bitmap != null) {
byte[] thumbData = IMAGE_MAX_SIZE);
if (thumbData.length > IMAGE_MAX_SIZE) {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, "分享的缩略图过大");
}
return;
}
msg.thumbData = thumbData;//缩略数据(图片内容)
}
//共同的部分
SendMessageToWX.Req req = new SendMessageToWX.Req();
req.transaction = buildDiffTransaction(builder.mShareType);
req.message = msg;
int mScene = builder.mPlatform == ShareKeeper.PLATFORM_WECHAT ? WXSceneSession : WXSceneTimeline;
req.scene = mScene;
//携带监听
Bundle bundle = new Bundle();
bundle.putSerializable("builder", builder);
req.toBundle(bundle);
iwxapi.sendReq(req);
} else {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, "分享参数异常");
}
}
}
}
压缩图片
关于微信分享最容易出错的就是分享的图片过大,微信规定分享的缩略图大小不能超过32768K,所以分享之前必须做压缩处理,本文主要采用先质量压缩至10%,如果还超过规定分享缩略图大小再采取尺寸压缩,如果尺寸压缩之后依旧不行,可以采用第三方压缩工具或者替换图片。
/**
* 图片压缩到指定大小
*
* @param bmp
* @param maxByteSize
* @return
*/
public static byte[] compressBitmapSpecifySize(final Bitmap bmp, final long maxByteSize) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
100, baos);
byte[] bytes = null;
int options = 100;
int outSize = baos.size();
if (outSize <= maxByteSize) {
bytes = baos.toByteArray();
} else {
while (outSize > maxByteSize && options > 0) {
baos.reset(); //清空baos
options, baos);//这里压缩options%,把压缩后的数据存放到baos中
outSize = baos.size();
options -= 10;
}
if (outSize > maxByteSize) {
bytes = compressBitmapSize(bmp, options, maxByteSize);
} else {
bytes = baos.toByteArray();
}
}
if (!bmp.isRecycled()) bmp.recycle();
return bytes;
}
/**
* 需要压缩
*
* @param bmp
*/
private static byte[] compressBitmapSize(Bitmap bmp, int options, final long maxByteSize) {
//继续在当前的质量下压缩
ByteArrayOutputStream baos = new ByteArrayOutputStream();
options, baos);
int outSize = baos.size();
int orginOpi = options;
while (outSize > maxByteSize && options > 0) {
baos.reset(); //清空baos
Bitmap bitmap = BitmpUtils.scaleBitmap(bmp, options * 0.1f);
orginOpi, baos);//这里压缩options%,把压缩后的数据存放到baos中
options--;
}
byte[] bytes = baos.toByteArray();
return bytes;
}
微信分享结果处理
微信分享的结果处理是在WXEntryActivity的onResp(BaseResp resp)方法中处理各种分享返回结果
if (shareBuilder != null) {
OnShareListener onShareListener = shareBuilder.mOnShareListener;
if (onShareListener != null) {
int errCode = resp.errCode;
if (errCode == BaseResp.ErrCode.ERR_OK) {
onShareListener.onShareSuccess(shareBuilder.mPlatform);
} else if (errCode == BaseResp.ErrCode.ERR_SENT_FAILED) {
onShareListener.onShareFailed(shareBuilder.mPlatform, "发送失败");
} else if (errCode == BaseResp.ErrCode.ERR_AUTH_DENIED) {
onShareListener.onShareFailed(shareBuilder.mPlatform, "认证被否决");
} else if (errCode == BaseResp.ErrCode.ERR_UNSUPPORT) {
onShareListener.onShareFailed(shareBuilder.mPlatform, "版本不支持");
} else if (errCode == BaseResp.ErrCode.ERR_COMM) {
onShareListener.onShareFailed(shareBuilder.mPlatform, "一般错误");
} else if (errCode == BaseResp.ErrCode.ERR_USER_CANCEL) {
onShareListener.onCancleShare(shareBuilder.mPlatform, "取消分享");
} else {
onShareListener.onShareFailed(shareBuilder.mPlatform, "未知错误");
}
}
}
QQ分享
检测QQ客户端
/**
* 手机是否安装QQ客户端
*
* @param context
* @return
*/
public static boolean isQQClientAvailable(Context context) {
final PackageManager packageManager = context.getPackageManager();
List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);
if (pinfo != null) {
for (int i = 0; i < pinfo.size(); i++) {
String pn = pinfo.get(i).packageName;
if (pn.equals("com.tencent.mobileqq")) {
return true;
}
}
}
return false;
}
QQ分享属性介绍
qq分享属性全部封装在一个Bundle里面
QQShare.SHARE_TO_QQ_KEY_TYPE:分享类型
QQShare.SHARE_TO_QQ_TITLE:分享标题(默认不可以为空,内部做了防止空指针)
QQShare.SHARE_TO_QQ_SUMMARY:分享简介(设置同上)
QQShare.SHARE_TO_QQ_TARGET_URL:weburl(除了分享图片以外必须参数)
QQShare.SHARE_TO_QQ_IMAGE_URL:网络图片(缩略图,但不是单纯分享图片的地址,没有也不影响)
QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL:本地图片路径(单纯的本地图片,必要参数,一定要本地路径)
QQShare.SHARE_TO_QQ_EXT_INT, QQShare.SHARE_TO_QQ_FLAG_QZONE_AUTO_OPEN):分享qq空间特有(权限)
注意:1、qq空间的属性需要把QQ替换成QZONE
2、网络图片链接在qq分享和qq空间分享的数据装载形式不一样,qq分享直接存在Bundle中,qq空间在先存放在集合中,然后添加集合方式,具体看下面代码。
QQ分享
由于QQ不能单独分享文本,因此需要单独处理,另外QQ空间不对外开放且QQ空间不能单独分享图片和视频,因此QQ空间分享只做默认的图文分享。因此下面仅介绍QQ分享。
文本分享
//纯文本分享
Intent intent = new Intent("android.intent.action.SEND");
intent.setType(NetiveShareTask.TYPE_TXT);
intent.putExtra(Intent.EXTRA_SUBJECT,"分享");
intent.putExtra(Intent.EXTRA_TEXT,builder.mDesc+"");//不能为空
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(new ComponentName(QQ_PACKAGE_NAME,QQ_SHARE_COMPONENT_NAME));
activity.startActivityForResult(intent,TEXT_REQUEST_CODE);
图片分享
QQ分享SDK默认只支持本地图片路径分享,因此如果是网络图片,需要先下载到本地然后再去分享,本地图片分享跟其他分享区别不大,只有一点就是纯图片分享,图片不能为空,但是链接是唯一可以为空的类型,因此这里主要讲解网络图片的分享。
/**
* 分享网络图片
* 需要先下载到本地
* 然后设置到本地路径去分享
*
* @param activity
* @param builder
*/
private static void performShareNetPic(final Activity activity, final Tencent mTencent, final ShareKeeper.Builder builder) {
new Thread() {
public void run() {
OnShareListener mOnShareListener = builder.mOnShareListener;
try {
String mImageUrl = builder.mImageUrl;
//先获取图片然后保存到本地分享
Bitmap urlBitmap = BitmpUtils.getUrlBitmap(mImageUrl);
//检测生成的图片是否存在
if (urlBitmap == null && mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, builder.mShareType, "分享图片为空!");
return;
}
//SDK分享网络图片
final String saveBitmapToLocal = BitmpUtils.saveBitmapToLocal(activity, urlBitmap);
builder.setImagePath(saveBitmapToLocal);
//最后转为本地分享
performShareLocalPic(activity, mTencent, builder);
} catch (IOException e) {
e.printStackTrace();
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, builder.mShareType, "分享异常:" + e.getMessage());
}
}
}
}.start();
}
音视频分享
QQ分享SDK是支持音视频分享的,不过是分享的链接,因此如果要分享本地音视频,要特殊处理。因此此处单独做QQ本地音视频的分享讲解。此处采用系统的分享方式,通过隐士跳转分享。
/**
* QQ分享本地视频
*
* @param activity
* @param builder
*/
private static void performQQShareAVideoNative(Activity activity, ShareKeeper.Builder builder) {
String mAVdioPath = builder.mAVdioPath;
OnShareListener mOnShareListener = builder.mOnShareListener;
Uri uri = UriUtils.getUri(activity, mAVdioPath);
if (uri == null) {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, builder.mShareType, "分享本地视频地址异常!");
}
return;
}
Intent intent = new Intent("android.intent.action.SEND");
intent.setType(NetiveShareTask.TYPE_VIDEO);
intent.putExtra(Intent.EXTRA_TEXT, builder.mTitle + "");//不能为空
intent.putExtra(Intent.EXTRA_SUBJECT, builder.mDesc + "");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(new ComponentName(QQ_PACKAGE_NAME, QQ_SHARE_COMPONENT_NAME));
activity.startActivityForResult(intent, VIDEO_REQUEST_CODE);
}
其他类型分享
/**
* 分享网页 默认类型
*
* @param activity
* @param mTencent
* @param builder
*/
private static void performQQShareWeb(Activity activity, Tencent mTencent, ShareKeeper.Builder builder) {
Bundle params = new Bundle();
params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);//默认分享类型
params.putString(QQShare.SHARE_TO_QQ_TITLE, builder.mTitle + "");//分享标题(默认不可以为空,防止空指针)
params.putString(QQShare.SHARE_TO_QQ_SUMMARY, builder.mDesc + "");//分享简介(默认可以为空防止空指针)
params.putString(QQShare.SHARE_TO_QQ_TARGET_URL, builder.mWebUrl);//weburl(除了图片以外都需要)
params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, builder.mImageUrl);//网络图片(缩略图,没有也不影响)
params.putString(QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL, builder.mImagePath);//本地图片路径(单纯的本地图片分享使用)
//创建监听器
mQQShareListener = new QQShareListener(builder);
mTencent.shareToQQ(activity, params, mQQShareListener);
}
说明:
1、由于文本分享没用SDK,所以监听只以跳转成功为分享成功,无取消分享监听
2、QQ空间无法进行图片分享(建议QQ空间只做图文分享)
3、QQ分享如果单独分享图片,只能分享本地图片
4、由于SDK做了判断,因此performShare()不再做一些基本的参数判断
QQ分享结果处理
QQ分享一定要处理当前activity的onActivityResult(),否则无法监听到QQ内部的分享回调。
/**
* 处理QQ分享
*
* @param requestCode
* @param resultCode
* @param data
*/
public void performQQShareResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}
if (mShareBuilder != null) {
OnShareListener mOnShareListener = mShareBuilder.mOnShareListener;
//需要判断是否是QQ分享
if (requestCode == Constants.REQUEST_QQ_SHARE
|| requestCode == Constants.REQUEST_QZONE_SHARE
|| requestCode == Constants.REQUEST_OLD_SHARE) {
IUiListener mIUiListener = QQShareTask.getIUiListener();
if (mOnShareListener != null && mIUiListener != null) {
Tencent.onActivityResultData(requestCode, resultCode, data, mIUiListener);
}
} else if (requestCode == QQShareTask.TEXT_REQUEST_CODE
|| requestCode == QQShareTask.PIC_REQUEST_CODE
|| requestCode == QQShareTask.VIDEO_REQUEST_CODE) {
//说明是QQ原生方式分享
if (mOnShareListener != null) {
mOnShareListener.onShareSuccess(mShareBuilder.mPlatform, mShareBuilder.mShareType);
}
}
}
}
原生分享
利用原生的分享也是可以的,但是无法监听分享的结果,暂且以跳转到目标页面为分享成功,异常情况为分享失败。
原生分享文本
/**
* 分享文本
*
* @param activity
* @param builder
*/
private static void performNativeShareTxt(Activity activity, ShareKeeper.Builder builder) {
OnShareListener mOnShareListener = builder.mOnShareListener;
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType(NetiveShareTask.TYPE_TXT);
intent.putExtra(Intent.EXTRA_TEXT, builder.mDesc + "");//不能为空
Intent chooser = Intent.createChooser(intent, builder.mTitle);
if (intent.resolveActivity(activity.getPackageManager()) != null) {
activity.startActivityForResult(chooser, SHARE_REQUEST_CODE);
} else {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, builder.mShareType, "分享所在Activity异常!");
}
}
}
原生分享图片
原生分享还是分享本地图片,网络图片要下载到本地再做分享。
/**
* 原生本地图片分享
*
* @param activity
* @param builder
*/
private static void performNativeShareLocalPic(Activity activity, ShareKeeper.Builder builder) {
String mImagePath = builder.mImagePath;
OnShareListener mOnShareListener = builder.mOnShareListener;
Uri uri = UriUtils.getUri(activity, mImagePath);
if (uri == null) {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, builder.mShareType, "分享本地视频地址异常!");
}
return;
}
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType(NetiveShareTask.TYPE_IMAGE);
intent.putExtra(Intent.EXTRA_TEXT, builder.mTitle + "");//不能为空
intent.putExtra(Intent.EXTRA_SUBJECT, builder.mDesc + "");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent chooser = Intent.createChooser(intent, builder.mTitle);
if (intent.resolveActivity(activity.getPackageManager()) != null) {
activity.startActivityForResult(chooser, SHARE_REQUEST_CODE);
} else {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, builder.mShareType, "分享所在Activity异常!");
}
}
}
原生分享视频
/**
* 原生分享本地视频
*
* @param activity
* @param builder
*/
private static void performNativeShareVideo(Activity activity, ShareKeeper.Builder builder) {
String mAVdioPath = builder.mAVdioPath;
OnShareListener mOnShareListener = builder.mOnShareListener;
Uri uri = UriUtils.getUri(activity, mAVdioPath);
if (uri == null) {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, builder.mShareType, "分享本地视频地址异常!");
}
return;
}
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType(NetiveShareTask.TYPE_VIDEO);
intent.putExtra(Intent.EXTRA_TEXT, builder.mTitle + "");//不能为空
intent.putExtra(Intent.EXTRA_SUBJECT, builder.mDesc + "");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent chooser = Intent.createChooser(intent, builder.mTitle);
if (intent.resolveActivity(activity.getPackageManager()) != null) {
activity.startActivityForResult(chooser, SHARE_REQUEST_CODE);
} else {
if (mOnShareListener != null) {
mOnShareListener.onShareFailed(builder.mPlatform, builder.mShareType, "分享所在Activity异常!");
}
}
}