unity微信開放平臺

一、前言

微信開放平臺,當前(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 編輯閱讀器


作者:ZeroyiQ
鏈接:https://juejin.cn/post/6860010358405333000
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章