Blink

纸上得来终觉浅,绝知此事要躬行

Unity接入QQSDK 【Android篇】

前言

Unity2019版本的classes.jar中没有包含UnityPlayerActivity,所以和接SDK和原来网上的教程有一点点不一样,所以自己摸索了一个接入SDK的流程。

项目地址https://gitee.com/blinkedu/unity_qq_sdk

实现功能

  • QQ登录
  • 获取QQ用户信息
  • 获取用户UnionId
  • QQ空间分享(图文分享)
  • QQ消息分享(图文分享)
  • 注销QQ登录

准备工作

环境

QQ互联官网

  • 登录QQ互联官网:https://connect.qq.com/
  • 创建应用
    1. 在官网选择 应用管理
    2. 选择 创建应用
    3. 选择 创建移动应用

按照提示创建完,等待审核通过就行了。在
应用管理 -> 移动应用 可以查看到刚刚创建的应用
《Unity接入QQSDK   【Android篇】》

Android工程

创建Android工程

《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

经过上面的创建,我们的Android工程(空工程)算是创建好了,但是这还只是一个空工程,无法和Unity进行交互,接下来会导入我们所需要的库文件

切记!!!切记!!!切记!!! 之后的所有操作都是在 qqlib 下面进行的

下载 QQSDK 并添加到项目中

SDK下载页网址: https://wiki.connect.qq.com/sdk%e4%b8%8b%e8%bd%bd
《Unity接入QQSDK   【Android篇】》

将下载好的SDK压缩包解压,找到我们需要的 jar包
《Unity接入QQSDK   【Android篇】》

将 jar包复制到 项目中的 lib 目录下,并添加到
qqlib moudle
《Unity接入QQSDK   【Android篇】》

添加 Unity相关jar包到项目

在 Unity 安装目录下找到
classes.jar
参考路径:D:\Unity\2019.3.0f6\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Development\Classes

关于 mono 和 il2cpp 这两个目录下的 classes.jar ,你看你的Unity项目选择的哪种方式就选择对应下面的 classes.jar
《Unity接入QQSDK   【Android篇】》
将 classes.jar 也添加到 Android项目中,方式和添加 QQSDK 步骤一样

添加 UnityPlayerActivity.java 到 项目中

在 Unity 2019 版本(具体是2019的哪个版本没有去试过),classes.jar 中是找不到 UnityPlayerActitiy 了的,所有需要我们手动将它添加到项目中去
参考路径:D:\Unity\2019.3.0f6\Editor\Data\PlaybackEngines\AndroidPlayer\Source\com\unity3d\player
《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

修改 qqlib 下面 AndroidManifest.xml

《Unity接入QQSDK   【Android篇】》
打开 AndroidManifest.xml ,将其中的内容替换为下面的内容

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.blinkedu.qqlib">

    <!--权限-->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application>

        <!--QQ登录-->
        <activity
            android:name="com.tencent.tauth.AuthActivity"
            android:launchMode="singleTask"
            android:noHistory="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="tencent你的AppId" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.tencent.connect.common.AssistActivity"
            android:configChanges="orientation|keyboardHidden"
            android:screenOrientation="behind"
            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
    </application>
</manifest>

注意一下检查包名是否和你项目中的一样哦
《Unity接入QQSDK   【Android篇】》

还有就是修改中AppId(在QQ互联官网的应用管理里面可以看到自己申请的应用的AppId)
《Unity接入QQSDK   【Android篇】》

修改 build.gradle

找到 build.gradle
《Unity接入QQSDK   【Android篇】》

将其中的 dependencies 修改为下面这样

dependencies {
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation files('libs/open_sdk_3.5.11.6_r13b47b7_lite.jar')    // 打包后会包含到arr包中
    compileOnly files('libs/classes.jar')                               // 只编译,不包含到arr包中
}

为什么要改成这样呢?
因为libs/classe.jar 我们是不希望它包含到构建的arr包中的,因为导入Unity中后,如果包含了classes.jar,会和Unity中已经存在的包冲突。所以在构建arr的时候只让它进行编译

接下来就开始撸代码啦

首先创建 MainActivity, 这个相当于Android项目的入口

《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

创建完成的
MainActivity 需要继承 UnityPlayerActivity

package cn.blinkedu.qqlib;

public class MainActivity extends UnityPlayerActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

创建回调类 BaseUiListener

需要实现实现
IUiListener 接口,这个类的对象是用来接收进行登录/分享等操作后接收返回的回调
之后进行登录等操作时,会创建一个该类的对象接收对应的操作的返回

package cn.blinkedu.qqlib;

import com.tencent.tauth.IUiListener;
import com.tencent.tauth.UiError;

class BaseUiListener implements IUiListener {
    // 完成操作
    @Override
    public void onComplete(Object o) {

    }

    // 发生错误
    @Override
    public void onError(UiError uiError) {

    }

    // 取消操作
    @Override
    public void onCancel() {

    }

    // 警告
    @Override
    public void onWarning(int i) {

    }
}

实现登录和分享等功能

MainActivity 完整代码,已经注释好了,可以自行查看

package cn.blinkedu.qqlib;

import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import com.tencent.connect.UnionInfo;
import com.tencent.connect.UserInfo;
import com.tencent.connect.common.Constants;
import com.tencent.connect.share.QQShare;
import com.tencent.connect.share.QzoneShare;
import com.tencent.tauth.IUiListener;
import com.tencent.tauth.Tencent;
import com.unity3d.player.UnityPlayer;

import org.json.JSONObject;

import java.util.ArrayList;

public class MainActivity extends UnityPlayerActivity {
    private final static String APP_ID = "101993349";    // 这里填你自己AppId
    private final static String TAG = "QQSdk";

    private Tencent mTencent = null;
    private IUiListener loginListener = null;           // 登录回调
    private IUiListener shareQQListener = null;         // QQ消息分享回调
    private IUiListener shareQZoneListener = null;      // QQ空间分享回调

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 初始化sdk
        mTencent = Tencent.createInstance(APP_ID, this.getApplicationContext());
        // 设置授权(很重要),如果没有设置授权,无法进行登录,会提示未授权
        if (Tencent.isPermissionNotGranted()) {
            Tencent.setIsPermissionGranted(true);
        }

        // 初始化登录回调
        loginListener = new BaseUiListener() {
            @Override
            public void onComplete(Object o) {
                JSONObject jsonObject = (JSONObject) o;
                // 保存openid和token
                initOpenIdAndToken(jsonObject);
                // 将接收到的json数据发到unity
                // 第一个参数:Unity场景中挂在接收数据的脚本的游戏物体名称
                // 第二个参数:脚本中的接收事件的方法名称
                // 第三个参数:字符串数据(这里传的是json字符串)
                UnityPlayer.UnitySendMessage("QQSdk", "OnLoginComplete", jsonObject.toString());
            }
        };
        // 初始化分享到QQ回调
        shareQQListener = new BaseUiListener() {
            @Override
            public void onComplete(Object o) {
                Toast.makeText(MainActivity.this, "分享成功", Toast.LENGTH_SHORT).show();
            }
        };
        // 初始化分享到QQ空间回调
        shareQZoneListener = new BaseUiListener() {
            @Override
            public void onComplete(Object o) {
                Toast.makeText(MainActivity.this, "分享成功", Toast.LENGTH_SHORT).show();
            }
        };
    }

    // 登录QQ
    public void login() {
        // 判断会话是否有效,如果会话无效就进行登录操作
        if (!mTencent.isSessionValid()) {
            mTencent.login(this, "all", loginListener);
        }
    }

    // 注销登录
    public void logout() {
        if (mTencent != null) {
            mTencent.logout(this);
            UnityPlayer.UnitySendMessage("QQSdk", "OnLogout", "");
            Log.d(TAG, "logout! ");
        }
    }

    // 获取用户信息
    public void getUserInfo() {
        if (mTencent != null && mTencent.isSessionValid()) {
            UserInfo info = new UserInfo(this, mTencent.getQQToken());
            info.getUserInfo(new BaseUiListener() {
                @Override
                public void onComplete(Object o) {
                    JSONObject jsonObject = (JSONObject) o;
                    UnityPlayer.UnitySendMessage("QQSdk", "OnGetUserInfoComplete", jsonObject.toString());
                }
            });
        }
    }

    // 获取UnionId
    public void getUnionId() {
        if (mTencent != null && mTencent.isSessionValid()) {
            UnionInfo unionInfo = new UnionInfo(this, mTencent.getQQToken());
            unionInfo.getUnionId(new BaseUiListener(){
                @Override
                public void onComplete(Object o) {
                    JSONObject jsonObject = (JSONObject) o;
                    UnityPlayer.UnitySendMessage("QQSdk", "OnGetUnionIdComplete", jsonObject.toString());
                }
            });
        }
    }

    // 分享到QQ
    public void shareToQQ(String title, String summary, String targetUrl, String imgUrl, String appName) {
        Bundle params = new Bundle();
        // 分享类型
        params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);
        // 标题
        params.putString(QQShare.SHARE_TO_QQ_TITLE, title);
        // 摘要
        params.putString(QQShare.SHARE_TO_QQ_SUMMARY, summary);
        // 跳转链接
        params.putString(QQShare.SHARE_TO_QQ_TARGET_URL, targetUrl);
        // 图片链接
        params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, imgUrl);
        // App名称
        params.putString(QQShare.SHARE_TO_QQ_APP_NAME, appName);
        // 分享
        mTencent.shareToQQ(this, params, shareQZoneListener);
    }

    // 分享到QQ空间(图文)
    public void shareToQZone(String title, String summary, String targetUrl, String imgUrl, String appName) {
        Bundle params = new Bundle();
        // 分享类型
        params.putInt(QzoneShare.SHARE_TO_QZONE_KEY_TYPE, QzoneShare.SHARE_TO_QZONE_TYPE_IMAGE_TEXT);
        // 标题
        params.putString(QzoneShare.SHARE_TO_QQ_TITLE, title);
        // 摘要
        params.putString(QzoneShare.SHARE_TO_QQ_SUMMARY, summary);
        // 跳转链接
        params.putString(QzoneShare.SHARE_TO_QQ_TARGET_URL, targetUrl);
        // 图片
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add(imgUrl);
        params.putStringArrayList(QzoneShare.SHARE_TO_QQ_IMAGE_URL, arrayList);
        // App名称
        params.putString(QzoneShare.SHARE_TO_QQ_APP_NAME, appName);
        // 分享
        mTencent.shareToQzone(this, params, shareQZoneListener);
    }

    // 保存token和openId
    private void initOpenIdAndToken(JSONObject o) {
        try {
            String token = o.getString(Constants.PARAM_ACCESS_TOKEN);
            String expires = o.getString(Constants.PARAM_EXPIRES_IN);
            String openId = o.getString(Constants.PARAM_OPEN_ID);
            // 确保保存的数据不为空
            if (!TextUtils.isEmpty(token) && !TextUtils.isEmpty(expires) && !TextUtils.isEmpty(openId)) {
                mTencent.setAccessToken(token, expires);
                mTencent.setOpenId(openId);
                Log.d(TAG, "initOpenIdAndToken success!");
            } else {
                Log.e(TAG, "initOpenIdAndToken fail!");
            }
        } catch (Exception e) {
            Log.e(TAG, "initOpenIdAndToken: ", e);
        }
    }

    // 这个方法必须加,不然可能无法接收到回调
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        IUiListener listener = null;
        if (requestCode == Constants.REQUEST_LOGIN || requestCode == Constants.REQUEST_APPBAR) {
            listener = loginListener;
        } else if (requestCode == Constants.REQUEST_QQ_SHARE) {
            listener = shareQQListener;
        } else if (requestCode == Constants.REQUEST_QZONE_SHARE) {
            listener = shareQZoneListener;
        }
        Tencent.onActivityResultData(requestCode, resultCode, data, listener);
        super.onActivityResult(requestCode, resultCode, data);
    }
}

构建arr包

Android相关的代码就是上面这么多了,接下来就是构建 arr 了
《Unity接入QQSDK   【Android篇】》

可以在 build/outputs/arr 目录下查看到生成的arr包
《Unity接入QQSDK   【Android篇】》

这里我们可以看到生成的是debug版本,这里顺带说一下怎么修改为生成release版的arr包 (选中左边侧栏下面的Build Variants)
《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK   【Android篇】》

生成的arr包我们是需要拷贝到Unity中进行使用的

Unity 工程

创建Unity工程

这一步就略过了哈,既然接sdk,相信Unity大家都应该很熟悉了

(注意将平台切换到Android平台,这里就默认各位的Unity中Android环境已经配置好了)
《Unity接入QQSDK   【Android篇】》

将 arr 拷贝到Unity中

《Unity接入QQSDK   【Android篇】》

创建 AndroidManifest.xml

《Unity接入QQSDK   【Android篇】》

将 AndroidManifest.xml 中的内容替换成下面这样

<?xml version="1.0" encoding="utf-8"?>
<!-- GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity3d.player" xmlns:tools="http://schemas.android.com/tools">

  <!--权限-->
  <uses-permission android:name="android.permission.INTERNET" />

  <application>

    <activity android:name="cn.blinkedu.qqlib.MainActivity" android:theme="@style/UnityThemeSelector" android:screenOrientation="landscape" android:launchMode="singleTask" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" android:hardwareAccelerated="false">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
      <meta-data android:name="android.notch_support" android:value="true" />
    </activity>

    <meta-data android:name="unity.splash-mode" android:value="0" />
    <meta-data android:name="unity.splash-enable" android:value="True" />
    <meta-data android:name="notch.config" android:value="portrait|landscape" />
    <meta-data android:name="unity.build-id" android:value="5ae1a0d2-2a7d-41e3-972f-a67457200620" />
  </application>

  <uses-feature android:glEsVersion="0x00030000" />
  <uses-feature android:name="android.hardware.vulkan.version" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />
</manifest>

注意这里的Activity标签里面的包名保持和Android工程中的包名一致

添加 LitJson.dll

为什么要添加这个动态链接库?
这里笔者用这个库来解析Android端发过来的Josn数据,如果各位读者想用其他库解析(或者不用)也是可以的。该库的可以在工程示例中找到,工程链接在文章最前面
《Unity接入QQSDK   【Android篇】》

创建 QQSdk.cs 脚本

该脚本是一个单例类,,目的是为了方便其他模块调用
在场景中创建一个空的GameObject, 然后将该脚本挂载上去即可

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class QQSdk : MonoBehaviour
{
    // 单例
    public static QQSdk Instance { get; private set; }

    private AndroidJavaClass jc = null;
    private AndroidJavaObject jo = null;

    private void Awake()
    {
        Instance = this;

        jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        jo = jc.GetStatic<AndroidJavaObject>("currentActivity");

        // 跳转场景不销毁
        DontDestroyOnLoad(this);
    }

    /// <summary>
    /// 登录
    /// </summary>
    public void Login()
    {
        jo?.Call("login");
    }

    /// <summary>
    /// 注销
    /// </summary>
    public void Logout()
    {
        jo?.Call("logout");
    }

    /// <summary>
    /// 获取用户信息
    /// </summary>
    public void GetUserInfo()
    {
        jo?.Call("getUserInfo");
    }

    /// <summary>
    /// 获取UnionId
    /// </summary>
    public void GetUnionId()
    {
        jo.Call("getUnionId");
    }

    /// <summary>
    /// 分享到QQ消息
    /// </summary>
    /// <param name="title">标题</param>
    /// <param name="summary">概要内容</param>
    /// <param name="targetUrl">跳转URL</param>
    /// <param name="imgUrl">图片URL</param>
    /// <param name="appName">应用名称</param>
    public void ShareToQQ(string title, string summary, string targetUrl, string imgUrl = "", string appName = "")
    {
        jo?.Call("shareToQQ", title, summary, targetUrl, imgUrl, appName);
    }

    /// <summary>
    /// 分享到QQ空间
    /// </summary>
    /// <param name="title">标题</param>
    /// <param name="summary">概要内容</param>
    /// <param name="targetUrl">跳转URL</param>
    /// <param name="imgUrl">图片URL</param>
    /// <param name="appName">应用名称</param>
    public void ShareToQZone(string title, string summary, string targetUrl, string imgUrl = "", string appName = "")
    {
        jo?.Call("shareToQZone", title, summary, targetUrl, imgUrl, appName);
    }

    #region 需要Android调用的方法
    // 登录成功
    public void OnLoginComplete(string json)
    {
        GetUserInfo();  // 获取用户信息
        GetUnionId();   // 获取UnionId
    }

    // 获取用户信息成功
    public void OnGetUserInfoComplete(string json)
    {
        // 测试用,打开界面并刷新信息显示
        Test.Instance.ShowInfoPanel();
        Test.Instance.RefreshInfo(json);
    }

    // 获取UnionId成功
    public void OnGetUnionIdComplete(string json)
    {

    }

    // 注销登录
    public void OnLogout(string arg)
    {
        // 测试用
        Test.Instance.ShowLoginPanel();
    }
    #endregion
}

其实到这一步SDK已经可以使用了,自己可以调用脚本中提供的接口,就可以获取相关数据了,具体测试内容可自己下载工程查看,这里就不展示了

解决分享到QQ消息失败问题

大家测试的时候会发现,分享到QQ消息会提示分享失败,但是其他功能都是正常的,这个是怎么回事呢?
这个是因为我们没有设置应用签名导致的,并不是我们代码写的有问题

解决方法

Unity自带生成keystore功能

  1. 在PlayerSetting中,点击KeyStoreManager
    《Unity接入QQSDK   【Android篇】》
  1. 创建一个新的keystore,并保存
    《Unity接入QQSDK   【Android篇】》
  2. 填写下面的的信息,并添加 (记住好自己的密码哦)
    《Unity接入QQSDK   【Android篇】》

《Unity接入QQSDK    【Android篇】》

点赞

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注