亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android本地搜索業(yè)務優(yōu)化方案

 更新時間:2023年05月16日 09:09:55   作者:云音樂技術團隊  
這篇文章主要為大家介紹了Android本地搜索業(yè)務優(yōu)化方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

在本文中,我們將通過 Android 本地搜索業(yè)務介紹如何使用 JavaScriptCore(以下簡稱 JSC)和Java Native Interface(以下簡稱 JNI)相關技術來實現(xiàn)搜索效率提升。

背景

本地搜索業(yè)務內(nèi)部使用動態(tài)下發(fā) JS 代碼實現(xiàn)一些業(yè)務邏輯,用戶觸發(fā)搜索到最終展示數(shù)據(jù)耗時久,體驗很差 ( 8000 首歌曲的處理量大概在 7 秒左右),分析:

  • 本地的 DB 和數(shù)據(jù)處理耗時占 50%
  • JS 引擎的數(shù)據(jù)傳輸上占 50%

DB 和數(shù)據(jù)處理不做討論,這里主要解決 JS 引擎的數(shù)據(jù)傳輸問題

基于現(xiàn)有方案的分析:

可以發(fā)現(xiàn) Native 在和 JVM 傳輸次數(shù)過多,且跨語言的數(shù)據(jù)傳輸序列化耗時

方案

結(jié)合現(xiàn)有業(yè)務特點:

  • 算法是變化的、動態(tài)下發(fā)的,所以代碼由 JS 實現(xiàn),故需要在 JS 引擎中執(zhí)行
  • Java 使用 JSC 需要借助 JNI,并加入一些邏輯處理
  • JNI 需要向 JS 引擎輸入數(shù)據(jù),同時需要獲取執(zhí)行得結(jié)果

得出如下流程圖

如何實現(xiàn)?

  • 準備好 JavaScriptCore 庫,這里復用 ReactNative 中的 so 庫
  • C++調(diào)用 JavaScriptCore 庫,實現(xiàn)部分邏輯,輸出業(yè)務層 a.so 庫
  • 上層使用 a.so 對庫進行調(diào)用

前置知識

方案實現(xiàn)需要了解 JavaScriptCore 和 JNI 的相關知識,下面分別介紹

JavaScriptCore 簡介

JavaScriptCore 是一個開源的 JavaScript 引擎,可以用來解析和執(zhí)行 JavaScript 代碼,類似的還有 V8、Hermes 等。

JSAPI 是 JavaScriptCore 的 C++接口,它提供了一組 C++類和函數(shù),可以用于將 JavaScript 嵌入到 C++程序中。JSAPI 提供了以下功能:

  • 創(chuàng)建和管理 JavaScript 對象和值
  • 執(zhí)行 JavaScript 代碼
  • 訪問 JavaScript 對象的屬性和方法
  • 注冊 JavaScript 函數(shù)
  • 處理 JavaScript 異常
  • 進行垃圾回收

JavaScriptCore 類型

  • JSC::JSObject:表示一個 JavaScript 對象。
  • JSC::JSValue:表示一個 JavaScript 值。
  • JSC::JSGlobalObject:表示 JavaScript 對象的全局對象。
  • JSC::JSGlobalObjectFunctions:包含一組函數(shù),用于實現(xiàn) JSAPI 的功能,如執(zhí)行 JavaScript 代碼、訪問 JavaScript 對象的屬性和方法等。

在 JSAPI 中,JavaScript 對象和值通過 JSC::JSObject 和 JSC::JSValue 類進行表示。
JSC::JSObject 表示一個 JavaScript 對象,它可以包含一組屬性和方法;
JSC::JSValue 表示一個 JavaScript 值,它可以是一個對象、一個數(shù)值、一個字符串或一個布爾值等。

JSAPI 提供了 JSC::JSGlobalObject 類作為 JavaScript 對象的全局對象,所有的 JavaScript 對象都是從該全局對象繼承而來。

API 介紹

JSContextGroupCreate

JSContextGroupRef 是一個包含多個 JSContext 的分組,它們可以共享內(nèi)存池和垃圾回收器,從而提高 JavaScript 執(zhí)行效率和減少內(nèi)存占用。

JSGlobalContextCreateInGroup

JSGlobalContextCreateInGroup 函數(shù)會創(chuàng)建一個 JSGlobalContextRef 類型的對象,表示一個 JavaScript 上下文對象,該對象包含一個虛擬機對象、內(nèi)存池、全局對象等成員變量。該函數(shù)返回值為創(chuàng)建的 JSGlobalContextRef 類型的對象,表示 JavaScript 上下文對象。
由于不同的 JSGlobalContextRef 對象擁有不同的全局對象,因此它們之間不會相互影響。在不同的 JSGlobalContextRef 對象中創(chuàng)建的 JavaScript 對象、函數(shù)、變量等,都是相互獨立的,它們之間不會共享數(shù)據(jù)或狀態(tài)。

JSEvaluateScript

用于執(zhí)行一段 JavaScript 代碼。其內(nèi)部工作機制主要包括以下幾個步驟:

  • 將 JavaScript 代碼轉(zhuǎn)換為抽象語法樹(AST)
    在執(zhí)行 JavaScript 代碼之前,JavaScriptCore 需要將其轉(zhuǎn)換為抽象語法樹(AST),這樣才能對其進行解析和執(zhí)行。JavaScriptCore 的 AST 解析器可以將 JavaScript 代碼轉(zhuǎn)換為一棵 AST 樹,其中每個節(jié)點代表了一條 JavaScript 語句或表達式。
  • 解析和執(zhí)行 AST 樹
    一旦生成了 AST 樹,JavaScriptCore 就可以對其進行解析和執(zhí)行了。在解析過程中,JavaScriptCore 會對 AST 樹進行遍歷,同時將其中的變量、函數(shù)等標識符與對應的值進行綁定。在執(zhí)行過程中,JavaScriptCore 會按照 AST 樹的結(jié)構(gòu)逐步執(zhí)行其中的語句和表達式,同時根據(jù)需要調(diào)用相應的函數(shù)和方法。
  • 將執(zhí)行結(jié)果返回給調(diào)用方
    一旦 JavaScript 代碼執(zhí)行完畢,JavaScriptCore 就會將其執(zhí)行結(jié)果返回給調(diào)用方。這個結(jié)果可以是任何 JavaScript 值,包括數(shù)字、字符串、對象、函數(shù)等。調(diào)用方可以根據(jù)需要對這個結(jié)果進行處理和使用。

JSEvaluateScript 是一個同步函數(shù),即在執(zhí)行完 JavaScript 代碼之前,它會一直等待,直到 JavaScript 代碼執(zhí)行完畢并返回結(jié)果。這意味著,在執(zhí)行長時間運行的 JavaScript 代碼時,JSEvaluateScript 函數(shù)可能會阻塞程序的運行。

我們可以通過線程來對 JS 代碼的異步化(以下省略一些判空邏輯)

void completionHandler(JSContextRef ctx, JSValueRef value, void *userData) {
    JSValueRef *result = (JSValueRef *)userData;
    *result = value;
}
void evaluateAsync(JSContextRef ctx, const char* script, JSObjectRef thisObject, JSValueRef* exception, JSAsyncEvaluateCallback completionHandler) {
    // 異步執(zhí)行
    std::thread([ctx, script, thisObject, exception, completionHandler]() {
        // 執(zhí)行腳本
        JSStringRef scriptStr = JSStringCreateWithUTF8CString(script);
        JSValueRef result = JSEvaluateScript(ctx, scriptStr, thisObject, nullptr, 0, exception);
        JSStringRelease(scriptStr);
        // 回調(diào) completionHandler
        completionHandler(result, exception);
    }).detach();
}

此外還應關注注冊到 JS 環(huán)境中的 C 接口回調(diào),這里因盡快返回,如果有耗時任務,則需要將結(jié)果通過異步去通知 JS 層,否則會阻塞 JS 線程(也就是調(diào)用該函數(shù)的線程)。

關鍵代碼示例

下面實現(xiàn)了一個向 global 中添加 getData 的 Native 函數(shù)

// 回調(diào)函數(shù)
JSValueRef JSCExecutor::onGetDataCallback(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
                                   size_t argumentCount, const JSValueRef arguments[],
                                   JSValueRef *exception) {
        LOGD(TAG, "onGetDataCallback");
        NativeBridge::JSCExecutor *executor = static_cast<NativeBridge::JSCExecutor *>(JSObjectGetPrivate(
                thisObject));
        ... // 省略參數(shù)、類型等判斷
        executor->xxx(); // C++業(yè)務側(cè)
        return xxx; // 返回到JS內(nèi)
}
bool JSCExecutor::initJSC() {
        // 初始化 JSC 引擎
        context_group_ = JSContextGroupCreate();
        JSClassDefinition global_class_definition = kJSClassDefinitionEmpty;
        global_class_ = JSClassCreate(&global_class_definition);
        // 在js執(zhí)行上下文環(huán)境(Group)中創(chuàng)建一個全局的js執(zhí)行上下文
        context_ = JSGlobalContextCreateInGroup(context_group_, global_class_);
        if (!context_) {
            LOGE(TAG, "create js context error!");
            return false;
        }
        // 獲取js執(zhí)行上下文的全局對象
        global_ = JSContextGetGlobalObject(context_);
        if (!global_) {
            LOGE(TAG, "get js context error!");
            return false;
        }
        // 綁定c++對象地址
        JSObjectSetPrivate(global_, this);
        // 注冊函數(shù)
        JSStringRef dynamic_get_data_func_name = JSStringCreateWithUTF8CString("getData");
        JSObjectRef dynamic_get_data_obj = JSObjectMakeFunctionWithCallback(context_,
                                                                            dynamic_get_data_func_name,
                                                                            onGetDataCallback);
        JSObjectSetProperty(context_,
                            obj,
                            dynamic_get_data_func_name,
                            dynamic_get_data_obj,
                            kJSPropertyAttributeDontDelete,
                            NULL);
        return true;
    }

JNI(Java Native Interface)

JNI 全稱為 Java Native Interface,是一種允許 Java 代碼與本地(Native)代碼交互的技術。JNI 提供了一組 API,可以使 Java 程序訪問和調(diào)用本地方法和資源,也可以使本地代碼訪問和調(diào)用 Java 對象和方法。
此方案需要使用 JNI 進行雙向調(diào)用。

C 調(diào)用 Java

步驟:

  • 獲取 JNIEnv 指針:JNIEnv 是一個結(jié)構(gòu)體指針,代表了 Java 虛擬機調(diào)用本地方法時的環(huán)境信息。JNIEnv 指針可以通過 Java 虛擬機實例、調(diào)用線程等參數(shù)獲取。
  • 獲取 Java 類、方法、字段等的 ID:通過 JNIEnv 指針,可以使用函數(shù) FindClass()、GetMethodID()、GetStaticMethodID()、GetFieldID()等函數(shù)獲取 Java 類、方法、字段等的 ID。比如在 C 中去創(chuàng)建 Java 對象,并操作相關 Java 對象
  • 調(diào)用 Java 方法或訪問 Java 字段:通過 JNIEnv 指針和 Java 對象的 ID,可以使用 CallObjectMethod()、CallStaticObjectMethod()、GetDoubleField()、SetObjectField()等函數(shù)調(diào)用 Java 方法或訪問 Java 字段。

JavaC

步驟:

  • 設計規(guī)劃功能、接口
  • Java 聲明 Native 方法
  • 按照 JNI 標準實現(xiàn)方法,并通過 System.loadLibrary()加載
public class TestJNI {
   static {
      System.loadLibrary("xxx.so"); // 加載動態(tài)鏈接庫
   }
   // 聲明本地方法
   private native void PrintHelloWorld();
   // 靜態(tài)方法
   public static native String GetVersion();
}
// C實現(xiàn)函數(shù)
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { ... } // so初始化回調(diào)函數(shù)
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserved) { ... } // so卸載回調(diào)函數(shù)
// 實現(xiàn)
包名_PrintHelloWorld(JNIEnv *env, jobject thiz) { ... }
包名_GetVersion(JNIEnv *env, jclass clazz) { ... }

關注點

JNI 的編寫會遇到有很多坑,比如 Java 封裝對象和 C++對象的生命周期關系、異步調(diào)用邏輯、編譯器報錯不完善、類型不匹配、JVM 環(huán)境不一致、運行線程不一致等等,下面是一些常用的規(guī)則

內(nèi)存

  • 在 C/C++代碼中,使用對象或智能指針去管理內(nèi)存,若使用 malloc、calloc 等函數(shù)分配內(nèi)存,然后使用 free 函數(shù)釋放內(nèi)存。
  • 在 JNI 中,通過 jobject 等 JNI 對象的創(chuàng)建和銷毀方法,手動管理 Java 內(nèi)存。例如,在 JNI 中創(chuàng)建 Java 對象時,需要調(diào)用 NewObject 等 JNI 方法創(chuàng)建 Java 對象,然后在使用完后,需要調(diào)用 DeleteLocalRef 等 JNI 方法釋放 Java 對象。

性能

  • 避免頻繁創(chuàng)建和銷毀 JNI 引用:創(chuàng)建和銷毀 JNI 引用(如 jobject、jclass、jstring 等)的開銷比較大,應該盡量避免頻繁創(chuàng)建和銷毀 JNI 引用。
  • 使用本地數(shù)據(jù)類型:JNI 支持本地數(shù)據(jù)類型(如 jint、jfloat、jboolean 等),這些數(shù)據(jù)類型與 Java 數(shù)據(jù)類型相對應,可以直接傳遞給 Java 代碼,避免了數(shù)據(jù)類型轉(zhuǎn)換的開銷。
  • 使用緩存:如果有一些數(shù)據(jù)在 JNI 函數(shù)中需要重復使用,可以考慮使用緩存,避免重復計算,比如 GetObjectClass、GetMethodID,這些可以保存起來重復使用。
  • 避免頻繁切換線程:JNI 函數(shù)會涉及到 Java 線程和本地線程之間的切換,這個過程比較耗時。因此,應該盡量避免頻繁切換線程。

避免 Native 側(cè)代碼對整體性能造成得侵入,如 NDK 下 std::vector 分配大數(shù)據(jù)造成得性能低下,如 RN0.63 版本以前存在這個問題:Make JSStringToSTLString 23x faster (733532e5e9 by @radex)這需要對不同得編譯環(huán)境差異性有所了解。

使用 NDK 編譯匯編代碼

/YourPath/Android/sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ --target=armv7-none-linux-androideabi21 --gcc-toolchain=/YourPath/Android/sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64 --sysroot=/YourPath/Android/sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -S native-lib.cpp

線程安全

  • 當一個線程調(diào)用 Java 方法時,JNI 系統(tǒng)將自動為該線程創(chuàng)建一個 JNIEnv。因此,在訪問 Java 對象之前,需要手動將當前線程與 JVM 綁定,以便獲取 JNIEnv 指針,這個過程就叫做 "Attach"??梢允褂?AttachCurrentThread 方法將當前線程附加到 JVM 上,然后就可以使用 JNIEnv 指針來訪問 Java 對象了。
    在 JNI 中,一般建議每個線程在使用完 JNIEnv 之后,立即 Detach,以釋放資源,避免內(nèi)存泄漏
  • Native 層線程安全需要針對自己得業(yè)務去區(qū)分是否需要加鎖

數(shù)據(jù)優(yōu)化結(jié)果

根據(jù)數(shù)據(jù)分析,性比之前減少了 50%的耗時

總結(jié)

上面概括性介紹了 JSC 和 JNI 的相關知識及經(jīng)驗總結(jié),由于篇幅有限一些問題沒有說明白或理解有誤,歡迎一起交流~~

參考

https://webkit.org/blog

https://developer.apple.com/documentation/javascriptcore

以上就是Android本地搜索業(yè)務優(yōu)化方案的詳細內(nèi)容,更多關于Android本地搜索優(yōu)化的資料請關注腳本之家其它相關文章!

相關文章

最新評論