Unity 华为快游戏JS桥接 实现写日志等功能

之前接入微信小游戏本身代码js桥接比较完善,抖音小游戏有缺少但也没缺的这么多,华为这边的API,大残啊!官方转换插件Github仓库上一次提交在3月份。(截至现在)API给的很简略,接入js代码那里说看demo,但unity的demo里面没jslib,另一个比较完善的demo看起来像是cocos的,比较无奈。

还好用unity导出的webgl产物,和导出rpk这两部是分开的,测试调整jslib可以之间改xxx.framework.js来快速尝试,不用浪费大量的时间用来打包。

华为官方的JS API文档中给出的API全局对象是qg, 在转换插件内部的代码QGSDK-Call-JS.jslib中可以找到大量的QG_开头的函数,没有qg字段,但API看起来都使用ral作为入口,再看打包插件里面的ral.js是调用qg的。

测试发现 ral === window.ral, qg === window.qg, 直接通过Object.keys(x).ForEach()遍历打印qgral两个对象,键值对的文本是能对的上的。直接调用又有些内容不同,比如有qg.showToast但没有ral.showToast,都使用qg,测试qg.showToast是能用的,不知道会不会有其他问题。

以下为桥接代码的思路展示,一些接口基于实际需求进行了简化。

处理js返回C#的回调:

 using LitJson; using System.Collections.Generic; using UnityEngine;  namespace Plugins.SDK {     public delegate void SDKCallback(int code, string msg, string dataStr);      public static class SDKCode {         public const int Succeed = 1;         public const int Failed = -1;         public const int Cancel = -2;     }      // 用于处理回调     public class CsJsEventHandler : MonoBehaviour     {         private static CsJsEventHandler s_Instance = null;         public static CsJsEventHandler Instance         {             get             {                 if (s_Instance == null)                 {                     GameObject go = new GameObject("CsJsEventHandler");                     GameObject.DontDestroyOnLoad(go);                     s_Instance = go.AddComponent<CsJsEventHandler>();                 }                 return s_Instance;             }         }     }      private readonly Dictionary<string, SDKCallback> m_Callbacks = new Dictionary<string, SDKCallback>();     private readonly HashSet<string> m_PersistentCallbacks = new HashSet<string>();      public void StartUp()     {     }      public string AddCallback(string funcName, SDKCallback callback, bool persistent = false)     {         string callbackId = $"{funcName}_{System.DateTime.Now:ddHHmmssfff}";         m_Callbacks[callbackId] = callback;         if (persistent)         {             m_PersistentCallbacks.Add(callbackId);         }         return callbackId;     }      public void RemoveCallback(string callbackId)     {         m_Callbacks.Remove(callbackId);         m_PersistentCallbacks.Remove(callbackId);     }      // 经常调用又是异步的函数     public string AddCallbackSingleton(string funcName, SDKCallback callback)     {         string callbackId = $"{funcName}_singleton";         m_Callbacks[callbackId] = callback;         m_PersistentCallbacks.Add(callbackId);         return callbackId;     }      public string GetCallbackSingleton(string funcName)     {         string callbackId = $"{funcName}_singleton";         return m_PersistentCallbacks.Contains(callbackId) ? callbackId : string.Empty;     }      public string RemoveCallbackSingleton(string funcName)     {         string callbackId = $"{funcName}_singleton";         RemoveCallback(callbackId);     }      public void ClearCallbacks()     {         m_Callbacks.Clear();         m_PersistentCallbacks.Clear();     }      private static string GetJsonString(JsonData obj, string key)     {         if (!obj.ContainsKey(key)) return string.Empty;         return obj[key]?.ToString() ?? string.Empty;             }      private static int GetJsonInt(JsonData obj, string key, int defaultValue = 0)     {         string s = GetJsonString(obj, key);         if (int.TryParse(s, out int v))         {             return v;         }         return defaultValue;     }      public void HandleJsEvent(string jsonStr)     {         if (string.IsNullOrEmpty(jsonStr)) return;          JsonData jsonData = JsonMapper.ToObject(jsonStr);         string callbackId = GetJsonString(jsonData, "callbackId");         if (m_Callbacks.TryGetValue(callbackId, out SDKCallback callback))         {             int code = GetJsonInt(jsonData, "code", SDKCode.Failed);             string msg = GetJsonString(jsonData, "msg");             string dataStr = GetJsonString(jsonData, "data");              if (!m_PersistentCallbacks.Contains(callbackId))             {                 m_Callbacks.Remove(callbackId);             }             callback.Invoke(code, msg, dataStr);         }     } }  

jslib

/* *   handler return code: *       SUCCEED: 1 *       FAILED: -1 *       CANCEL: -2 * *   只展示部分函数,请根据实际需求自行添加 */  var js_bridge_sdk_api = {  // File System // 主要是调试日志用, 能用同步的都用同步了      JSB_GetUserDataPath: function() {         return JSBHelper.stringToBuffer(qg.env.USER_DATA_PATH);     },     JSB_FSMAccessSync: function(path) {         var fsm = qg.getFileSystemManager();         try {             fsm.accessSync(UTF8ToString(path));             return JSBHelper.stringToBuffer("access:ok");         } catch(error) {             return JSBHelper.stringToBuffer(`error:${error}`);         }     },     JSB_FSMCopyFileSync: function(path) { /* ... */ },     JSB_FSMMkdirSync: function(dirPath) { /* ... */ },     JSB_FSMRmdirSync: function(dirPath) { /* ... */ },     JSB_FSMReaddirSync: function(dirPath) {         var fsm = qg.getFileSystemManager();         try {             var result = fsm.readdirSync(UTF8ToString(dirPath));             var text = JSON.stringify(result);             return JSBHelper.stringToBuffer(text);         } catch(error) {             return JSBHelper.stringToBuffer("[]");         }     },     JSB_FSMUnlinkSync: function(path) { /* ... */ },     JSB_FSMReadTxtFile: function(filePath) {         // readFileSync可以读utf8和binary, 不会写byte[]数组怎么从js传递到C#, 但因为没有需求, 先直接跳过了         var fsm = qg.getFileSystemManager();         try {             var result = fsm.readFileSync(UTF8ToString(dirPath), "utf8");             return JSBHelper.stringToBuffer(result);         } catch(error) {             return JSBHelper.stringToBuffer("");         }     },     JSB_FSMWriteTxtFile: function(filePath, text) {         // 同上         var fsm = qg.getFileSystemManager();         try {             fsm.readFileSync(UTF8ToString(filePath), UTF8ToString(text), "utf8");             return JSBHelper.stringToBuffer("ok");         } catch(error) {             return JSBHelper.stringToBuffer(`error:${error}`);         }     },     JSB_FSMAppendTxtFile: function(filePath, text) {         // 同上         var fsm = qg.getFileSystemManager();         try {             fsm.appendFileSync(UTF8ToString(filePath), UTF8ToString(text), "utf8");             return JSBHelper.stringToBuffer("ok");         } catch(error) {             return JSBHelper.stringToBuffer(`error:${error}`);         }     },  // UI      JSB_ShowToast: function(config) {         // 参数尽量简单, 用一个json字符串传递         var options = JSON.parse(UTF8ToString(config));         qg.showToast(options);     },     JSB_HideToast: function(config) {         qg.hideToast({});     },     JSB_ShowModal: function(config) {         // 快应用加载器里面这个modal好像能点穿的, 略坑         var options = JSON.parse(UTF8ToString(config));         var callbackId = options.callbackId; // 这里的callbackId是C#传递过来的, 后面展示         delete options.callbackId;         options.success = function(res) {             if (res.confirm) {                 JSBHelper.returnEvent(callbackId, 1, "", "");             }             else if (res.cancel) {                 JSBHelper.returnEvent(callbackId, -2, "", "");             }         };         options.fail = function() {             JSBHelper.returnEvent(callbackId, -1, "", "");         };         qg.showModal(options);     },     JSB_ShowLoading: function(title) {         qg.showLoading({             title: UTF8ToString(title),             mask: true,         });     },     JSB_HideLoading: function() {         qg.hideLoading({});     },  // Misc      JSB_GetBatteryLevel: function() {         var info = qg.getBatteryInfoSync();         return parseInt(info.level);     },     JSB_TriggerGC: function() {         qg.triggerGC();     },     JSB_GetNetworkType: function() {         var callbackId_j = UTF8ToString(callbackId);         qg.getNetworkType({             success: function(res) {                 var networkType = res.networkType;                 if (networkType === undefined) networkType = "unknown";                 JSBHelper.returnEvent(callbackId_j, 1, "", networkType);             },             fail: function() {                 JSBHelper.returnEvent(callbackId_j, -1, "", "");             }         });     },     JSB_OnNetworkStatusChange: function(callbackId) {         // 因为回调一旦挂上就没有移除的需求, 没有把callback存起来         var callbackId_j = UTF8ToString(callbackId);         qg.onNetworkStatusChange(function(res) {             var data = JSON.stringify(res);             JSBHelper.returnEvent(callbackId_j, 1, "", data);         });     },     JSB_Vibrate: function(mode) {         if (mode == 0) {             qg.vibrateShort({});         }         else {             qg.vibrateLong({});         }     },     JSB_PreviewImage: function(url) {         qg.previeImage({             urls: [                 UTF8ToString(url)             ]         });     },  // Helper      $JSBHelper: {         stringToBuffer: function(valueStr) {             var bufferSize = lengthBytesUTF8(valueStr) + 1;             var buffer = _malloc(bufferSize);             stringToUTF8(valueStr, buffer, bufferSize);             return buffer;         },         returnEvent: function(callbackId, code, msg, dataStr) {             var obj = {                 callbackId: callbackId,                 code: code,                 msg: msg,                 data: dataStr,             };             var text = JSON.stringify(obj);             qg.unityInstance.Module.SendMessage("CsJsEventHandler", "HandleJsEvent", text);             // Unity2021 文档给的是"MyGameInstance", 需要到index.html里面自己新建一个var并获取unityInstance             // 但在这里不行, 测试"SendMessage"和"qg.unityInstance.Module.SendMessage"可用         }     } };  autoAddDeps(js_bridge_sdk_api, "$JSBHelper"); mergeInfo(LibraryManager.library, js_bridge_sdk_api);  

C#调用

 using LitJson; using System.Runtime.InteropServices;  namespace Plugins {     public static partial class JSBridgeExterns     { #if !UNITY_EDITOR         [DllImport("__Internal")]         public static extern string JSB_GetUserDataPath();          // ... 省略, 函数签名对上即可 #else         // 如果调用的代码不想写#if, 可以再写一遍UNITY_EDITOR的版本         public static string JSB_GetUserDataPath() => default;          // ... #endif     }      // 调用展示     public static class Test     {         public static void Test()         {             // 基础调用              // 获取文件目录                         string userDataPath = JSBridgeExterns.JSB_GetUserDataPath();              // toast             JsonData toastOptions = new JsonData();             toastOptions["title"] = "Hello, world!";             toastOptions["icon"] = "none";             toastOptions["mask"] = true;             toastOptions["duration"] = 2000;             JSBridgeExterns.JSB_ShowToast(toastOptions.ToJson());              // 带回调的调用             SDKCallback callback = (code, msg, dataStr) => {                 if (code == SDKCode.Succeed)                 {                     // ...                 }                 else if (code == SDKCode.Cancel)                 {                     // ...                 }                 else if (code == SDKCode.Failed)                 {                     // ...                 }             };             string callbackId = CsJsEventHandler.Instance.AddCallback("showModal", callback);             JsonData modalOptions = new JsonData();             modalOptions["title"] = "系统";             modalOptions["content"] = "您有新短消息, 请注意查收";             modalOptions["callbackId"] = callbackId;             JSBridgeExterns.JSB_ShowModal(modalOptions.ToJson());              // 挂网络状态监听             string singletonCallbackId = CsJsEventHandler.Instance.AddCallbackSingleton("onNetworkStatusChange", _OnNetworkStatusChange);             JSBridgeExterns.JSB_OnNetworkStatusChange(singletonCallbackId);         }          private static void _OnNetworkStatusChange(int code, string msg, string dataStr)         {             string json = dataStr;             // ...         }     } }  

发表评论

相关文章