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 通过浏览器起调应用了。