Unity 多平臺原生SDK接入速覽(一):微信開放平臺
該系列將記錄我對於五個平臺(微信、QQ、Facebook、Twitter、微博)的原生SDK的調研,重點關注登入和分享。P.S. 當前並沒有 iOS 裝置,因此文章都是以 Android 平臺的接入為主,使用的 IDE 為 Android Studio。
ZeroyiQ:Unity 多平臺原生SDK接入速覽(二):QQ互聯
ZeroyiQ:Unity 多平臺原生SDK接入速覽(三):Facebook
ZeroyiQ:Unity 多平臺原生SDK接入速覽(四):Twitter
ZeroyiQ:Unity 多平臺原生SDK接入速覽(五):微博
一、前言
微信開放平臺,當前(2020-6-24)註冊賬戶必須要填寫企業資訊,還需要應用稽核。請優先解決賬戶和稽核問題,獲取到應用 AppID 和 Secret。
二、SDK接入
1. 配置環境
專案 build.gradle 中新增依賴。
dependencies {
api 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}
2. 設定許可權
AndroidManifest.xml 中設定,如果使用到掃碼登入,或者 mta(騰訊移動分析) 才需要新增以下許可權。
<!-- 掃碼登入 需要許可權--> <uses-permission android:name="android.permission.INTERNET" /> <!-- mta 需要許可權--> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
3. 初始化
要使微信能響應我們的程式,必須向微信註冊我們的應用(AppID)。
private static final String WX_ID = "應用ID(需要替換)"; // IWXAPI 是第三方app和微信通訊的openApi介面 private static IWXAPI WXAPI; private void init() { // 通過WXAPIFactory工廠,獲取IWXAPI的例項 WXAPI = WXAPIFactory.createWXAPI(activity, WX_ID, true); // 將應用的appId註冊到微信 WXAPI.registerApp(WX_ID); //建議動態監聽微信啟動廣播進行註冊到微信 activity.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { WXAPI.registerApp(WX_ID); } }, new IntentFilter(ConstantsAPI.ACTION_REFRESH_WXAPP)); }
4. 傳送請求
現在我們就可以通過 通過 IWXAPI 的 sendReq 和 sendResp 兩個方法來發送請求了。
boolean sendReq(BaseReq req);
sendReq 是第三方 app 主動傳送訊息給微信,傳送完成之後會切回到第三方 app 介面。
boolean sendResp(BaseResp resp);
sendResp 是微信向第三方 app 請求資料,第三方 app 迴應資料之後會切回到微信介面。
5. 接收請求
在與包名相同的路徑下新增一個 wxapi 目錄,並在該目錄下新增一個 WXEntryActivity 類,該類繼承自 Activity ,實現 IWXAPIEventHandler 介面。
WXEntryActivity
AndroidManifest.xml 中配置該 Activity,需要填入我們自己的包名
<activity
android:name=".wxapi.WXEntryActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:taskAffinity="包名"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
WXEntryActivity 中新增 Intent 的傳遞
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WeChat.WXAPI.handleIntent(getIntent(), this);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
WeChat.WXAPI.handleIntent(intent, this);
}
IWXAPIEventHandler 有 onReq 和 onResp 兩個回撥方法。要注意的是通過 sendReq 傳送的請求,將由 onResp 回調回來;通過 sendResp 傳送的請求,將由 onReq 回調回來。
三、登入
1. 發起登入請求
在註冊完 OpenSdk 後,發起登入請求。
/**
* 登入微信
* @param context 上下文
* @param api 微信 OpenAPI
* @param wechatCode 回撥介面
*/
public static void loginWeChat(Context context, IWXAPI api, WeChatCode wechatCode) {
//判斷是否安裝了微信客戶端
if (!api.isWXAppInstalled()) {
ToastUtils.show(context.getApplicationContext(),R.string.wechat_error_unInstalled);
return;
}
mWeChatCode = wechatCode;
// 傳送授權登入資訊,來獲取code
SendAuth.Req req = new SendAuth.Req();
// 應用的作用域,獲取個人資訊
req.scope = "snsapi_userinfo";
/**
* 用於保持請求和回撥的狀態,授權請求後原樣帶回給第三方
* 為了防止csrf攻擊(跨站請求偽造攻擊),後期改為隨機數加session來校驗
*/
Random random = new Random();
WeChat.WXState = WeChat.WX_STATE_ROOT + random.nextInt(1000);
req.state = WeChat.WXState;
// 傳送請求
api.sendReq(req);
}
/**
* 返回code的回撥介面
*/
public interface WeChatCode {
void getResponse(String code);
}
2. 接收回調,獲得 code
WXEntryActivity 的 onResp 方法中接收回調。
/**
* 第三方應用傳送到微信的請求處理後的響應結果,會回撥到該方法
* @param baseResp 回撥 response
*/
@Override
public void onResp(BaseResp baseResp) {
int result;
switch (baseResp.errCode) {
case BaseResp.ErrCode.ERR_OK:
result = R.string.errcode_success;
UnityCallApi.unityLogInfo(TAG, "onResp OK");
break;
case BaseResp.ErrCode.ERR_USER_CANCEL:
result = R.string.errcode_cancel;
UnityCallApi.unityLogInfo(TAG, "onResp ERR_USER_CANCEL ");
break;
case BaseResp.ErrCode.ERR_AUTH_DENIED:
result = R.string.errcode_deny;
UnityCallApi.unityLogInfo(TAG, "onResp ERR_AUTH_DENIED");
break;
case BaseResp.ErrCode.ERR_UNSUPPORT:
result = R.string.errcode_unsupported;
UnityCallApi.unityLogInfo(TAG, "onResp ERR_UNSUPPORT " + baseResp.errCode);
break;
default:
result = R.string.errcode_unknown;
UnityCallApi.unityLogInfo(TAG, "onResp default errCode " + baseResp.errCode);
break;
}
ToastUtils.show(this,getString(result)+ ", type=" + baseResp.getType());
if (baseResp.getType() == ConstantsAPI.COMMAND_SENDAUTH) {
// 校驗 state
String state = ((SendAuth.Resp) baseResp).state;
if (state.equals(WeChat.WXState)) {
String code = ((SendAuth.Resp) baseResp).code;
// 返回 code 進行下一步
mWeChatCode.getResponse(code);
UnityCallApi.unityLogInfo(TAG, "Get WeChat scope. code:" + code);
} else {
String errorLog = "onResp: State not match!" + WeChat.WXState + "/" + state;
UnityCallApi.unityLogError(TAG, errorLog);
}
}
}
3. 獲取 access_token
優先判斷本地是否已經儲存 access_token,有則進行有效期檢測,沒有則通過 code 獲取最新 access_token。
public void login(Activity activity) {
WXEntryActivity.loginWeChat(this.activity, WXAPI, new WXEntryActivity.WeChatCode() {
@Override
public void getResponse(String code) {
// 從手機本地獲取儲存的授權口令資訊,判斷是否存在access_token,不存在請求獲取,存在就判斷是否過期
String accessToken = (String) ShareUtils.getValue(WeChat.this.activity, WEIXIN_ACCESS_TOKEN_KEY, "none");
String openid = (String) ShareUtils.getValue(WeChat.this.activity, WEIXIN_OPENID_KEY, "");
if (!"none".equals(accessToken)) {
// 有access_token,判斷是否過期有效
isExpireAccessToken(accessToken, openid);
} else {
// 沒有access_token
getAccessToken(code);
}
}
});
}
getAccessToken 獲取最新 access_token
/**
* 微信登入獲取授權口令
*/
private void getAccessToken(String code) {
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=" + WX_ID +
"&secret=" + WX_SECRET +
"&code=" + code +
"&grant_type=authorization_code";
// 網路請求 GET 獲取access_toke
//NetClient是對Okhttp3 的封裝,見引用3
NetClient.getNetClient().callNet(url, new NetClient.MyCallBack() {
@Override
public void onFailure(int code) {
}
@Override
public void onResponse(String response) {
// 處理回撥
processGetAccessTokenResult(response);
}
});
}
isExpireAccessToken 校驗 access_token,沒有通過則重新整理 refreshAccessToken
/**
* 微信登入判斷accesstoken是過期
*
* @param accessToken token
* @param openid 授權使用者唯一標識
*/
private void isExpireAccessToken(final String accessToken, final String openid) {
String url = "https://api.weixin.qq.com/sns/auth?" +
"access_token=" + accessToken +
"&openid=" + openid;
NetClient.getNetClient().callNet(url, new NetClient.MyCallBack() {
@Override
public void onFailure(int code) {
}
@Override
public void onResponse(String response) {
WXErrorInfo info = mGson.fromJson(response, WXErrorInfo.class);
if (0 == info.getErrcode() && "ok".equals(info.getErrmsg())) {
// accessToken沒有過期,獲取使用者資訊
getUserInfo(accessToken, openid);
Toast.makeText(activity.getApplicationContext(), response.toString(), Toast.LENGTH_LONG).show();
} else {
// 過期了,使用refresh_token來重新整理accesstoken
refreshAccessToken();
}
}
});
}
/**
* 微信登入重新整理獲取新的access_token
*/
private void refreshAccessToken() {
// 從本地獲取以儲存的refresh_token
final String refreshToken = (String) ShareUtils.getValue(activity, WEIXIN_REFRESH_TOKEN_KEY, "");
if (TextUtils.isEmpty(refreshToken)) {
return;
}
// 拼裝重新整理access_token的url請求地址
String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?" +
"appid=" + WX_ID +
"&grant_type=refresh_token" +
"&refresh_token=" + refreshToken;
// 執行請求
NetClient.getNetClient().callNet(url, new NetClient.MyCallBack() {
@Override
public void onFailure(int code) {
// 重新請求授權
login(activity);
}
@Override
public void onResponse(String response) {
WXAccessTokenInfo info = mGson.fromJson(response, WXAccessTokenInfo.class);
saveAccessInfoToLocation(info);
// 判斷是否獲取成功,成功則去獲取使用者資訊,否則提示失敗
processGetAccessTokenResult(response);
}
});
}
processGetAccessTokenResult 處理請求返回的結果 response
/**
* 微信登入處理獲取的授權資訊結果
*
* @param response 授權資訊結果
*/
private void processGetAccessTokenResult(String response) {
// 驗證獲取授權口令返回的資訊是否成功
if (validateSuccess(response)) {
// 使用Gson解析返回的授權口令資訊
WXAccessTokenInfo tokenInfo = mGson.fromJson(response, WXAccessTokenInfo.class);
// 儲存資訊到手機本地
saveAccessInfoToLocation(tokenInfo);
// 獲取使用者資訊
getUserInfo(tokenInfo.getAccess_token(), tokenInfo.getOpenid());
} else {
// 授權口令獲取失敗,解析返回錯誤資訊
WXErrorInfo wxErrorInfo = mGson.fromJson(response, WXErrorInfo.class);
String result = String.format(Locale.ENGLISH, "processGetAccessTokenResult: Get Access Token Error. Code:%d msg:%s", wxErrorInfo.getErrcode(), wxErrorInfo.getErrmsg());
UnityCallApi.unityLogError(TAG, result);
}
}
WXAccessTokenInfo 是對應正確返回的類
WXErrorInfo 是對應錯誤返回的類
四、獲取使用者資訊
在登入後,傳送獲取使用者資訊請求。
/**
* 微信token驗證成功後,聯網獲取使用者資訊
*
* @param access_token
* @param openid
*/
private void getUserInfo(String access_token, String openid) {
String url = "https://api.weixin.qq.com/sns/userinfo?" +
"access_token=" + access_token +
"&openid=" + openid;
NetClient.getNetClient().callNet(url, new NetClient.MyCallBack() {
@Override
public void onFailure(int code) {
UnityCallApi.unityLogError(TAG, "Get User Info Error.Code:" + code);
UnityCallApi.sendLoginInfoToUnity(false, "");
}
@Override
public void onResponse(String response) {
UnityCallApi.unityLogInfo(TAG, "Get User Info Successful.");
// 傳送到 Unity 進行解析
UnityCallApi.sendLoginInfoToUnity(true, response);
}
});
}
將返回資訊傳遞給 Unity 進行解析。
正確返回 json
錯誤返回 json
五、分享
1. 文字
WXTextObject textObj = new WXTextObject();
textObj.text = text;
// 多媒體訊息物件
WXMediaMessage msg = new WXMediaMessage();
msg.mediaObject = textObj;
// msg.title = "Will be ignored";
msg.description = text;
msg.mediaTagName = "我是mediaTagName啊";
SendMessageToWX.Req req = new SendMessageToWX.Req();
// type + 時間戳
req.transaction = buildTransaction("text");
req.message = msg;
req.scene = mTargetScene;
WXAPI.sendReq(req);
2. 圖片
WXImageObject imgObj = new WXImageObject(bmp);
WXMediaMessage msg = new WXMediaMessage();
msg.mediaObject = imgObj;
// bitmap 縮放到 150*150
Bitmap thumbBmp = Bitmap.createScaledBitmap(bmp, THUMB_SIZE, THUMB_SIZE, true);
bmp.recycle();
// bitmap 轉 二進位制
msg.thumbData = ShareUtils.bmpToByteArray(thumbBmp, true);
SendMessageToWX.Req req = new SendMessageToWX.Req();
req.transaction = buildTransaction("img");
req.message = msg;
req.scene = mTargetScene;
WXAPI.sendReq(req);
3. 網頁
WXWebpageObject webpage = new WXWebpageObject();
webpage.webpageUrl = "http://www.qq.com";
WXMediaMessage msg = new WXMediaMessage(webpage);
msg.title = "叮咚,群助手提醒你~";
msg.description = "離下班還有最後一個小時了!";
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.send_img);
Bitmap thumbBmp = Bitmap.createScaledBitmap(bmp, THUMB_SIZE, THUMB_SIZE, true);
bmp.recycle();
msg.thumbData = Util.bmpToByteArray(thumbBmp, true);
SendMessageToWX.Req req = new SendMessageToWX.Req();
req.transaction = buildTransaction("webpage");
req.message = msg;
req.scene = mTargetScene;
api.sendReq(req);
六、總結
微信開放平臺感覺目前還是缺少API文件, 比如登入的 scope 裡到底有哪些作用域,就不是很明確。依靠谷歌,還是找到些線索,根據引用4描述有以下幾種。
snsapi_message:幫助你通過該應用向好友傳送訊息
snsapi_userinfo:獲得你的公開資訊(暱稱,頭像等)
snsapi_friend:尋找與你共同使用該應用的好友
snsapi_contact:獲得你的好友關係
然而新增後,依舊不能申請到朋友關係,並且也不知道是通過什麼介面獲取的。當前目測只有和 TX 合作的應用能夠申請到相關許可權。替代方案為自己手動維護個關係網。分享連結,連結中包含分享使用者id。有使用者點選,則能判斷兩人為朋友關係。
當前分享應用,使用者點選分享跳轉應用的操作,推測也需要進行合作。替換方案為分享網頁,使用者點選後,引導右上角開啟預設瀏覽器,之後就是 Android 通過瀏覽器起調應用了。