使用 微博、QQ、微信、釘釘 原生 SDK
接入,提供這些平臺的登陸、分享功能支持。針對業務邏輯對各個平臺的接口進行封裝,對外提供一致的表現,在減輕接入壓力的同時,又能得到原生 SDK
最大的靈活性。php
考慮到每一個平臺的SDK
也在不斷的更新,且每一個項目的需求差別比較大,如可能只須要支持部分平臺,所以沒有對類庫進行發佈,請下載GitHub
上的module
自行依賴,在類庫設計的過程當中,每一個平臺都是獨立的,若是隻須要支持部分平臺,只須要刪除platform
包下面對應的實現便可,不會對其餘平臺形成影響。
項目地址 : GitHub - SocialSdkLibraryjava
本文地址 :快速接入微信微博QQ釘釘原生登陸分享git
🎉 2018.6.7 項目得到了第100顆🌟,最後一顆是我問同事要的🤦github
🎉 2018.5.12 修復內存問題、功能擴展 穩定版本 1.1.0web
🎉 2018.2.12 支持釘釘分享json
🎉 2017.12.12 對代碼進行簡單重構並測試 穩定版本 1.0.0bash
<div style="width:100%;display: flex;height:30px;">微信
<img style="margin-right:20px;" src="https://badge.juejin.im/entry...;/>網絡
<img style="margin-right:20px;" src="https://img.shields.io/github...;/>app
<img style="margin-right:20px;" src="https://img.shields.io/github...;/>
</div>
<!--more-->
還在優化中...
🔥 簡單:只須要關注幾個管理類和相關數據的構造便可實現所需功能,不須要考慮複雜的受權和分享邏輯。
🔥 輕量:除了必須的第三方 sdk
以外,本項目只依賴了一個簡單的異步任務的框架 bolts (38k)
,後續會考慮也剔除掉,不引入無用依賴,保證與宿主項目高度統一。
🔥 全面:內部存儲受權 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); } } } };