使用 微博、QQ、微信、釘釘 原生 SDK
接入,提供這些平臺的登陸、分享功能支持。針對業務邏輯對各個平臺的接口進行封裝,對外提供一致的表現,在減輕接入壓力的同時,又能得到原生 SDK
最大的靈活性。php
考慮到每一個平臺的
SDK
也在不斷的更新,且每一個項目的需求差別比較大,如可能只須要支持部分平臺,所以沒有對類庫進行發佈,請下載GitHub
上的module
自行依賴,在類庫設計的過程當中,每一個平臺都是獨立的,若是隻須要支持部分平臺,只須要刪除platform
包下面對應的實現便可,不會對其餘平臺形成影響。java
項目地址 : GitHub - SocialSdkLibrarygit
本文地址 :快速接入微信微博QQ釘釘原生登陸分享github
🎉 2018.5.12 修復內存問題、功能擴展 穩定版本 1.1.0web
🎉 2018.2.12 支持釘釘分享json
🎉 2017.12.12 對代碼進行簡單重構並測試 穩定版本 1.0.0bash
還在優化中...微信
🔥 簡單:只須要關注幾個管理類和相關數據的構造便可實現所需功能,不須要考慮複雜的受權和分享邏輯。網絡
🔥 輕量:除了必須的第三方 sdk
以外,本項目只依賴了一個簡單的異步任務的框架 bolts (38k)
,後續會考慮也剔除掉,不引入無用依賴,保證與宿主項目高度統一。app
🔥 全面:內部存儲受權 token
,避免屢次受權;對 qq、微信、微博 作了完善的支持;
🔥 擴展性:
platform
包下該平臺的具體實現便可。AbsPlatform
接入其餘平臺分享,自定義擴展。🔥 功能性:針對實際項目需求進行擴展,例如在分享前統一對分享數據提供一次從新構造的機會。
🔥 兼容性:
web
分享兼容。Intent
兼容不支持的數據模式,如支持本地視頻分享,qq
的純文字分享等等。使用 SocialSdk 只須要關注如下幾個文件:
👉️ SocialSdk.java
結合 SocialConfig.java
用來進行受權信息的配置。
👉️ Target.java
類是單獨分離出來的常量類,指向了登陸和分享的具體目標。
👉️ LoginManager.java
用來實現 qq、微信、微博第三方受權登陸,內部存儲 accessToken
,無需屢次受權,只要調用 LoginManager.login()
方法。
👉️️ ShareManager.java
用來實現 8 種數據類型、4 個平臺、8 個渠道的分享,只要調用 ShareManager.share()
方法。
針對多方 SDK
的要求,對權限、和必要的界面、服務都已經在類庫中進行了配置,當依賴該類庫時,會自動合併,不過仍然還須要在項目的 app/build.gradle
中配置對應的 qqId
的 manifestPlaceholders
,代碼以下:
defaultConfig {
manifestPlaceholders = [qq_id: "11049xxxxx"]
}
複製代碼
關於 manifestPlaceholders
的使用
當使用 manifestPlaceholders = [qq_id: "11049xxxxx"] 的方式時,以前聲明的全部 manifestPlaceholders 都會被替換掉,只保留最後的。
當使用 manifestPlaceholders.qq_id = "11049xxxxx" 的方式時,會在原來的 manifestPlaceholders 中追加新的,同時也保留之前的。
建議的方式是,在 defaultConfig 中使用直接賦值的方式,而在 buildTypes 和 Favors 中使用追加的方式,避免將以前的覆蓋掉。
複製代碼
你須要在使用 SDK 以前進行初始化操做,建議放在 Applicaton
中進行。
String qqAppId = getString(R.string.QQ_APP_ID);
String wxAppId = getString(R.string.WX_APP_ID);
String wxSecretKey = getString(R.string.WX_SECRET_KEY);
String sinaAppId = getString(R.string.SINA_APP_ID);
String ddAppId = getString(R.string.DD_APP_ID);
SocialSdkConfig config = new SocialSdkConfig(this)
// 開啓調試
.setDebug(true)
// 配置釘釘
.dd(ddAppId)
// 配置qq
.qq(qqAppId)
// 配置微信
.wechat(wxAppId, wxSecretKey)
// 配置微博
.sina(sinaAppId)
// 配置Sina的RedirectUrl,有默認值,若是是官網默認的不須要設置
.sinaRedirectUrl("http://open.manfenmm.com/bbpp/app/weibo/common.php")
// 配置Sina受權scope,有默認值,默認值 all
.sinaScope(SocialConstants.SCOPE)
// 不加載釘釘和微博平臺
.disablePlatform(Target.PLATFORM_DD)
.disablePlatform(Target.PLATFORM_WB)
// 當縮略圖由於各類緣由沒法獲取時,將會使用默認圖,避免分享中斷
.defImageResId(R.mipmap.ic_launcher_new);
// 👮 添加 config 數據,必須
SocialSdk.init(config);
// 👮 添加自定義的 json 解析,必須,參考 temp 文件夾下的實現
SocialSdk.setJsonAdapter(new GsonJsonAdapter());
// 👮 請求處理類,若是使用了微博的 openApi 分享,這個是必須的,參考 temp 文件夾下的實現
SocialSdk.setRequestAdapter(new OkHttpRequestAdapter());
複製代碼
使用 adapter
這種模式主要參照了一些成熟的類庫,目的是爲了對外提供更好的擴展性,這部份內容能夠關注 SocialSdk.java
.
IJsonAdapter
,負責 Json
解析,爲了保持和宿主項目 json
解析框架的統一,是必須自定義添加的(沒有內置一個實現是由於使用自帶的 JsonObject
解析實在麻煩,又不想內置一個三方庫進來,採起的這種折衷方案),提供一個 Gson
下的實現僅供參考 - GsonJsonAdapter.java
IRequestAdapter
,負責請求數據,目前微信的 OAuth2
受權和圖片下載的相關請求都是使用 IRequestAdapter
代理,已經使用 URLConnection
內置了一個實現,若是你有本身的需求能夠重寫這部分,能夠參考 - OkHttpRequestAdapter.java
登錄功能支持三個平臺,qq,微信,微博;
// 3個平臺
Target.LOGIN_QQ;
Target.LOGIN_WX;
Target.LOGIN_WB;
複製代碼
使用 OnLoginListener
監聽登陸返回結果,返回的 LoginResult
中主要包括登陸類型,基本用戶信息,令牌信息 3 部分。
public class LoginResult {
// 登錄的類型,對應 Target.LOGIN_QQ 等。。。
private int type;
// 返回的基本用戶信息
// 針對登陸類型可強轉爲 WbUser,WxUser,QQUser 來獲取更加豐富的信息
private BaseUser mBaseUser;
// 本次登錄的 token 信息,openid,unionid,token,expires_in
private BaseAccessToken mBaseToken;
}
// 登錄結果監聽
mOnLoginListener = new OnLoginListener() {
@Override
public void onSuccess(LoginResult loginResult) {
Log.e(TAG, loginResult.toString());
}
@Override
public void onCancel() {
toast("登陸取消");
}
@Override
public void onException(PlatformException e) {
toast("登陸失敗 " + e.toString());
}
};
// 3個平臺
Target.LOGIN_QQ;
Target.LOGIN_WX;
Target.LOGIN_WB;
// 喚醒登錄
LoginManager.login(mActivity, Target.LOGIN_QQ, mOnLoginListener);
複製代碼
清除受權 token
,爲了不每次登陸都要求用戶打開受權界面從新點擊受權的很差體驗,類庫裏面對 token
進行了持久化的存儲,當本地 token
沒有過時時,直接使用這個 token
去請求用戶信息,同時提供了清除本地 token
的方法。
LoginManager.java
// 清除所有平臺的 token
public static void clearAllToken(Context context) // 清除指定平臺的 token public static void clearToken(Context context, @Target.LoginTarget int loginTarget) 複製代碼
請仔細查看平臺和數據類型中間的支持能力
openApi
形式去分享時,可能有較長的延時,建議在生命週期中增長進度條顯示,避免用戶等待好久沒有響應。// 發短信
ShareManager.sendSms(mActivity,"13612391817","msg body");
// 發郵件
ShareManager.sendEmail(mActivity,"1101873740@qq.com","subject","msg body");
// 打開渠道對應應用
ShareManager.openApp(mActivity,Target.PLATFORM_QQ);
複製代碼
分享支持 8 種類型的數據;若是某個平臺不兼容某種類型的分享,將會使用 web
分享的方式代替;好比微信不支持 app
分享,分享出去以後時 web
分享的模式。支持的 8 種類型分別是:
- 開啓渠道對用的 app。
- 分享文字。
- 分享圖片( jpg , png , gif )。
- 分享 app。
- 分享 web。
- 分享 music。
- 分享 video。
- 分享本地 video,使用 Intent 方式喚醒。
// 支持的分享渠道
Target.SHARE_DD; // 釘釘好友
Target.SHARE_QQ_FRIENDS; // qq好友
Target.SHARE_QQ_ZONE; // qq空間
Target.SHARE_WX_FRIENDS; // 微信好友
Target.SHARE_WX_ZONE; // 微信朋友圈
Target.SHARE_WX_FAVORITE; // 微信收藏
Target.SHARE_WB; // 新浪微博
複製代碼
分享時,咱們首先要構造分享用的數據,ShareObj
對象提供了多種靜態方法用來快速建立對應分享的類型的對象。
// 測試用的路徑
localImagePath = new File(Environment.getExternalStorageDirectory(), "1.jpg").getAbsolutePath();
localVideoPath = new File(Environment.getExternalStorageDirectory(), "video.mp4").getAbsolutePath();
localGifPath = new File(Environment.getExternalStorageDirectory(), "3.gif").getAbsolutePath();
netVideoPath = "http://7xtjec.com1.z0.glb.clouddn.com/export.mp4";
netImagePath = "http://7xtjec.com1.z0.glb.clouddn.com/token.png";
netMusicPath = "http://7xtjec.com1.z0.glb.clouddn.com/test_music.mp3";
netMusicPath = "http://mp3.haoduoge.com/sSocialSdkConfig/2017-05-19/1495207225.mp3";
targetUrl = "http://bbs.csdn.net/topics/391545021";
// 打開渠道對應app
ShareObj shareMediaObj = ShareObj.buildOpenAppObj();
// 分享文字
ShareObj textObj = ShareObj.buildTextObj("分享文字", "summary");
// 分享圖片
ShareObj imageObj = ShareObj.buildImageObj("分享圖片", "summary", localImagePath);
// 分享gif
ShareObj imageGifObj = ShareObj.buildImageObj("分享圖片", "summary", localGifPath);
// 分享app
ShareObj appObj = ShareObj.buildAppObj("分享app", "summary", localImagePath, targetUrl);
// 分享web
ShareObj webObj = ShareObj.buildWebObj("分享web", "summary", localImagePath, targetUrl);
// 分享視頻
ShareObj videoObj = ShareObj.buildVideoObj("分享視頻", "summary", localImagePath, targetUrl, localVideoPath, 10);
// 本地視頻分享、部分平臺支持
ShareObj videoLocalObj = ShareObj.buildVideoObj("分享本地視頻", "summary", localVideoPath);
// 分享音樂
ShareObj musicObj = ShareObj.buildMusicObj("分享音樂", "summary", localImagePath, targetUrl, netMusicPath, 10);
複製代碼
分享結果,使用 OnShareListener
進行檢測。OnShareListener
提供了豐富的方法來支持分享的各個階段,關於分享對象重構的操做,在下一部分說明。
public class SimpleShareListener implements OnShareListener{
@Override
public void onStart(int shareTarget, ShareObj obj) {
// 分享開始
}
@Override
public ShareObj onPrepareInBackground(int shareTarget, ShareObj obj) {
// 重構分享對象,不須要時返回 null 便可
return null;
}
@Override
public void onSuccess() {
// 分享成功
}
@Override
public void onFailure(SocialError e) {
// 分享失敗
}
@Override
public void onCancel() {
// 分享取消
}
}
複製代碼
// 喚醒分享
ShareManager.share(mActivity, Target.SHARE_QQ_FRIENDS, imageObj, mOnShareListener);
複製代碼
關於重寫分享對象,其實提供一種能在分享以前對須要分享的 ShareObj
進行統一處理的機會,相似中間插一道自定義工序,好比能夠用來解決網絡圖片沒法分享,咱們須要將它下載到本地,在進行分享,又好比圖片分享出去以前加上 app 水印等操做。
主要是重寫 OnShareListener
的 onPrepareInBackground
方法,這個方法會在分享以前首先執行,若是返回不是 null
,將會使用新建立的 ShareObj
進行分享,另外因爲考慮到可能進行耗時操做,這個方法是在子線程執行的。
@Override
public ShareObj onPrepareInBackground(int shareTarget,ShareObj obj) {
// 重構分享對象,不須要時返回 null 便可
return null;
}
複製代碼
看一個實現,主要功能是在分享以前用來將網絡圖下載到本地而後更新 ShareObj
指向的圖片地址,這樣就能夠支持網絡圖片的直接分享,固然,SocialSdk
目前已經支持網絡圖片的分享,這只是一個例子。
public class MyShareListener extends SimpleShareListener {
public static final String TAG = MyShareListener.class.getSimpleName();
private Context mContext;
private LoadingDialog mLoadingDialog;
public MyShareListener(Context context) {
mContext = context;
mLoadingDialog = new LoadingDialog(mContext);
}
@Override
public void onStart(int shareTarget, ShareObj obj) {
if (mLoadingDialog != null)
mLoadingDialog.show();
}
@Override
public ShareObj onPrepareInBackground(int shareTarget, ShareObj obj) throws Exception{
// 網絡路徑,先進行文件下載進行文件下載
ShareObjHelper.prepareThumbImagePath(obj);
// 分享照片且不是gif時加水印
if (obj.getShareObjType() == ShareObj.SHARE_TYPE_IMAGE
&& !FileHelper.isGifFile(obj.getThumbImagePath())) {
File thumbImageFile = new File(obj.getThumbImagePath());
File saveFile = new File(Constants.THUMB_IMAGE_PATH, thumbImageFile.getName());
if (!FileUtil.fileIsExist(saveFile.getAbsolutePath())) {
ImageUtils.drawWaterMarkSync(mContext, obj.getThumbImagePath(), saveFile.getAbsolutePath(), false, false);
}
obj.setThumbImagePath(saveFile.getAbsolutePath());
}
return obj;
}
@Override
public void onSuccess() {
ToastUtil.show("分享成功");
}
@Override
public void onFailure(SocialError e) {
switch (e.getErrorCode()) {
case SocialError.CODE_NOT_INSTALL:
ToastUtil.show("應用未安裝");
break;
}
L.e(TAG, "分享失敗" + e.toString());
}
@Override
public void onCancel() {
ToastUtil.show("分享取消");
}
}
複製代碼
爲了更好的統一分享失敗時返回的異常,返回的全部異常都會有一個 code
,能夠根據不一樣的 code
定位問題和給出更友好的提示。
CODE_COMMON_ERROR = 101; // 通用錯誤,未歸類
CODE_NOT_INSTALL = 102; // 沒有安裝應用
CODE_VERSION_LOW = 103; // 版本太低,不支持
CODE_SHARE_OBJ_VALID = 104; // 分享的對象參數有問題
CODE_SHARE_BY_INTENT_FAIL = 105; // 使用 Intent 分享失敗
CODE_STORAGE_READ_ERROR = 106; // 沒有讀存儲的權限,獲取分享縮略圖將會失敗
CODE_STORAGE_WRITE_ERROR = 107; // 沒有寫存儲的權限,微博分享視頻copy操做將會失敗
CODE_FILE_NOT_FOUND = 108; // 文件不存在
CODE_SDK_ERROR = 109; // sdk 返回錯誤
CODE_REQUEST_ERROR = 110; // 網絡請求發生錯誤
CODE_CANNOT_OPEN_ERROR = 111; // 沒法啓動 app
CODE_PARSE_ERROR = 112; // 數據解析錯誤
CODE_IMAGE_COMPRESS_ERROR = 113; // 圖片壓縮失敗
複製代碼
例如你能夠這麼作:
mOnShareListener = new SimpleShareListener() {
@Override
public void onFailure(SocialError e) {
showMsg("分享失敗 " + e.toString());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (e.getErrorCode() == SocialError.CODE_STORAGE_READ_ERROR) {
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
} else if (e.getErrorCode() == SocialError.CODE_STORAGE_WRITE_ERROR) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
}
}
}
};
複製代碼