網站首頁 編程語言 正文
背景
我們在使用 Cocos 和 Native 進行交互的時候,發現體驗并不是特別的友好。 如下所示,為我們項目當中的一段代碼(代碼已脫敏),當檢測到發生了 js 異常,我們需要通知 Native 端去做一些處理。
jsException: function (scence, msg, stack) {
if (cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID) {
jsb.reflection.callStaticMethod(
"xxx/xxx/xxx/xxx/xxx",
"xxx",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
scence, msg, stack
);
} else if (cc.sys.isNative && cc.sys.os === cc.sys.OS_IOS) {
jsb.reflection.callStaticMethod(
"xxx",
"xxx:xxx:xxx:",
scence, msg, stack
);
}
}
從代碼當中我們可以看出有以下問題:
- 方法調用非常麻煩,特別是 Android 端,需要明確的指出方法的路徑,方法名,參數類型,特別容易出錯;
- 一旦 Native 端的方法發生變化,Cocos層必須要同步修改,否則就會出現異常,甚至有可能 crash;
- 不方便回調;
我們嘗試著用注解的思路來解決這些問題,從而設計并實現了 ABCBinding 。ABCBinding 能夠極大的簡化 Cocos和 Native 的交互,方便維護。
ABCBinding 的結構設計
ABCBinding 的結構如下圖所示:
ABCBinding 包含 Native 和 TS 兩個部分,Native 負責約束本地的方法,TS 負責對 Cocos 層提供調用 Native 方法的接口。 我們重新定義了一套 Cocos 和 Native 的交互模式:
- 提供 Cocos 訪問的 Native 方法必須為 public static,且參數必須為 Transfer(Transfer 為 SDK 提供的接口,能夠在 Cocos 層和 Native 層傳遞數據);
- 方法必須使用 ABCBinder 注解修飾,并在注解內傳入約定好的 tag,此 tag 用于唯一標識這個方法;
- 使用 SDK 提供的 callNativeMethod 方法,傳入約定好的 tag 調用 Native 方法。
例子: 如下所示,為調用 Native 方法去下載,我們只需要傳入約定好的 tag:downloadFile,并傳入參數,便可以進行下載了。 TS 層:
Binding.callNativeMethod('downloadFile', { url: 'https://xxx.jpeg' }
).then((result) => {
this.label.string = `下載成功:path=${result.path}`;
}).catch((error) => {
this.label.string = error.msg;
});
Native 層:
@ABCBinder("downloadFile")
public static void download(Transfer transfer){
new Thread(new Runnable() {
@Override
public void run() {
String url = transfer.get("url","");
try{
//下載中
...
//下載成功
TransferData data = new TransferData();
data.put("path","/xxx/xxx.jpg");
transfer.onSuccess(data);
}catch (Exception e){
//失敗
transfer.onFailure(e);
}
}
}).start();
}
通過例子可以看到,使用 ABCBinding 能夠讓 Cocos 和 Native 的交互簡單很多,我們再也不用再傳入復雜的路徑和參數了,而且回調也變得很優雅。接下來我們來看看 ABCBinding 是如何實現的。
具體實現
從上面的例子我們可以看出,ABCBinding 是通過 tag 來實現 Cocos 和 Native 進行交互的,那 SDK 是如何通過 tag 來找到對應的方法呢?
通過 tag 找到 Native 方法
我們定義了編譯時注解 ABCBinder,
@Retention(RetentionPolicy.CLASS)//編譯時生效
@Target({ElementType.METHOD})//描述方法
public @interface ABCBinder {
String value();
}
在編譯期間會生成一個類 ABCBindingProxy,成員變量 executors 包含了所有對應的 tag 和方法。其中真正可執行的方法被包裝在了 ExecutableMethod 接口當中。
//以下為自動生成的代碼
private Map executors = new java.util.HashMap();
private ABCBindingProxy() {
executors.put("test2",new ExecutableMethod() {
@Override
public void execute(Transfer t) {
com.example.abcbinding.MainActivity.test2(t);
}
});
executors.put("test1",new ExecutableMethod() {
@Override
public void execute(Transfer t) {
com.example.abcbinding.MainActivity.test1(t);
}
});
}
public interface ExecutableMethod {
void execute(Transfer t);
}
因此我們只需要通過 executors 和 tag 就可以找到了對應的方法了,接著我們看看 TS 是如何與 Native 進行交互的, 以下是 SDK 里面 TS 層的部分代碼,SDK 屏蔽了調用的具體細節,將請求的參數轉變成為 json 字符串,并將相關的參數傳遞給 SDK 內部的方法 execute,由 execute 將請求轉發給真正的方法。
public callNativeMethod(methodName: string, args ?: Record<string, string | number | boolean>): Promise < any > {
...
if (cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID) {
let resultCode = jsb.reflection.callStaticMethod(
'com/tencent/abckit/binding/ABCBinding',
'execute',
'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I',
methodName,
typeof args === 'object' ? JSON.stringify(args) : '', cbName);
...
} else if (cc.sys.isNative && cc.sys.os === cc.sys.OS_IOS) {
let retId = jsb.reflection.callStaticMethod(
'ABCBinding',
'execute:methodName:args:callback:',
methodName,
typeof args === 'object' ? JSON.stringify(args) : '', cbName);
...
} else {
let msg = 'no implemented on this platform';
reject(msg);
}
}
這樣,我們就解決了通過 tag 找到對應方法的問題。但還有兩個問題需要解決: 如何約束 Native 方法?以及如何保證 tag 的唯一性?
約束 Native 方法
ABCBinding 規定,提供 Cocos 訪問的 Native 方法必須為 public static,參數必須為 Transfer,且 tag 必須要保持唯一性,那怎么來約束呢? 在代碼的編譯期間,我們會去檢查所有被 ABCBinder 修飾的方法,若發現這個方法并不符合我們的規范,則會直接報錯。 如下所示: 1.參數錯誤
@ABCBinder("test1")
public static void test1(Transfer transfer, int a) {
Log.i("測試", "test()");
}
2.方法非 static
@ABCBinder("test2")
public void test2(Transfer transfer) {
Log.i("測試", "test()");
}
3.方法非 public
@ABCBinder("test3")
protected static void test3(Transfer transfer) {
Log.i("測試", "test()");
}
4.tag 重復
@ABCBinder("helloworld")
public static void test1(Transfer transfer) {
Log.i("測試", "test()");
}
@ABCBinder("helloworld")
public static void test2(Transfer transfer) {
Log.i("測試", "test()");
}
優雅的回調
SDK 會在編譯期間自動生成 callJs 方法,所有的回調都是通過 callJs 方法實現。Native 方法只需要調用 Transfer 所提供的回調接口,便可以輕松的將結果回調給 Cocos。由于 callJs 代碼是自動生成,所以 SDK 不需要直接依賴 Cocos 庫,只需要業務方依賴即可。
//以下為自動生成的代碼
public void callJs(final String globalMethod, final TransferData params) {
org.cocos2dx.lib.Cocos2dxHelper.runOnGLThread(new Runnable() {
@Override
public void run() {
String command = String.format("window && window.%s && window.%s(%s);",
globalMethod,globalMethod, params.parseJson());;
org.cocos2dx.lib.Cocos2dxJavascriptJavaBridge.evalString(command);;
}
});
}
ABCBinding 提供了onProcess,onSuccess 和 onFailed 回調方法, 以下為 downloadFile 接口的的回調示例: TS 層:
Binding.withOptions({
//回調onProcess
onProgress: (progress) => {
let current = progress.current;
this.label.string = `progress:${current}`;
}
}).callNativeMethod(
'downloadFile',
{ url: 'https://xxxx.jpg' }
).then((result) => {
//回調onSuccess
this.label.string = `下載成功:path=${result.path}`;
}).catch((error) => {
//回調onFailed
if (error) {
this.label.string = error.msg;
}
});
Native 層:
@ABCBinder("downloadFile")
public static void download(Transfer transfer){
new Thread(new Runnable() {
@Override
public void run() {
String url = transfer.get("url","");
try{
//模擬下載過程
for(int i =0;i<100;i++) {
transfer.onProgress("current", i);
}
//下載成功
TransferData data = new TransferData();
data.put("path","/xxx/xxx.jpg");
transfer.onSuccess(data);
}catch (Exception e){
//失敗
transfer.onFailure(e);
}
}
}).start();
}
其他 feature
抹平系統差異
使用 ABCBinding 無須再判斷當前系統是 Android 還是 IOS ,只需對應 Native 方法的 tag 保持一致即可。
Binding.callNativeMethod('isLowDevice').then(({isLowDevice}) => {
console.log(isLowDevice);
})
無需關心線程切換
Native 方法使用 Transfer 回調時,ABCBinding 會自動切換到 Cocos 線程執行,無需業務方關心。
@ABCBinding("getHardwareInfo")
public static void getHardwareInfo(Transfer transfer) {
TransferData data = new TransferData();
data.put("brand", Build.BRAND);
data.put("model", Build.MODEL);
data.put("OsVersion", Build.VERSION.RELEASE);
data.put("SdkVersion", Build.VERSION.SDK);
transfer.onSuccess(data);
}
支持超時
ABCBinding 支持設置超時,其中超時的時間單位為秒,如下所示,超時會回調到 catch 方法當中。
Binding.withOptions({
timeout: 120,
onProgress: (progress) => {
let current = progress.current;
this.label.string = `progress:${current}`;
}
}).callNativeMethod(
'downloadFile',
{ url: 'https://xxxx.jpg' }
).then((result) => {
this.label.string = `下載成功:path=${result.path}`;
}).catch((error) => {
if (error.code == ERROR_CODE_TIMEOUT) {
console.log("超時");
}
});
彩蛋:在熱更新當中的應用
我們在使用 Cocos 熱更新服務的過程中發現,怎么確定 Cocos 熱更新包能夠發布到哪個 App 版本,是個難題。Cocos 熱更新包能不能在這個 App 版本上正確運行,跟兩個因素有關,Cocos 版本和 Native 接口。
Cocos 版本 如果 App 和熱更包的 Cocos 版本不一致,那么很有可能這個熱更包無法在 App 上運行,除非官方做了一些兼容處理。不過這個因素可控,Cocos 的版本不會頻繁的升級,而且我們知道 Cocos 版本和 App 版本的對應關系。
Native 接口 如果熱更包調用了某個 Native 的接口,但是這個接口在有些版本上不存在,那該熱更包就無法在這個版本的 App 上運行。 在我們的業務場景當中,Cocos 版本不會頻繁變更,但是每個版本的 Native 代碼可能會相差較大,人工來核對每個版本的 Native 接口變更是一件極為費時費力的事情。 那么 ABCBinding 能幫助我們做什么呢?
讓熱更包兼容所有版本的 App
首先我們去除 Cocos 版本的因素,因為這個因素可控,且業務方無法解決。 ABCBinding 知道本地有哪些接口可用,所以當 Cocos 調用了一個不存在的接口時,我們會返回一個特殊的 code,這樣熱更包只需要在內部做兼容處理就可以了。 如下所示:
Binding.callNativeMethod('downloadFile', { url: 'https://xxx.jpeg' }
).then((result) => {
//處理邏輯
}).catch((error) => {
if(error.code == ERROR_CODE_METHOD_NOT_DEFINED){
console.log("未找到該方法");
}
});
元素綁定
既然熱更新跟這兩個元素有關,我們就可以通過這兩個元素,讓 App 和熱更包進行匹配,如果能夠匹配,那么這個熱更包就可以下發到這個版本的 App 上。 Cocos 版本:我們在打包的時候就可以確定; Native 接口:在打包的過程中,ABCBinding 可以將當前所支持的接口,按照約定的方式生成一個元素。 例如:本地的接口有 test1,test2,test3 我們可以將接口按照指定的方式排序拼接取 md5 值,生成第二個元素。 這樣當 App 和熱更新包的兩個元素能夠匹配時,就能夠下發了。
原文鏈接:https://juejin.cn/post/7174615967324962853
相關推薦
- 2022-10-31 解決Python3中二叉樹前序遍歷的迭代問題_python
- 2022-06-21 C語言零基礎精通變量與常量_C 語言
- 2022-01-21 win10 如何做到 C盤 的絕對干凈,所有軟件都安裝到D盤,C盤只用來存操作系統。
- 2022-11-06 關于useEffect的第二個參數解讀_React
- 2022-11-19 springboot整合使用云服務器上的Redis方法_Redis
- 2022-10-23 C#中數組擴容的幾種方式介紹_C#教程
- 2022-08-21 Go語言的互斥鎖的詳細使用_Golang
- 2022-06-02 Python學習之迭代器詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支