腾讯TIM插件的使用

  • unity版本
  • 注意事项
  • 1 newtonjson版本问题
  • 2 和项目里已经有的json工具包的类名起了冲突
  • 3 项目已经做到一定程度不建议使用
  • 安卓版本
  • 导入
  • 初始化
  • 客户端,服务器获取usersig
  • 基础的方法调用
  • 安卓打包aar导入unity使用
  • 安卓端
  • 注意事项1
  • 注意事项2
  • 注意事项3
  • 注意事项4
  • 注意事项5
  • 注意事项6
  • unity端
  • 注意事项1
  • 注意事项2
  • 总结



很久没更了,最近的状态好了很多,这里就再记录一下研究了4天的腾讯TIM插件过程中的一些心得

unity版本

腾讯TIM插件官方里其实已经有针对unity出了一个package包了,直接导入到unity里就可以使用了,不过需要注意一下

注意事项

1 newtonjson版本问题

TIM对json的序列化和反序列化使用的是Newtonjson,并且是net3.5版本的,我们的unity项目里需要匹配上才行,不然会在json转化的过程中报错导致功能无法正常使用

2 和项目里已经有的json工具包的类名起了冲突

如果你的项目里已经使用了别的json序列化反序列化的工具包了(比如在下使用的就是litjson),并且项目里引用的litjson已经很多次了的话,在下是不太建议使用腾讯官方给我们提供的这个unity版本的
第一是两个工具包对json的解析各有不同,最重要的是亮哥工具包同名的类有很多,但里面实现的方法却截然不同,所以当你的项目里已经用了其他json工具包的时候最好是不要用这个unity版本,修改起来会非常的痛苦(问就是经历过T_T)

3 项目已经做到一定程度不建议使用

为啥这样说,除了上面说到的不同的json工具包之外,官方提供的这个包,由于涉及到安卓的内容,所以他会在plguins和其他地方有脚本需要导入,如果你的项目已经做了很多工作了,导入这个插件的话,后期不用的时候会很痛苦,因为要一个一个找这个包里的类,除了花时间之外可能还会在不经意间删掉其他内容的代码.这样就得不偿失了
综上所述,在项目的初期还是比较建议使用官方的unity包的,因为里面的demo十分的容易上手,在下应该是半天就大概知道了怎么对基础的功能进行调用和实现,打包起来也没啥太困难的

安卓版本

因为上面的原因,所以不得不重新捡起安卓的内容,在androidstudo里导入官方提供的安卓sdk,在里面封装自己的方法,然后打包成aar包,导入到unity进行调用
到写这篇帖子的时间,在下才刚刚到导入as里进行基础的对话功能的实现,导入到unity的部分还没测试
不得不说官方提供的文档是真的生涩
在百度了许久和自己摸索之下才走通了基本的功能

导入

在官网上下载TIM的aar包.地址:下载地址

这里还有其他两种在as里导入sdk的方式
导入sdk

在下用的是第二种,这里没有啥问题,因为官方有图给我们,照着做就好了

初始化

初始化这里分两个部分吧

客户端,服务器获取usersig

这里官方都有给文档,也不详细说了,在下当前处于demo阶段,所以很暴力的使用了客户端生成的方式

基础的方法调用

初始化sdk和登陆的方法在官方的文档里也都有说明,这个照着写就好了,在下在使用的过程中也没有遇到啥大问题
下面是比较需要注意的
有关事件监听的方法
这个方法尽量在初始化sdk成功之后调用比较好,在下在其他帖子里看到放在这里面才可以实时接收到消息(当然应该没有这么奇葩,不过提示一下大家如果监听的过程出现了问题不妨试试修改一下添加监听事件的顺序)
然后就是怎么测试实时获得消息了
这里需要两台机子,一个是发送方,一个是接收方(之前在下的思路是自己发送给自己,应该就不用在创建多一个新的账号和手机去测试接收了,我还是太天真了)
一定要用两台机子,并且userid不一样才可以知道事件监听的函数是否正常运行
到这里基本上就结束了小弟的帖子了
贴上代码吧
注意,这里因为是在as里测试的,所以需要创建一个简单的应用界面

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/sendmsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="252dp"
        android:text="发送消息"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/inputText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="190dp"
        android:layout_marginBottom="141dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="输入文字"
        app:layout_constraintBottom_toTopOf="@+id/scrollView2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ScrollView
        android:id="@+id/scrollView2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/getconversationlist">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/toastText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="debug信息" />
        </LinearLayout>
    </ScrollView>

    <Button
        android:id="@+id/getconversationlist"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="311dp"
        android:layout_marginBottom="17dp"
        android:text="拉取对话记录"
        app:layout_constraintBottom_toTopOf="@+id/scrollView2"
        app:layout_constraintStart_toStartOf="@+id/sendmsg"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.tencent.imsdk.v2.V2TIMCallback;
import com.tencent.imsdk.v2.V2TIMConversation;
import com.tencent.imsdk.v2.V2TIMConversationListener;
import com.tencent.imsdk.v2.V2TIMConversationResult;
import com.tencent.imsdk.v2.V2TIMGroupMemberInfo;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMMessage;
import com.tencent.imsdk.v2.V2TIMSDKConfig;
import com.tencent.imsdk.v2.V2TIMSDKListener;
import com.tencent.imsdk.v2.V2TIMSimpleMsgListener;
import com.tencent.imsdk.v2.V2TIMUserInfo;
import com.tencent.imsdk.v2.V2TIMValueCallback;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private TextView tem;

    private String userId = "pure";
    private static MainActivity _instance;


    public static MainActivity getInstance() {
        if (_instance == null) {
            _instance = new MainActivity();
        }
        return _instance;
    }

    private V2TIMSimpleMsgListener listener;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tem = findViewById(R.id.toastText);
        findViewById(R.id.sendmsg).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TextView text = findViewById(R.id.inputText);
                System.out.println(text.getText().toString());
                sendMsgTIM(text.getText().toString(), "发送对象");
            }
        });

        findViewById(R.id.getconversationlist).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getConversation();
            }
        });
        awakeTIM();


    }

    //初始化
    public void awakeTIM() {
        // 1. 从 IM 控制台获取应用 SDKAppID,详情请参考 SDKAppID。
        // 2. 初始化 config 对象
        V2TIMSDKConfig config = new V2TIMSDKConfig();
        // 3. 指定 log 输出级别,详情请参考 SDKConfig。
        config.setLogLevel(V2TIMSDKConfig.V2TIM_LOG_INFO);
        // 4. 初始化 SDK 并设置 V2TIMSDKListener 的监听对象。
        int sdkAppID = 在腾讯开发里创建应用后生成的sdkid;
        // initSDK 后 SDK 会自动连接网络,网络连接状态可以在 V2TIMSDKListener 回调里面监听。
        V2TIMManager.getInstance().initSDK(this, sdkAppID, config, new V2TIMSDKListener() {
            // 5. 监听 V2TIMSDKListener 回调
            @Override
            public void onConnecting() {
                // 正在连接到腾讯云服务器
                System.out.println("正在连接到腾讯云服务器....");
            }

            @Override
            public void onConnectSuccess() {
                // 已经成功连接到腾讯云服务器
                System.out.println("已经成功连接到腾讯云服务器....");
                String userSig = GenerateTestUserSig.genTestUserSig(userId);
                loginTIM(userId, userSig);
                //添加事件监听器
                V2TIMManager.getInstance().addSimpleMsgListener(new V2TIMSimpleMsgListener() {
                    @Override
                    public void onRecvC2CTextMessage(String msgID, V2TIMUserInfo sender, String text) {
                        super.onRecvC2CTextMessage(msgID, sender, text);
                        tem.setText(text);
                        System.out.println("接收到来自" + sender.getUserID() + "发送的消息" + text);
                    }

                    @Override
                    public void onRecvC2CCustomMessage(String msgID, V2TIMUserInfo sender, byte[] customData) {
                        super.onRecvC2CCustomMessage(msgID, sender, customData);
                    }

                    @Override
                    public void onRecvGroupTextMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, String text) {
                        super.onRecvGroupTextMessage(msgID, groupID, sender, text);
                    }

                    @Override
                    public void onRecvGroupCustomMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, byte[] customData) {
                        super.onRecvGroupCustomMessage(msgID, groupID, sender, customData);
                    }

                });
            }

            @Override
            public void onConnectFailed(int code, String error) {
                // 连接腾讯云服务器失败
                System.out.println("连接腾讯云服务器失败....");
            }
        });

        //添加会话监听
        V2TIMManager.getConversationManager().setConversationListener(new V2TIMConversationListener() {
            @Override
            public void onSyncServerStart() {
                super.onSyncServerStart();
            }

            @Override
            public void onSyncServerFinish() {
                super.onSyncServerFinish();
            }

            @Override
            public void onSyncServerFailed() {
                super.onSyncServerFailed();
            }

            @Override
            public void onNewConversation(List<V2TIMConversation> conversationList) {
                super.onNewConversation(conversationList);
                tem.setText("onNewConversation : " + conversationList.get(0).getConversationID());
            }

            @Override
            public void onConversationChanged(List<V2TIMConversation> conversationList) {
                super.onConversationChanged(conversationList);
                tem.setText("OnConversationChanged....."  + conversationList.get(0).getConversationID());
            }
        });

    }

    public void loginTIM(String userId, String userSig) {
        V2TIMManager.getInstance().login(userId, userSig
                , new V2TIMCallback() {
                    @Override
                    public void onError(int i, String s) {
                        System.out.println("login fail");
                    }

                    @Override
                    public void onSuccess() {
                        tem.setText("login success");
                    }
                });
    }

    public void sendMsgTIM(String text, String sender) {
        V2TIMManager.getInstance().sendC2CTextMessage(text, sender, new V2TIMValueCallback<V2TIMMessage>() {
            @Override
            public void onError(int i, String s) {
                System.out.println("发送消息失败");
            }

            @Override
            public void onSuccess(V2TIMMessage v2TIMMessage) {
                System.out.println("发送消息成功");

            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        V2TIMManager.getInstance().unInitSDK();
    }

    public void getConversation() {
        V2TIMManager.getConversationManager().getConversationList(0, 100, new V2TIMValueCallback<V2TIMConversationResult>() {
            @Override
            public void onError(int i, String s) {

            }

            @Override
            public void onSuccess(V2TIMConversationResult v2TIMConversationResult) {
                String conversationId = v2TIMConversationResult.getConversationList().get(0).getConversationID();
                System.out.println(conversationId);
                V2TIMManager.getConversationManager().getConversation(conversationId, new V2TIMValueCallback<V2TIMConversation>() {
                    @Override
                    public void onError(int i, String s) {

                    }

                    @Override
                    public void onSuccess(V2TIMConversation v2TIMConversation) {
                        tem.setText(v2TIMConversation.getLastMessage().getTextElem().getText());
                    }
                });
            }
        });
    }
}

后面把基础功能都实现了之后,导入到unity里能正常使用了再更新吧~

同天下午更新
服务器获得usersig
C#服务器获得usersig脚本 把这个脚本命名空间里的内容复制到项目中新建的cs脚本里
然后在网上下载ComponentAce.Compression.Libs.zlib
这个工具包的源码导入到项目里,可以把上面的脚本也放在这个源码里,方便统一管理
然后在需要用的时候创建上面超链接里的类TLSSigAPIv2,调用里面的getusersidg方法,填上用户的userid即可获得用户对应的usersig
不知道怎么删掉命名空间的话可以用我下面贴出来的代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using ComponentAce.Compression.Libs.zlib;
public class TLSSigAPIv2
{
    private readonly int sdkappid;
    private readonly string key;

    public TLSSigAPIv2(int sdkappid, string key)
    {
        this.sdkappid = sdkappid;
        this.key = key;
    }

    /**
    *【功能说明】用于签发 TRTC 和 IM 服务中必须要使用的 UserSig 鉴权票据
    *
    *【参数说明】
    * userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
    * expire - UserSig 票据的过期时间,单位是秒,比如 86400 代表生成的 UserSig 票据在一天后就无法再使用了。
    */
    public string genUserSig(string userid, int expire = 180 * 86400)
    {
        return genUserSig(userid, expire, null, false);
    }

    /**
    *【功能说明】
    * 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
    * PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
    *  - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
    *  - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
    * 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】=>【应用管理】=>【应用信息】中打开“启动权限密钥”开关。
    *
    *【参数说明】
    * userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
    * roomid - 房间号,用于指定该 userid 可以进入的房间号
    * expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
    * privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
    *  - 第 1 位:0000 0001 = 1,创建房间的权限
    *  - 第 2 位:0000 0010 = 2,加入房间的权限
    *  - 第 3 位:0000 0100 = 4,发送语音的权限
    *  - 第 4 位:0000 1000 = 8,接收语音的权限
    *  - 第 5 位:0001 0000 = 16,发送视频的权限  
    *  - 第 6 位:0010 0000 = 32,接收视频的权限  
    *  - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
    *  - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限  
    *  - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
    *  - privilegeMap == 0010 1010 == 42  代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
    */
    public string genPrivateMapKey(string userid, int expire, uint roomid, uint privilegeMap)
    {
        byte[] userbuf = genUserBuf(userid, roomid, expire, privilegeMap, 0, "");
        System.Console.WriteLine(userbuf);
        return genUserSig(userid, expire, userbuf, true);
    }
    /**
    *【功能说明】
    * 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
    * PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
    *  - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
    *  - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
    * 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】=>【应用管理】=>【应用信息】中打开“启动权限密钥”开关。
    *
    *【参数说明】
    * userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
    * roomstr - 房间号,用于指定该 userid 可以进入的房间号
    * expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
    * privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
    *  - 第 1 位:0000 0001 = 1,创建房间的权限
    *  - 第 2 位:0000 0010 = 2,加入房间的权限
    *  - 第 3 位:0000 0100 = 4,发送语音的权限
    *  - 第 4 位:0000 1000 = 8,接收语音的权限
    *  - 第 5 位:0001 0000 = 16,发送视频的权限  
    *  - 第 6 位:0010 0000 = 32,接收视频的权限  
    *  - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
    *  - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限  
    *  - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
    *  - privilegeMap == 0010 1010 == 42  代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
    */
    public string genPrivateMapKeyWithStringRoomID(string userid, int expire, string roomstr, uint privilegeMap)
    {
        byte[] userbuf = genUserBuf(userid, 0, expire, privilegeMap, 0, roomstr);
        System.Console.WriteLine(userbuf);
        return genUserSig(userid, expire, userbuf, true);
    }
    private string genUserSig(string userid, int expire, byte[] userbuf, bool userBufEnabled)
    {
        DateTime epoch = new DateTime(1970, 1, 1); // unix 时间戳
        Int64 currTime = (Int64)(DateTime.UtcNow - epoch).TotalMilliseconds / 1000;

        string base64UserBuf;
        string jsonData;
        if (true == userBufEnabled)
        {
            base64UserBuf = Convert.ToBase64String(userbuf);
            string base64sig = HMACSHA256(userid, currTime, expire, base64UserBuf, userBufEnabled);
            // 没有引入 json 库,所以这里手动进行组装
            jsonData = String.Format("{{"
               + "\"TLS.ver\":" + "\"2.0\","
               + "\"TLS.identifier\":" + "\"{0}\","
               + "\"TLS.sdkappid\":" + "{1},"
               + "\"TLS.expire\":" + "{2},"
               + "\"TLS.time\":" + "{3},"
               + "\"TLS.sig\":" + "\"{4}\","
               + "\"TLS.userbuf\":" + "\"{5}\""
               + "}}", userid, sdkappid, expire, currTime, base64sig, base64UserBuf);
        }
        else
        {
            // 没有引入 json 库,所以这里手动进行组装
            string base64sig = HMACSHA256(userid, currTime, expire, "", false);
            jsonData = String.Format("{{"
                + "\"TLS.ver\":" + "\"2.0\","
                + "\"TLS.identifier\":" + "\"{0}\","
                + "\"TLS.sdkappid\":" + "{1},"
                + "\"TLS.expire\":" + "{2},"
                + "\"TLS.time\":" + "{3},"
                + "\"TLS.sig\":" + "\"{4}\""
                + "}}", userid, sdkappid, expire, currTime, base64sig);
        }

        byte[] buffer = Encoding.UTF8.GetBytes(jsonData);
        return Convert.ToBase64String(CompressBytes(buffer))
            .Replace('+', '*').Replace('/', '-').Replace('=', '_');
    }
    public byte[] genUserBuf(string account, uint dwAuthID, int dwExpTime, uint dwPrivilegeMap, uint dwAccountType, string roomStr)
    {
        int length = 1 + 2 + account.Length + 20;
        int offset = 0;
        if (roomStr.Length > 0)
            length = length + 2 + roomStr.Length;
        byte[] userBuf = new byte[length];

        if (roomStr.Length > 0)
            userBuf[offset++] = 1;
        else
            userBuf[offset++] = 0;

        userBuf[offset++] = (byte)((account.Length & 0xFF00) >> 8);
        userBuf[offset++] = (byte)(account.Length & 0x00FF);

        byte[] accountByte = System.Text.Encoding.UTF8.GetBytes(account);
        accountByte.CopyTo(userBuf, offset);
        offset += account.Length;

        //dwSdkAppid
        userBuf[offset++] = (byte)((sdkappid & 0xFF000000) >> 24);
        userBuf[offset++] = (byte)((sdkappid & 0x00FF0000) >> 16);
        userBuf[offset++] = (byte)((sdkappid & 0x0000FF00) >> 8);
        userBuf[offset++] = (byte)(sdkappid & 0x000000FF);

        //dwAuthId
        userBuf[offset++] = (byte)((dwAuthID & 0xFF000000) >> 24);
        userBuf[offset++] = (byte)((dwAuthID & 0x00FF0000) >> 16);
        userBuf[offset++] = (byte)((dwAuthID & 0x0000FF00) >> 8);
        userBuf[offset++] = (byte)(dwAuthID & 0x000000FF);

        //time_t now = time(0);
        //uint32_t expire = now + dwExpTime;
        long expire = dwExpTime + (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
        userBuf[offset++] = (byte)((expire & 0xFF000000) >> 24);
        userBuf[offset++] = (byte)((expire & 0x00FF0000) >> 16);
        userBuf[offset++] = (byte)((expire & 0x0000FF00) >> 8);
        userBuf[offset++] = (byte)(expire & 0x000000FF);

        //dwPrivilegeMap     
        userBuf[offset++] = (byte)((dwPrivilegeMap & 0xFF000000) >> 24);
        userBuf[offset++] = (byte)((dwPrivilegeMap & 0x00FF0000) >> 16);
        userBuf[offset++] = (byte)((dwPrivilegeMap & 0x0000FF00) >> 8);
        userBuf[offset++] = (byte)(dwPrivilegeMap & 0x000000FF);

        //dwAccountType
        userBuf[offset++] = (byte)((dwAccountType & 0xFF000000) >> 24);
        userBuf[offset++] = (byte)((dwAccountType & 0x00FF0000) >> 16);
        userBuf[offset++] = (byte)((dwAccountType & 0x0000FF00) >> 8);
        userBuf[offset++] = (byte)(dwAccountType & 0x000000FF);

        if (roomStr.Length > 0)
        {
            userBuf[offset++] = (byte)((roomStr.Length & 0xFF00) >> 8);
            userBuf[offset++] = (byte)(roomStr.Length & 0x00FF);

            byte[] roomStrByte = System.Text.Encoding.UTF8.GetBytes(roomStr);
            roomStrByte.CopyTo(userBuf, offset);
            offset += roomStr.Length;
        }
        return userBuf;
    }
    private static byte[] CompressBytes(byte[] sourceByte)
    {
        MemoryStream inputStream = new MemoryStream(sourceByte);
        Stream outStream = CompressStream(inputStream);
        byte[] outPutByteArray = new byte[outStream.Length];
        outStream.Position = 0;
        outStream.Read(outPutByteArray, 0, outPutByteArray.Length);
        return outPutByteArray;
    }

    private static Stream CompressStream(Stream sourceStream)
    {
        MemoryStream streamOut = new MemoryStream();
        ZOutputStream streamZOut = new ZOutputStream(streamOut, zlibConst.Z_DEFAULT_COMPRESSION);
        CopyStream(sourceStream, streamZOut);
        streamZOut.finish();
        return streamOut;
    }

    public static void CopyStream(System.IO.Stream input, System.IO.Stream output)
    {
        byte[] buffer = new byte[2000];
        int len;
        while ((len = input.Read(buffer, 0, 2000)) > 0)
        {
            output.Write(buffer, 0, len);
        }
        output.Flush();
    }

    private string HMACSHA256(string identifier, long currTime, int expire, string base64UserBuf, bool userBufEnabled)
    {
        string rawContentToBeSigned = "TLS.identifier:" + identifier + "\n"
             + "TLS.sdkappid:" + sdkappid + "\n"
             + "TLS.time:" + currTime + "\n"
             + "TLS.expire:" + expire + "\n";
        if (true == userBufEnabled)
        {
            rawContentToBeSigned += "TLS.userbuf:" + base64UserBuf + "\n";
        }
        using (HMACSHA256 hmac = new HMACSHA256())
        {
            UTF8Encoding encoding = new UTF8Encoding();
            Byte[] textBytes = encoding.GetBytes(rawContentToBeSigned);
            Byte[] keyBytes = encoding.GetBytes(key);
            Byte[] hashBytes;
            using (HMACSHA256 hash = new HMACSHA256(keyBytes))
                hashBytes = hash.ComputeHash(textBytes);
            return Convert.ToBase64String(hashBytes);
        }
    }
}

调用的方法也很简单

unity usb通讯 unity 通信插件_unity usb通讯


填上对应的askid,sdkkey和用户的userid即可

安卓打包aar导入unity使用

安卓端

上面安卓打包的流程基本不变,需要多加亿些细节

注意事项1

比如需要把含腾讯sdk的项目改成library(这样才能打包成aar/jar)

这个其实不难,在as里左上角的file,new module,然后选择Android library就好了

后面的操作和前面的一样.这里贴一个项目结构,红圈的部分是我们下面会改动的地方

unity usb通讯 unity 通信插件_android studio_02

注意事项2

由于我们是在安卓里接收到的信息,所以是需要在接收到消息之后发送到unity然后进行处理的
那么涉及到和unity交互的话必然是需要一个和unity通信的桥梁的,这个桥梁之前安卓和unity交互的时候其实有写的,就是unity-class.jar这个包,需要把它导入到刚才创建好的library项目里的libs,然后点击这个jar右键 add as library这样才能正常引用
然后就是在接收到消息的方法里添加一个unityplayer.sendmessage(unity场景中的对象名称,方法名,具体要传的参数);

注意事项3

然后在这个library的build.gradle里添加下面的这个代码,让as能帮我们打包出aar包

task CopyPlugin(type: Copy) {
    dependsOn assemble
    from('build/outputs/aar')
    into('../../Assets/Plugins/Android')
    include(project.name + '.aar')
}

然后在as 的右边知道gradle.找到我们创建的library,点击other,找到我们的CopyPlugins,然后双击他就会自动帮我们打包aar了

注意事项4

看过我之前的帖子或者对安卓和unity交互有经验的大佬应该都会知道的,需要在打包成aar之后找到这个aar,把后缀名改成zip.然后双击打开,找到里面的libs,删掉里面的unity-class.jar(其实这里腾讯的sdk也是可以直接删掉的,至于原因后面再讲).然后再把后缀名改回来
到这里安卓部分的内容就结束了

注意事项5

打包的aar的路径在哪?

这个看注意事项3里,写着build/outputs/aar,可以在那里面找

unity usb通讯 unity 通信插件_unity_03

注意事项6

安卓里的项目路径包名尽量和unity的包名保持一致,如果一开始创建library的时候没有一致,后面再修改一致也是可以的,亲测没问题

保持一致的原因在下想到的大概是这个包到时会导入到unity的Assets/Plguins/Android的文件夹下,而这个路径下的内容都会被打包成安卓项目里的libs的内容,而我们安卓在进行方法和类的引用的时候,当前的类都会有这个显示

unity usb通讯 unity 通信插件_ide_04


所以如果我们的包名不一致的话应该就找不到libs(也即是我们的Assets/Plguins/Android)下的aar和jar了.所以尽量保持一致就好(调试的时候在下就是因为包名没有保持一致,在unity打包运行之后,安卓调用第三方启动类的时候就报错了,显示找不到对应的类的异常).后面试了一下,好像不同的包名也不会怎样,只要你引用的时候包名没错就行了…真是神奇的

unity端

注意事项1

安卓打包出来的aar需要放在unity项目的Assets/Plguins/Android文件夹下才有效果
由于这次我们在安卓里使用的第三方插件是aar的格式而不是jar的格式,所以在unity里我们还需要把前面使用到的腾讯的sdk的aar导入到Assets/Plguins/Android下才行,否则unity在打包成apk之后进行调用的时候会报引用异常的问题
然后在下试了一下, 即使在前面打包好的aar里把libs的腾讯第三方aar删掉,只要保证unity的Assets/Plguins/Android路径下有那个aar包,unity打包出来的apk也能正常的引用这个第三方的aar包(至于原理以在下目前的时间和能力还不足以给出定论,不过大概猜一下unity在打包的过程中会把Assets/Plguins/Android下的内容都当做as里的libs进行处理,所以只要在这里有对应的libs,unity在进行调用的时候就都能正常引用,但是如果在aar包里的libs的引用unity可能还没有做到这么细节的掌握吧,也可能是在下的unity版本过低)

注意事项2

在unity里怎么调用安卓的类和方法呢,这个之前安卓和unity交互的贴子里也有说过了
这里就提多一嘴,因为我们安卓里的第三方插件的方法里有一个是需要传入context对象(也就是activity)的,但是我们安卓的aar包里只提供方法,没有activity,这个时候需要把我们unity当前的activity传给安卓就可以了.详细的代码看之前的帖子就好了
安卓和unity交互-第三方插件需要context/activity

总结

到这里,腾讯tim的sdk的使用流程基本就结束了.按照上面的流程的话应该是没有啥问题的
这里说一下,我的unity版本是2019.4.28f,as的版本是3.4,gradle版本是5.4.1
如果上面的步骤有问题的话可以看看是不是版本的问题
一些想说的,程序员的工作做到现在大概知道了其中的一些规则(在下不是科班出身),当然也知道其中的危害(黑眼圈,害怕功能不能实现,临时出现的bug导致的焦虑,一直盯着电脑没有任何其他放松的渠道等,而且工资还很低)
个人觉得可能这个当做兴趣爱好更好一点,除非你是一个很自律的人,除了工作之外能很合理的安排好自己的休息时间和放松时间(或者公司有对应的放松福利等环节,亦或者是你单纯的身体心理都很强大),不过这样的人我想从事审什么行业应该都会很强
没有上面条件的大家,个人确实不推荐入行,甭管这里面的诱惑有多大,工资看着有十几k,但都是熬夜加班换来的
是想要健康还是金钱,大家都是成年人自己选择吧,在下只是谈谈自己这段时间的感受和看法

当前现在的还是会坚持到完成的,毕竟已经跟了一年多了,不管这个项目的底层有多差都是自己努力过的证据,还不想放弃.加油吧
后面还有其他的新功能实现的话会继续更新帖子的~
如果你觉得对你有用的话不妨点个赞?(虽然不知道点赞有啥用,不过好像大家都这么结尾)