Android

关注公众号 jb51net

关闭
首页 > 软件编程 > Android > Android本地搜索优化

Android本地搜索业务优化方案

作者:云音乐技术团队

这篇文章主要为大家介绍了Android本地搜索业务优化方案详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

在本文中,我们将通过 Android 本地搜索业务介绍如何使用 JavaScriptCore(以下简称 JSC)和Java Native Interface(以下简称 JNI)相关技术来实现搜索效率提升。

背景

本地搜索业务内部使用动态下发 JS 代码实现一些业务逻辑,用户触发搜索到最终展示数据耗时久,体验很差 ( 8000 首歌曲的处理量大概在 7 秒左右),分析:

DB 和数据处理不做讨论,这里主要解决 JS 引擎的数据传输问题

基于现有方案的分析:

可以发现 Native 在和 JVM 传输次数过多,且跨语言的数据传输序列化耗时

方案

结合现有业务特点:

得出如下流程图

如何实现?

前置知识

方案实现需要了解 JavaScriptCore 和 JNI 的相关知识,下面分别介绍

JavaScriptCore 简介

JavaScriptCore 是一个开源的 JavaScript 引擎,可以用来解析和执行 JavaScript 代码,类似的还有 V8、Hermes 等。

JSAPI 是 JavaScriptCore 的 C++接口,它提供了一组 C++类和函数,可以用于将 JavaScript 嵌入到 C++程序中。JSAPI 提供了以下功能:

JavaScriptCore 类型

在 JSAPI 中,JavaScript 对象和值通过 JSC::JSObject 和 JSC::JSValue 类进行表示。
JSC::JSObject 表示一个 JavaScript 对象,它可以包含一组属性和方法;
JSC::JSValue 表示一个 JavaScript 值,它可以是一个对象、一个数值、一个字符串或一个布尔值等。

JSAPI 提供了 JSC::JSGlobalObject 类作为 JavaScript 对象的全局对象,所有的 JavaScript 对象都是从该全局对象继承而来。

API 介绍

JSContextGroupCreate

JSContextGroupRef 是一个包含多个 JSContext 的分组,它们可以共享内存池和垃圾回收器,从而提高 JavaScript 执行效率和减少内存占用。

JSGlobalContextCreateInGroup

JSGlobalContextCreateInGroup 函数会创建一个 JSGlobalContextRef 类型的对象,表示一个 JavaScript 上下文对象,该对象包含一个虚拟机对象、内存池、全局对象等成员变量。该函数返回值为创建的 JSGlobalContextRef 类型的对象,表示 JavaScript 上下文对象。
由于不同的 JSGlobalContextRef 对象拥有不同的全局对象,因此它们之间不会相互影响。在不同的 JSGlobalContextRef 对象中创建的 JavaScript 对象、函数、变量等,都是相互独立的,它们之间不会共享数据或状态。

JSEvaluateScript

用于执行一段 JavaScript 代码。其内部工作机制主要包括以下几个步骤:

JSEvaluateScript 是一个同步函数,即在执行完 JavaScript 代码之前,它会一直等待,直到 JavaScript 代码执行完毕并返回结果。这意味着,在执行长时间运行的 JavaScript 代码时,JSEvaluateScript 函数可能会阻塞程序的运行。

我们可以通过线程来对 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) {
    // 异步执行
    std::thread([ctx, script, thisObject, exception, completionHandler]() {
        // 执行脚本
        JSStringRef scriptStr = JSStringCreateWithUTF8CString(script);
        JSValueRef result = JSEvaluateScript(ctx, scriptStr, thisObject, nullptr, 0, exception);
        JSStringRelease(scriptStr);
        // 回调 completionHandler
        completionHandler(result, exception);
    }).detach();
}

此外还应关注注册到 JS 环境中的 C 接口回调,这里因尽快返回,如果有耗时任务,则需要将结果通过异步去通知 JS 层,否则会阻塞 JS 线程(也就是调用该函数的线程)。

关键代码示例

下面实现了一个向 global 中添加 getData 的 Native 函数

// 回调函数
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));
        ... // 省略参数、类型等判断
        executor->xxx(); // C++业务侧
        return xxx; // 返回到JS内
}
bool JSCExecutor::initJSC() {
        // 初始化 JSC 引擎
        context_group_ = JSContextGroupCreate();
        JSClassDefinition global_class_definition = kJSClassDefinitionEmpty;
        global_class_ = JSClassCreate(&global_class_definition);
        // 在js执行上下文环境(Group)中创建一个全局的js执行上下文
        context_ = JSGlobalContextCreateInGroup(context_group_, global_class_);
        if (!context_) {
            LOGE(TAG, "create js context error!");
            return false;
        }
        // 获取js执行上下文的全局对象
        global_ = JSContextGetGlobalObject(context_);
        if (!global_) {
            LOGE(TAG, "get js context error!");
            return false;
        }
        // 绑定c++对象地址
        JSObjectSetPrivate(global_, this);
        // 注册函数
        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 程序访问和调用本地方法和资源,也可以使本地代码访问和调用 Java 对象和方法。
此方案需要使用 JNI 进行双向调用。

C 调用 Java

步骤:

JavaC

步骤:

public class TestJNI {
   static {
      System.loadLibrary("xxx.so"); // 加载动态链接库
   }
   // 声明本地方法
   private native void PrintHelloWorld();
   // 静态方法
   public static native String GetVersion();
}
// C实现函数
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { ... } // so初始化回调函数
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserved) { ... } // so卸载回调函数
// 实现
包名_PrintHelloWorld(JNIEnv *env, jobject thiz) { ... }
包名_GetVersion(JNIEnv *env, jclass clazz) { ... }

关注点

JNI 的编写会遇到有很多坑,比如 Java 封装对象和 C++对象的生命周期关系、异步调用逻辑、编译器报错不完善、类型不匹配、JVM 环境不一致、运行线程不一致等等,下面是一些常用的规则

内存

性能

避免 Native 侧代码对整体性能造成得侵入,如 NDK 下 std::vector 分配大数据造成得性能低下,如 RN0.63 版本以前存在这个问题:Make JSStringToSTLString 23x faster (733532e5e9 by @radex)这需要对不同得编译环境差异性有所了解。

使用 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

线程安全

数据优化结果

根据数据分析,性比之前减少了 50%的耗时

总结

上面概括性介绍了 JSC 和 JNI 的相关知识及经验总结,由于篇幅有限一些问题没有说明白或理解有误,欢迎一起交流~~

参考

https://webkit.org/blog

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

以上就是Android本地搜索业务优化方案的详细内容,更多关于Android本地搜索优化的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文