一、前言
微信開放平臺,當前(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 通過瀏覽器起調應用了。
七、引用
- 資源中心是微信開放平臺開發者所需所有相關資源的彙集地,包括: | 微信開放文檔
- 如何在Unity中使用官方SDK實現微信、QQ、微博帳號登錄(Android) -騰訊遊戲學院
- android 網絡請求okhttp解耦逆天封裝,使用簡單,擴展性強
- 作業部落 Cmd Markdown 編輯閱讀器
作者:ZeroyiQ
鏈接:https://juejin.cn/post/6860010358405333000
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。