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 接口。

img

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 是对应正确返回的类

img

WXErrorInfo 是对应错误返回的类

img

四、获取用户信息

在登录后,发送获取用户信息请求。

 /** * 微信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

img

错误返回 json

img

五、分享

1. 文字

img

 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. 图片

img

 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. 网页

img

 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。有用户点击,则能判断两人为朋友关系。

img

当前分享应用,用户点击分享跳转应用的操作,推测也需要进行合作。替换方案为分享网页,用户点击后,引导右上角打开默认浏览器,之后就是 Android 通过浏览器起调应用了。

img

七、引用

  1. 资源中心是微信开放平台开发者所需所有相关资源的汇集地,包括: | 微信开放文档
  2. 如何在Unity中使用官方SDK实现微信、QQ、微博帐号登录(Android) -腾讯游戏学院
  3. android 网络请求okhttp解耦逆天封装,使用简单,扩展性强
  4. 作业部落 Cmd Markdown 编辑阅读器

相关文章