欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

引言

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

背景

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

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

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

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

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

方案

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

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

得出如下流程圖

如何實(shí)現(xiàn)?

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

前置知識(shí)

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

JavaScriptCore 簡(jiǎn)介

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

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

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

JavaScriptCore 類型

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

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

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

API 介紹

JSContextGroupCreate

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

JSGlobalContextCreateInGroup

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

JSEvaluateScript

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

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

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

我們可以通過(guò)線程來(lái)對(duì) 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();
}

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

關(guān)鍵代碼示例

下面實(shí)現(xiàn)了一個(gè)向 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è)務(wù)側(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)建一個(gè)全局的js執(zhí)行上下文
        context_ = JSGlobalContextCreateInGroup(context_group_, global_class_);
        if (!context_) {
            LOGE(TAG, "create js context error!");
            return false;
        }
        // 獲取js執(zhí)行上下文的全局對(duì)象
        global_ = JSContextGetGlobalObject(context_);
        if (!global_) {
            LOGE(TAG, "get js context error!");
            return false;
        }
        // 綁定c++對(duì)象地址
        JSObjectSetPrivate(global_, this);
        // 注冊(cè)函數(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)代碼交互的技術(shù)。JNI 提供了一組 API,可以使 Java 程序訪問(wèn)和調(diào)用本地方法和資源,也可以使本地代碼訪問(wèn)和調(diào)用 Java 對(duì)象和方法。
此方案需要使用 JNI 進(jìn)行雙向調(diào)用。

C 調(diào)用 Java

步驟:

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

JavaC

步驟:

  • 設(shè)計(jì)規(guī)劃功能、接口
  • Java 聲明 Native 方法
  • 按照 JNI 標(biāo)準(zhǔn)實(shí)現(xiàn)方法,并通過(guò) System.loadLibrary()加載
public class TestJNI {
   static {
      System.loadLibrary("xxx.so"); // 加載動(dòng)態(tài)鏈接庫(kù)
   }
   // 聲明本地方法
   private native void PrintHelloWorld();
   // 靜態(tài)方法
   public static native String GetVersion();
}
// C實(shí)現(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ù)
// 實(shí)現(xiàn)
包名_PrintHelloWorld(JNIEnv *env, jobject thiz) { ... }
包名_GetVersion(JNIEnv *env, jclass clazz) { ... }

關(guān)注點(diǎn)

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

內(nèi)存

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

性能

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

避免 Native 側(cè)代碼對(duì)整體性能造成得侵入,如 NDK 下 std::vector 分配大數(shù)據(jù)造成得性能低下,如 RN0.63 版本以前存在這個(gè)問(wèn)題:Make JSStringToSTLString 23x faster (733532e5e9 by @radex)這需要對(duì)不同得編譯環(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

線程安全

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

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

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

總結(jié)

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

參考

https://webkit.org/blog

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

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

相關(guān)文章

最新評(píng)論