组织介绍

OHOS Utils

OHOS Utils 是一个为 OpenHarmony 操作系统(OHOS)提供实用功能的 C++ 库。该项目主要聚焦于简化 Native API 的使用,提供日志记录、异步处理、NAPI 封装等功能,适用于构建高性能的 OHOS 应用程序和组件。

功能特性

  • NAPI处理:提供 NapiHandler,用于更方便地处理 JavaScript 与 Native 代码之间的交互。
  • 异步任务处理:通过 NapiAsyncHandler 类实现异步任务的创建、执行和结果返回,简化异步编程模型。
  • NAPI安全封装:提供 NapiSafeWrapper封装智能指针,更方便的在c库和ArkTs不同对象之间流转。
  • 任务处理:提供了NapiContextNapiTaskHandler 可以处理不同线程、不同运行时环境下的任务。
  • 资源管理:提供了NapiScope,帮助开发者管理 Native 资源,避免内存泄漏。
  • 日志系统:封装了日志记录模块 OHOS_LOG,支持不同级别的日志输出,便于调试和运行时监控。

主要组件

  • NapiAsyncHandler:用于创建和管理异步任务,支持绑定 JavaScript 回调函数。
  • NapiHandler:提供对 NAPI 接口的封装,简化参数解析和返回值处理。
  • NapiSafeWrapper:用于安全封装 Native 对象,可以很好在c库和ArkTS对象之间流转,而不用担心内存泄漏和野指针问题。
  • NapiTaskHandler: 可以用于多线程、同一运行时不同上下文、不同运行时等任务处理,合理切换上下文,安全运行于不同运行时之间。
  • OHOS_LOG:日志系统,支持调试、信息、警告、错误等日志级别。

使用场景

  • 需要执行耗时操作(如网络请求、文件读写)并返回结果给 JavaScript 的场景。
  • 需要在 Native 层封装复杂逻辑,并通过 NAPI 暴露给 JavaScript 调用。
  • 需要记录运行时日志以进行调试或性能分析的场景。

安装与配置

  1. 依赖项:确保已安装 OpenHarmony SDK 和相关开发工具。
  2. 集成方式:将本项目作为子模块引入您的 OHOS 项目中,或直接复制源代码到您的项目结构中。
  3. 编译配置:根据您的项目需求配置 CMakeLists.txt 文件,确保正确链接本库的源文件。

在CMakeLists.txt中增加以下代码

add_definitions(-DOHOS_LOG_TAG=\"mediacodec\")
add_subdirectory(${RELATE_TO_OHOSUTILS_DIR}/ohos_utils ohos_utils)
target_link_libraries(mediacodec PUBLIC ohos_utils)

示例代码

  1. 模块定义
export class MediaCodec {
  constructor(mime: string);
  bind(extractor: MediaExtractor, callback: (status: number, codecInfo: CodeInfoAttr) => void): void;
  configure(colorFormat?: number, surface?: SurfaceTexture): void;
  start(): Promise<void>;
  stop(): Promise<void>;
}

// EvaUtils
export const initRender: (controllerId: number, surfaceId: string, isNeedYuv: boolean, isNormalMp4: boolean, isVideoRecord?: boolean) => Promise<number>;
export const setBgBitmap: (controllerId: number, pixelMap: image.PixelMap | null) => void;

对应的c++代码如下:

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    NapiModuleHandler::AddModule("MediaCodec", new MediaCodecNapi(env, exports)); // 类定义 
    NapiModuleHandler::AddModule("EvaUtils", new EvaUtilsNapi(env, exports));  //  包含多个函数定义
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "mediacodec",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterNapiModule(void)
{
    napi_module_register(&demoModule);
}

MediaCodecNapi、EvaUtilsNapi都要继承NapiModuleHandler, 并实现ExportStub函数。具体参考example里的例子

2)一般napi接口处理

type BaseCallback = ()=>void;
export const changeGroupMute:(callback: BaseCallback, operationID: string, groupId: string, isMute: boolean)=>void;

NAPI如下:


static napi_value ChangeGroupMute(napi_env env, napi_callback_info info)
{
    NapiHandler napiHandler(env, info, PARAM_COUNT_4);
    // 解析参数列表, 分别用INDEX_0, INDEX_1, INDEX_2表示第一、二、三个参数,其他以此类推,目前最大支持INDEX_8
    
    // 结构体类型解析
    BaseCallback cb = napiHandler.ParseArgAs<BaseCallback>(INDEX_0, [&](const napi_value &obj) -> BaseCallback {
        return ParseBaseCallback(env, obj);
    });
    // 一般类型解析
    std::string operationID = napiHandler.ParseArgAs<std::string>(INDEX_1);
    std::string groupID = napiHandler.ParseArgAs<std::string>(INDEX_2);
    int32_t isMute = napiHandler.ParseArgAs<int32_t>(INDEX_3);

    // 调用c接口
    // 返回结果
    return napiHandler.GetNapiValue<std::string>();
}

3) 异步函数处理(Promise)

export const start: ()=> Promise<void>;

start的NAPI如下:

static napi_value start(napi_env env, napi_callback_info info)
{
    NapiHandler napiHandler(env, info, PARAM_COUNT_0);
    std::shared_ptr<MediaCodec> mediaCodec = napiHandler.UnbindSafeObject<MediaCodec>();
    if (mediaCodec == nullptr) {
        LOGE("Unbind MediaCodec nullptr");
        return nullptr;
    }

    return napiHandler.PromiseCall("start", [mediaCodec](napi_env env, void *d) -> napi_status {
        mediaCodec->Start();
        return napi_ok;
    });
}
  1. 类定义
export class MediaCodec {
  constructor(mime: string);
  start(): Promise<void>;
  stop(): void;
}

对应的NAPI实现如下:

// 析构函数
static void JsCodecDestructor(napi_env env, void *nativeObject, void *finalizeHint)
{
    LOGD("destructor MediaCodec");
    NapiHandler napiHandler(env);
    napiHandler.DeleteSafeObject<MediaCodec>(nativeObject);
}

// 构造函数
static napi_value JsCodecConstructor(napi_env env, napi_callback_info info)
{
    NapiHandler napiHandler(env, info, PARAM_COUNT_0);

    LOGD("constructor MediaCodec");
    std::shared_ptr<MediaCodec> codec = std::make_shared<MediaCodec>();
    return napiHandler.BindSafeObject<MediaCodec>(codec, mediacodec::JsCodecDestructor);
}

// start方法, Promise异步调用
static napi_value start(napi_env env, napi_callback_info info)
{
    NapiHandler napiHandler(env, info, PARAM_COUNT_0);
    std::shared_ptr<MediaCodec> mediaCodec = napiHandler.UnbindSafeObject<MediaCodec>();
    if (mediaCodec == nullptr) {
        LOGE("Unbind Extractor nullptr");
        return nullptr;
    }

    return napiHandler.PromiseCall("start", [mediaCodec](napi_env env, void *d) -> napi_status {
        mediaCodec->Start();
        return napi_ok;
    });
}

// stop 
static napi_value stop(napi_env env, napi_callback_info info)
{
    NapiHandler napiHandler(env, info, PARAM_COUNT_0);
    std::shared_ptr<MediaCodec> mediaCodec = napiHandler.UnbindSafeObject<MediaCodec>();

    if (mediaCodec == nullptr) {
        LOGE("Unbind Extractor nullptr");
        return nullptr;
    }
    mediaCodec->Stop();
    return napiHandler.GetVoidValue();
}

void MediaCodecNapi::ExportStub()
{
    std::vector<napi_property_descriptor> desc = {
        DECLARE_NAPI_FUNCTION("start", start),
        DECLARE_NAPI_FUNCTION("stop", stop),
    };
    ExportClass("MediaCodec", JsCodecConstructor, desc);
}

解决方案

如何保证被wrap的对象按期望顺序析构

问题:在使用napi_wrap把两个 C++ 对象包装成两个 JavaScript 对象的场景中,由于这两个 C++ 对象存在依赖关系,要求其中一个C++对象必须在另一个C++对象之前析构。然而,JavaScript 垃圾回收(GC)的时机不确定,直接在napi_wrap的finalize_cb回调里销毁 C++ 对象,没办法保证析构顺序符合要求。该如何保证两个C++对象析构的前后顺序?

我们的解决办法是将一个shared_ptr的智能指针包裹在NapiSafeWrapper内, napi_wrap包裹的是NapiSafeWrapper的指针,每次napi_unwrap 后从NapiSafeWrapper 取出shared_ptr的指针,这样不需要考虑谁先后释放问题。

NapiHandler 提供如下两个方法解决此问题。

// class NapiHandler
 template <class T>
    napi_value BindSafeObject(const std::shared_ptr<T> &object, napi_finalize destructor)
    {
        NapiSafeWrapper<T> *wrapper = new NapiSafeWrapper<T>(object);
        napi_ref ref = nullptr;
        NAPI_CALL(
            env_,
            napi_wrap(
                env_, thisArg_, static_cast<void *>(wrapper), destructor, nullptr, nullptr));
        wrapper->SetJsWrapper(env_, ref);
        return thisArg_;
    }

template <class T>
    std::shared_ptr<T> UnbindSafeObject()
    {
        NapiSafeWrapper<T> *t = nullptr;
        NAPI_CALL_HANDLE(env_, napi_unwrap(env_, thisArg_, reinterpret_cast<void **>(&t)), nullptr);
        if (t == nullptr) {
            return nullptr;
        }
        return t->GetSafeObject();
    }

以下是使用的例子。在构造函数里, 直接将智能指针绑定到NapiSafeWrapper, 而在调用时候,从NapiSafeWrapper取出来。

static napi_value JsCodecConstructor(napi_env env, napi_callback_info info)
{
    NapiHandler napiHandler(env, info, PARAM_COUNT_1);
    std::string mine = napiHandler.ParseArgAs<std::string>(INDEX_0);
    
    LOGD("constructor MediaCodec");
    std::shared_ptr<MediaCodec> codec = std::make_shared<MediaCodec>(mine);
    return napiHandler.BindSafeObject<MediaCodec>(codec, [](napi_env env, void *nativeObject, void *finalizeHint) {
        LOGD("destructor MediaCodec");
        NapiHandler napiHandler(env);
        napiHandler.DeleteSafeObject<MediaCodec>(nativeObject);
    });
    
}

static napi_value bind(napi_env env, napi_callback_info info)
{
    NapiHandler napiHandler(env, info, PARAM_COUNT_2);
    // Note: 具体类型可能处理办法不同
    std::shared_ptr<MediaExtractor> extractor = napiHandler.ParseArgAs<std::shared_ptr<MediaExtractor>>(INDEX_0, [&](const napi_env env, const napi_value &obj) -> std::shared_ptr<MediaExtractor> {
        NapiSafeWrapper<MediaExtractor> *t = nullptr;
        napi_unwrap(env, obj, reinterpret_cast<void **>(&t));
        if (t == nullptr) {
            return nullptr;
        }
        return t->GetSafeObject();
    });
    // Note: Unknown ArkTs type: (status: number, codecInfo: CodeInfoAttr) => void
    // TODO: auto callback = napiHandler.ParseArgAs<Unknown ArkTs type>(INDEX_1)
    
    std::shared_ptr<MediaCodec> mediaCodec = napiHandler.UnbindSafeObject<MediaCodec>();
    if (mediaCodec == nullptr) {
        LOGE("Unbind MediaCodec nullptr");
        return nullptr;
    }
    
    // TODO: void result = bind(extractor, callback);
    return napiHandler.GetVoidValue();
}

贡献指南

欢迎贡献代码和改进文档。请遵循以下步骤提交 PR:

  1. Fork 本仓库。
  2. 创建新分支 (git checkout -b feature/new-feature)。
  3. 提交更改 (git commit -am 'Add new feature')。
  4. 推送分支 (git push origin feature/new-feature)。
  5. 创建 Pull Request。

许可证

本项目采用 Apache-2.0 许可证。详情请查看 LICENSE 文件。

成就
4
Star
74
Fork
成员(1)
2226718 meto475 1766992680
城meto

搜索帮助