【车载audio】【audio hal 01】【Android 音频子系统:Audio HAL Server 启动全流程深度解析】

Android 音频子系统:Audio HAL Server 启动全流程深度解析

1. 背景与架构概述

在 Android Treble 架构中,音频系统的核心逻辑被拆分为 Framework 层(AudioFlinger)Vendor 层(Audio HAL)android.hardware.audio.service 进程是 Vendor 层的守护进程,它的主要职责是:

  1. 对接硬件 :加载厂商实现的音频驱动库(.so)。
  2. 提供接口:将硬件能力通过 HIDL 或 AIDL 接口暴露给系统服务。
  3. 常驻运行 :作为独立进程,处理来自 audioserver 的 Binder 请求。

本文将深入分析该进程从编译、启动到最终与 AudioFlinger 握手的全过程。


2. 全链路启动流程图

AudioFlinger (Framework) hwservicemanager Audio HAL Service (vendor 分区) init 进程 AudioFlinger (Framework) hwservicemanager Audio HAL Service (vendor 分区) init 进程 ------ 阶段一:Framework 订阅监听 ------ ------ 阶段二:HAL 进程启动与初始化 ------ ------ 阶段三:动态发现与服务注册 ------ ------ 阶段四:跨进程握手 ------ 注册通知监听 (IDevicesFactory) 1 解析 init.rc 启动进程 2 main(): 初始化 Binder/AIDL 线程池 3 main(): 配置 hwbinder 共享内存大小 4 寻找版本匹配的 -impl.so 5 dlopen / dlsym (执行 HIDL_FETCH) 6 创建 DevicesFactory 对象 7 registerAsService("default") 8 发送通知:IDevicesFactory 已就绪 9 getService("default") 10 返回 Binder 代理 (Bp) 11 建立连接并监听死亡通知 12


3. 进程构建与配置

3.1 编译蓝图:cc_binary

Audio HAL Server 是一个标准的 C++ 二进制文件,通过 Android.bp 定义。

makefile 复制代码
cc_binary {
    name: "android.hardware.audio.service",

    init_rc: ["android.hardware.audio.service.rc"],
    relative_install_path: "hw",
    vendor: true,

    srcs: ["service.cpp"],

    cflags: [
        "-Wall",
        "-Wextra",
        "-Werror",
    ],

    shared_libs: [
        "libcutils",
        "libbinder",
        "libbinder_ndk",
        "libhidlbase",
        "liblog",
        "libutils",
        "libhardware",
    ],

    defaults: [
        "android_hardware_audio_config_defaults",
    ],
}

3.2 启动配置:init.rc

系统启动时由 init 进程拉起。注意其运行权限为 audioserver,并赋予了 SYS_NICE 权限以确保音频流的实时性。

c 复制代码
service vendor.audio-hal /vendor/bin/hw/android.hardware.audio.service
    class hal
    user audioserver
    # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
    group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub
    capabilities BLOCK_SUSPEND SYS_NICE
    # setting RLIMIT_RTPRIO allows binder RT priority inheritance
    rlimit rtprio 10 10
    ioprio rt 4
    task_profiles ProcessCapacityHigh HighPerformance
    onrestart restart audioserver

4. 源码深度解析:从 main 开始

4.1 进程入口:service.cpp

main 函数负责搭建整个 Binder 通信环境,并采用"从新到旧"的版本回退策略注册接口。

c 复制代码
/**
 * Audio HAL Service 进程入口函数
 *
 * 该 main 函数负责:
 * 1. 初始化 vendor 侧 Binder(vndbinder / hwbinder / AIDL Binder)运行环境
 * 2. 配置 Binder / HIDL RPC 线程池及共享内存参数
 * 3. 注册 Audio 相关的 HIDL HAL 接口(必选 / 可选)
 * 4. 通过 dlopen 方式加载并注册部分外部 HAL 实现(如 Bluetooth Audio)
 * 5. 进入 Binder 线程池循环,作为常驻 Audio HAL 服务运行
 *
 * 该进程通常运行在 vendor 分区,
 * 是 system_server 与底层音频硬件之间的核心服务节点。
 */
int main(int /* argc */, char* /* argv */ []) {

    /**
     * 忽略 SIGPIPE 信号。
     *
     * 在 Binder 或 socket 通信中:
     * - 如果对端进程异常退出
     * - 当前进程向已关闭的 fd 写数据
     * 默认行为会触发 SIGPIPE 并导致进程被系统杀死。
     *
     * Audio HAL 是常驻服务进程,必须具备健壮性,
     * 因此忽略 SIGPIPE,由代码自行处理错误返回。
     */
    signal(SIGPIPE, SIG_IGN);

    /**
     * 初始化 android::ProcessState,并指定使用 /dev/vndbinder。
     *
     * 说明:
     * - vndbinder 是 system binder 在 vendor 侧的镜像
     * - 用于 system_server ↔ vendor HAL 的跨分区 Binder 通信
     *
     * Audio HAL 运行在 vendor 进程中,
     * 必须显式使用 vndbinder,而不能使用 /dev/binder。
     */
    ::android::ProcessState::initWithDriver("/dev/vndbinder");

    /**
     * 启动 vndbinder 的 Binder 线程池。
     *
     * 该线程池用于:
     * - 处理来自 system_server 的 HIDL / Binder 请求
     * - 接收 Audio HAL 接口调用
     *
     * 如果不启动线程池,Binder 调用将无法被处理。
     */
    ::android::ProcessState::self()->startThreadPool();

    /**
     * 设置 AIDL(libbinder_ndk)线程池的最大线程数。
     *
     * Audio HAL 中的 AIDL 接口通常对并发要求不高,
     * 限制线程数可以:
     * - 减少竞态条件
     * - 降低锁复杂度
     * - 避免资源被过度并发访问
     */
    ABinderProcess_setThreadPoolMaxThreadCount(1);

    /**
     * 启动 AIDL Binder 线程池。
     *
     * 使基于 AIDL 的 HAL 接口可以开始接收客户端请求。
     */
    ABinderProcess_startThreadPool();

    /**
     * 定义系统属性默认值。
     *
     * 用于判断 hwbinder mmap 大小是否被厂商显式配置。
     */
    const int32_t defaultValue = -1;

    /**
     * 从系统属性中读取 hwbinder mmap 大小配置(单位:KB)。
     *
     * hwbinder mmap 区域用于:
     * - HIDL Binder 之间的大块数据传输
     * - FMQ(Fast Message Queue)
     * - 较大的参数结构体
     *
     * 在音频场景中,该大小可能影响:
     * - 音频参数下发
     * - offload / effect 配置
     */
    int32_t value =
        property_get_int32("persist.vendor.audio.service.hwbinder.size_kbyte", defaultValue);

    /**
     * 如果系统属性存在(即不等于默认值),
     * 则使用该值重新配置 hwbinder 的 mmap 大小。
     */
    if (value != defaultValue) {
        /**
         * 打印日志,记录当前使用的 mmap 配置。
         */
        ALOGD("Configuring hwbinder with mmap size %d KBytes", value);

        /**
         * 将 KB 转换为 Byte,并初始化 hwbinder 的共享内存大小。
         *
         * 注意:
         * - 该配置必须在 Binder 实际使用前完成
         * - 否则配置将不会生效
         */
        ProcessState::initWithMmapSize(static_cast<size_t>(value) * 1024);
    }

    /**
     * 配置 HIDL RPC 线程池。
     *
     * 参数说明:
     * - 16 :HIDL RPC 最大线程数
     * - callerWillJoin = true:
     *     表示 main 线程本身也会进入 RPC 线程池
     *
     * 这是 HIDL HAL 能够被 system_server 调用的必要条件。
     */
    configureRpcThreadpool(16, true /*callerWillJoin*/);

    // Automatic formatting tries to compact the lines, making them less readable
    // clang-format off

    /**
     * 定义必须注册的 Audio HAL 接口族列表。
     *
     * 每个 InterfacesList 的结构为:
     * - 第一个元素:接口族名称(仅用于日志)
     * - 后续元素  :按"版本从新到旧"排列的 HIDL 接口全名
     *
     * 只要其中任意一个版本注册成功,
     * 即认为该接口族在当前设备上可用。
     */
    const std::vector<InterfacesList> mandatoryInterfaces = {
        {
            "Audio Core API",
            "android.hardware.audio@7.1::IDevicesFactory",
            "android.hardware.audio@7.0::IDevicesFactory",
            "android.hardware.audio@6.0::IDevicesFactory", /*HidlServiceManagement: Registered android.hardware.audio@6.0::IDevicesFactory/default  当时注册成功的是*/
            "android.hardware.audio@5.0::IDevicesFactory",
            "android.hardware.audio@4.0::IDevicesFactory",
        },
        {
            "Audio Effect API",
            "android.hardware.audio.effect@7.0::IEffectsFactory",
            "android.hardware.audio.effect@6.0::IEffectsFactory",
            "android.hardware.audio.effect@5.0::IEffectsFactory",
            "android.hardware.audio.effect@4.0::IEffectsFactory",
        }
    };

    /**
     * 定义可选注册的 Audio 相关 HAL 接口族。
     *
     * 这些接口通常依赖特定硬件能力:
     * - SoundTrigger(语音唤醒)
     * - Bluetooth Audio
     *
     * 注册失败不会影响 Audio 核心功能,
     * 因此仅打印警告日志。
     */
    const std::vector<InterfacesList> optionalInterfaces = {
        {
            "Soundtrigger API",
            "android.hardware.soundtrigger@2.3::ISoundTriggerHw",
            "android.hardware.soundtrigger@2.2::ISoundTriggerHw",
            "android.hardware.soundtrigger@2.1::ISoundTriggerHw",
            "android.hardware.soundtrigger@2.0::ISoundTriggerHw",
        },
        {
            "Bluetooth Audio API",
            "android.hardware.bluetooth.audio@2.2::IBluetoothAudioProvidersFactory",
            "android.hardware.bluetooth.audio@2.1::IBluetoothAudioProvidersFactory",
            "android.hardware.bluetooth.audio@2.0::IBluetoothAudioProvidersFactory",
        },
        {
            /**
             * 旧版 Bluetooth A2DP Offload HIDL 接口。
             *
             * 该接口在 Bluetooth Audio HAL V2
             * 完全支持 offload 后可被移除。
             */
            "Bluetooth Audio Offload API",
            "android.hardware.bluetooth.a2dp@1.0::IBluetoothAudioOffload"
        }
    };

    /**
     * 定义通过共享库方式注册的可选 HAL 实现。
     *
     * 与 passthrough HAL 不同:
     * - HAL 实现不与当前进程静态链接
     * - 通过 dlopen 在运行期动态加载
     *
     * 常见于 Bluetooth Audio 等模块。
     */
    const std::vector<std::pair<std::string,std::string>> optionalInterfaceSharedLibs = {
        {
            "android.hardware.bluetooth.audio-impl",
            "createIBluetoothAudioProviderFactory",
        },
        {
            "android.hardware.bluetooth.adapter1.audio-impl",
            "createIBluetoothAudioProviderFactory",
        },
    };
    // clang-format on

    /**
     * 注册所有必须存在的 Audio HAL 接口族。
     *
     * 若整个接口族注册失败,
     * 表示系统无法提供核心 Audio 能力,
     * 因此直接触发 fatal,终止进程。
     */
    for (const auto& listIter : mandatoryInterfaces) {
        auto iter = listIter.begin();
        const std::string& interfaceFamilyName = *iter++;

        LOG_ALWAYS_FATAL_IF(
            !registerPassthroughServiceImplementations(iter, listIter.end()),
            "Could not register %s", interfaceFamilyName.c_str());
    }

    /**
     * 注册所有可选的 Audio HAL 接口族。
     *
     * 注册失败不会中断进程,
     * 仅用于提示当前设备不支持对应能力。
     */
    for (const auto& listIter : optionalInterfaces) {
        auto iter = listIter.begin();
        const std::string& interfaceFamilyName = *iter++;

        ALOGW_IF(
            !registerPassthroughServiceImplementations(iter, listIter.end()),
            "Could not register %s", interfaceFamilyName.c_str());
    }

    /**
     * 通过 dlopen 方式注册外部 HAL 服务实现。
     *
     * 每个共享库内部通常提供一个工厂函数,
     * 用于创建并注册对应的 HAL Binder 服务。
     */
    for (const auto& interfacePair : optionalInterfaceSharedLibs) {
        const std::string& libraryName = interfacePair.first;
        const std::string& interfaceLoaderFuncName = interfacePair.second;

        if (registerExternalServiceImplementation(libraryName, interfaceLoaderFuncName)) {
            ALOGI("%s() from %s success",
                  interfaceLoaderFuncName.c_str(), libraryName.c_str());
        } else {
            ALOGW("%s() from %s failed",
                  interfaceLoaderFuncName.c_str(), libraryName.c_str());
        }
    }

    /**
     * 当前线程加入 HIDL RPC 线程池。
     *
     * 该调用不会返回,
     * main 线程进入 Binder 循环,
     * 进程开始作为 Audio HAL 服务长期运行。
     */
    joinRpcThreadpool();
}

4.2 接口注册核心逻辑:Passthrough 模式

在 Android 中,音频接口常通过 Passthrough 模式加载。这意味着 HAL 的具体实现代码直接加载到当前进程空间。

注册辅助函数
c 复制代码
/**
 * registerPassthroughServiceImplementations
 *
 * 用于注册一组 HIDL passthrough HAL 接口实现。
 *
 * 设计背景:
 * - 同一 HAL 接口通常存在多个版本(如 audio@7.1 / 7.0 / 6.0)
 * - 设备只需支持其中一个版本即可满足 framework 要求
 * - 因此采用"从新到旧逐个尝试"的 fallback 策略
 *
 * 返回值语义:
 * - 只要任意一个接口版本注册成功,即返回 true
 * - 所有版本都失败才返回 false
 */
static bool registerPassthroughServiceImplementations(Iter first, Iter last) {
    for (; first != last; ++first) {

        /**
         * 尝试注册当前接口版本对应的 passthrough HAL 实现。
         *
         * registerPassthroughServiceImplementation 内部会:
         * - 查找该接口的 HAL 实现
         * - 创建服务对象
         * - 将服务注册到 hwservicemanager
         */
        if (registerPassthroughServiceImplementation(*first) == OK) {

            /**
             * 当前接口版本注册成功,
             * 认为整个接口族已经满足要求,立即返回成功。
             */
            return true;
        }
    }

    /**
     * 所有接口版本均注册失败,
     * 表示该接口族在当前设备上不可用。
     */
    return false;
}
外部库动态加载逻辑

对于蓝牙音频等模块,通过 dlopen 灵活加载,避开了静态链接的限制。

c 复制代码
/**
 * registerExternalServiceImplementation
 *
 * 通过 dlopen + 工厂函数的方式,注册一个外部 HAL 服务实现。
 *
 * 设计背景:
 * - 某些 HAL(如 Bluetooth Audio)不适合 passthrough 方式
 * - HAL 实现可能依赖不同厂商库或运行时选择
 * - 因此采用动态加载的方式进行注册
 *
 * 参数说明:
 * - libName  : 共享库名(不包含 .so)
 * - funcName : 库中用于创建并注册 HAL 服务的工厂函数名
 */
static bool registerExternalServiceImplementation(const std::string& libName,
                                                  const std::string& funcName) {
    constexpr int dlMode = RTLD_LAZY;

    // 清除之前可能残留的 dlerror 状态
    dlerror();

    // 拼接出完整的共享库文件名
    auto libPath = libName + ".so";

    /**
     * 动态加载共享库。
     *
     * 若加载失败,说明该 HAL 实现不适用于当前设备,
     * 或者相关模块未编译进 vendor 镜像。
     */
    void* handle = dlopen(libPath.c_str(), dlMode);
    if (handle == nullptr) {
        const char* error = dlerror();
        ALOGE("Failed to dlopen %s: %s", libPath.c_str(),
              error != nullptr ? error : "unknown error");
        return false;
    }

    /**
     * 查找 HAL 工厂函数。
     *
     * 该函数通常负责:
     * - 创建 HAL Binder 服务实例
     * - 将其注册到 Binder / hwservicemanager
     */
    binder_status_t (*factoryFunction)();
    *(void**)(&factoryFunction) = dlsym(handle, funcName.c_str());

    if (!factoryFunction) {
        const char* error = dlerror();
        ALOGE("Factory function %s not found in libName %s: %s",
              funcName.c_str(), libPath.c_str(),
              error != nullptr ? error : "unknown error");

        // 工厂函数不存在,释放共享库
        dlclose(handle);
        return false;
    }

    /**
     * 调用工厂函数完成 HAL 注册。
     *
     * 仅当返回 STATUS_OK 时,
     * 才认为外部 HAL 服务注册成功。
     */
    return ((*factoryFunction)() == STATUS_OK);
}

5. 寻址与实例化:Passthrough 机制的终点

5.1 注册实现 (LegacySupport.cpp)

这是 HAL 实现被推送到 hwservicemanager 的最后一公里。

c 复制代码
/**
 * registerPassthroughServiceImplementation
 *
 * 用于注册一个 HIDL passthrough HAL 服务实例。
 *
 * 背景说明:
 * - Passthrough HAL 的特点是:
 *   - HAL 实现代码直接运行在当前进程中
 *   - 不通过独立的 binderized HAL service 进程
 * - 因此在注册前,必须:
 *   1. 找到本地 HAL 实现(不能是 remote)
 *   2. 校验其接口类型是否符合预期
 *   3. 将该实现注册到 hwservicemanager
 *
 * 该函数是 Audio HAL / Effect HAL 等
 * 在 vendor 进程中完成 HIDL 服务注册的"最后一道关卡"。
 *
 * 参数说明:
 * - interfaceName        : 用于查找 HAL 实现的接口名(可能是某个版本)
 * - expectInterfaceName  : 期望的接口 descriptor(用于安全校验)
 * - registerServiceCb    : 实际执行注册动作的回调函数
 * - serviceName          : HAL 服务名(通常是 "default")
 *
 * 返回值:
 * - OK           : 注册成功
 * - EXIT_FAILURE : 任一步骤失败
 *
 * warn_unused_result:
 * - 强制调用者必须检查返回值
 * - 防止 HAL 注册失败却被忽略
 */
__attribute__((warn_unused_result))
status_t registerPassthroughServiceImplementation(
        const std::string& interfaceName,
        const std::string& expectInterfaceName,
        RegisterServiceCb registerServiceCb,
        const std::string& serviceName) {

    /**
     * 通过 HIDL 内部机制获取 passthrough HAL 实现对象。
     *
     * getRawServiceInternal 的关键行为:
     * - 根据 interfaceName + serviceName 查找 HAL 实现
     * - retry = true:
     *     表示在首次失败时可进行重试(适配 HAL 初始化时序)
     * - getStub = true:
     *     强制获取 passthrough stub(即本地实现)
     *
     * 成功返回:
     * - 一个运行在当前进程内的 HAL 对象(sp<IBase>)
     */
    sp<IBase> service =
            getRawServiceInternal(interfaceName,
                                  serviceName,
                                  true  /* retry */,
                                  true  /* getStub */); //  -----------------重点6 拿到 DevicesFactory 对象

    /**
     * 如果未能获取 HAL 实现,说明:
     * - 该接口版本在当前设备上未实现
     * - 或 HAL 模块未编译 / 未链接进当前进程
     */
    if (service == nullptr) {
        ALOGE("Could not get passthrough implementation for %s/%s.",
              interfaceName.c_str(), serviceName.c_str());
        return EXIT_FAILURE;
    }

    /**
     * 校验该 HAL 实现是否为本地对象。
     *
     * 对于 passthrough HAL:
     * - service->isRemote() 必须返回 false
     *
     * 如果是 remote,说明:
     * - 该接口实际上是 binderized HAL
     * - 或被错误地代理到了其他进程
     *
     * 这与 passthrough HAL 的设计目标相违背。
     */
    if (service->isRemote()) {
        ALOGE("Implementation of %s/%s is remote!",
              interfaceName.c_str(), serviceName.c_str());
        return EXIT_FAILURE;
    }

    /**
     * 用于保存 HAL 实现实际声明的接口 descriptor。
     *
     * 该 descriptor 由 HAL 实现自身返回,
     * 用于防止"接口名与实现类型不匹配"的错误注册。
     */
    std::string actualName;

    /**
     * 通过 HIDL interfaceDescriptor() 获取实现的真实接口名。
     *
     * 示例返回值:
     * - "android.hardware.audio@6.0::IDevicesFactory"
     *
     * 使用 Return<void> 是因为:
     * - HIDL 接口调用本身是一次 Binder 风格调用
     * - 需要显式检查通信是否成功
     */
    Return<void> result = service->interfaceDescriptor(
            [&actualName](const hidl_string& descriptor) {
                actualName = descriptor;
            });

    /**
     * 如果接口 descriptor 获取失败,说明:
     * - HIDL 通信异常
     * - HAL 实现不完整或存在严重错误
     */
    if (!result.isOk()) {
        ALOGE("Error retrieving interface name from %s/%s: %s",
              interfaceName.c_str(),
              serviceName.c_str(),
              result.description().c_str());
        return EXIT_FAILURE;
    }

    /**
     * 校验 HAL 实现的真实接口类型是否符合预期。
     *
     * 该检查用于防止以下问题:
     * - HAL 实现返回了错误的接口类型
     * - 不同接口之间发生误注册
     *
     * 这是 Audio HAL 等核心模块的重要安全校验点。
     */
    if (actualName != expectInterfaceName) {
        ALOGE("Implementation of %s/%s is actually %s, not a %s!",
              interfaceName.c_str(),
              serviceName.c_str(),
              actualName.c_str(),
              expectInterfaceName.c_str());
        return EXIT_FAILURE;
    }

    /**
     * 调用注册回调函数,将 HAL 服务注册到 hwservicemanager。
     *
     * registerServiceCb 内部通常会:
     * - 将 service 转换为具体接口类型
     * - 调用 registerAsService(serviceName)
     */
    status_t status = registerServiceCb(service, serviceName); //  -----------------重点7 最终将 DevicesFactory 服务注册进 hwservicemanager 中

    /**
     * 根据注册结果打印日志。
     */
    if (status == OK) {
        ALOGI("Registration complete for %s/%s.",
              interfaceName.c_str(), serviceName.c_str());
    } else {
        ALOGE("Could not register service %s/%s (%d).",
              interfaceName.c_str(), serviceName.c_str(), status);
    }

    /**
     * 返回最终注册结果。
     */
    return status;
}

5.2 核心函数:getRawServiceInternal

该函数通过扫描 /vendor/lib64/hw 等目录,寻找符合命名的 .so 文件。

c 复制代码
// 核心函数:根据 VINTF / 系统配置 / 运行模式,
// 获取指定 HIDL HAL 的"原始实现对象(IBase)"
//
// 该函数是 HIDL getService 体系的底层实现,
// 同时支持:
// 1) HWBinder HAL(独立进程)
// 2) Passthrough HAL(so 直连)
// 3) Legacy HAL(无 VINTF 声明的历史模式)
// 4) Treble 测试 / Stub 场景
sp<::android::hidl::base::V1_0::IBase> getRawServiceInternal(
        const std::string& descriptor,   // 接口全名,如 android.hardware.audio@6.0::IDevicesFactory
        const std::string& instance,     // 实例名,通常是 "default"
        bool retry,                      // 是否允许在 HAL 未启动时重试等待
        bool getStub) {                  // 是否只获取本地 stub(不走 Binder)

    // Transport 表示 HAL 的部署方式(来自 VINTF)
    // - HWBINDER     : HAL 运行在独立进程,通过 Binder 通信
    // - PASSTHROUGH : HAL 以 so 形式直接 dlopen 到当前进程
    // - EMPTY       : VINTF 未声明(legacy 场景)
    using Transport = IServiceManager1_0::Transport;

    // Waiter 用于在 HWBinder 模式下等待 HAL service 注册完成
    // 解决 "client 启动早于 HAL server" 的时序问题
    sp<Waiter> waiter;

    // hwservicemanager(HIDL Service Manager)
    sp<IServiceManager1_1> sm;

    // 默认认为是 legacy(VINTF 未声明)
    Transport transport = Transport::EMPTY;

    // Recovery 模式是一个特例:
    // - 没有 hwservicemanager
    // - 没有完整 Binder 环境
    // 因此只能使用 passthrough HAL
    if (kIsRecovery) {
        transport = Transport::PASSTHROUGH;
    } else {
        // 获取 hwservicemanager
        sm = defaultServiceManager1_1();
		-------------- system/libhidl/transport/ServiceManagement.cpp
		+	// 获取 HIDL ServiceManager(V1.2 版本)的全局单例
		+	//
		+	// 这是 HIDL 世界中"所有 HAL 注册 / 查询"的根节点:
		+	//   - HAL Server 通过它 register()
		+	//   - HAL Client 通过它 get()
		+	//
		+	// 对应的远端服务是:
		+	//   /vendor/bin/hw/android.hidl.manager@1.2-service
		+	//
		+	// 本函数负责:
		+	// 1. 延迟初始化(lazy init)
		+	// 2. 线程安全的单例创建
		+	// 3. 确保 hwservicemanager 已经启动
		+	// 4. 建立当前进程到 hwservicemanager 的 Binder 通道
		+	sp<IServiceManager1_2> defaultServiceManager1_2() {
		+	    using android::hidl::manager::V1_2::BnHwServiceManager;
		+	    using android::hidl::manager::V1_2::BpHwServiceManager;
		+	
		+	    // 全局 mutex:保护 ServiceManager 单例的创建过程
		+	    // 使用 new + 引用,避免静态析构顺序问题(AOSP 常见写法)
		+	    static std::mutex& gDefaultServiceManagerLock = *new std::mutex;
		+	
		+	    // 全局缓存的 IServiceManager 单例
		+	    // 一旦成功获取,后续所有调用都会直接返回
		+	    static sp<IServiceManager1_2>& gDefaultServiceManager =
		+	            *new sp<IServiceManager1_2>;
		+	
		+	    {
		+	        // 保证多线程环境下只初始化一次
		+	        std::lock_guard<std::mutex> _l(gDefaultServiceManagerLock);
		+	
		+	        // 如果已经初始化过,直接返回
		+	        if (gDefaultServiceManager != nullptr) {
		+	            return gDefaultServiceManager;
		+	        }
		+	
		+	        // 检查 hwbinder 设备节点是否存在且可访问
		+	        //
		+	        // 含义:
		+	        // - /dev/hwbinder 不存在 → 设备不支持 HIDL/HwBinder
		+	        // - 或当前进程无权限 → vendor/system 隔离或 SELinux 问题
		+	        //
		+	        // 在这些情况下,HIDL ServiceManager 不可用
		+	        if (access("/dev/hwbinder", F_OK | R_OK | W_OK) != 0) {
		+	            // HwBinder not available on this device or not accessible to
		+	            // this process.
		+	            return nullptr;
		+	        }
		+	
		+	        // 阻塞等待 hwservicemanager 启动完成
		+	        //
		+	        // hwservicemanager 是 HIDL 架构的"中枢服务",
		+	        // 必须先于所有 HAL client / server 启动。
		+	        //
		+	        // 如果它还没 ready,这里会一直等
		+	        waitForHwServiceManager();
		+	
		+	        // 尝试从 Binder context 中获取 hwservicemanager 的 Binder 对象
		+	        //
		+	        // ProcessState::self()->getContextObject(nullptr)
		+	        //   → 获取 Binder Context Manager(类似 system_server 在 system binder 中的角色)
		+	        //
		+	        // fromBinder:
		+	        //   - 把一个通用的 IBinder
		+	        //   - 转换为强类型的 IServiceManager1_2 接口
		+	        //
		+	        // BpHwServiceManager : proxy(client 侧)
		+	        // BnHwServiceManager : stub(server 侧)
		+	        while (gDefaultServiceManager == nullptr) {
		+	            gDefaultServiceManager =
		+	                fromBinder<IServiceManager1_2,
		+	                           BpHwServiceManager,
		+	                           BnHwServiceManager>(
		+	                    ProcessState::self()->getContextObject(nullptr)); // 通过这里拿到了 ServiceManager 的代理 BpHwServiceManager
		+	
		+	            // 理论上不常发生,但在系统启动早期可能出现:
		+	            // - Binder 驱动 ready
		+	            // - hwservicemanager 进程尚未完全注册
		+	            if (gDefaultServiceManager == nullptr) {
		+	                LOG(ERROR) << "Waited for hwservicemanager, but got nullptr.";
		+	                sleep(1);
		+	            }
		+	        }
		+	    }
		+	
		+	    // 返回全局唯一的 hwservicemanager 接口
		+	    return gDefaultServiceManager;
		+	}
		-------------->		
		

        if (sm == nullptr) {
            // 正常系统中不应该发生,属于致命环境错误
            ALOGE("getService: defaultServiceManager() is null");
            return nullptr;
        }

        // 查询 VINTF manifest,确认该 HAL 的 Transport 类型
        // 这是 Treble 架构下的"最终裁判"
        Return<Transport> transportRet = sm->getTransport(descriptor, instance);

        if (!transportRet.isOk()) {
            // 无法从 hwservicemanager 获取 transport,直接失败
            ALOGE("getService: defaultServiceManager()->getTransport returns %s",
                  transportRet.description().c_str());
            return nullptr;
        }
        transport = transportRet;
    }

    // 根据 transport 派生出几种判断标志
    const bool vintfHwbinder = (transport == Transport::HWBINDER);     // 正规 HWBinder HAL
    const bool vintfPassthru = (transport == Transport::PASSTHROUGH); // VINTF 声明为 passthrough

    // Treble 测试模式(CTS / VTS)
    // 在该模式下,系统会放宽某些 VINTF 限制
    const bool trebleTestingOverride = isTrebleTestingOverride();

    // 是否允许 legacy HAL(未写入 VINTF 的 HAL)
    // 典型场景:
    // - 老设备
    // - 非 enforce VINTF 的系统
    // - Treble 测试 + userdebug
    const bool allowLegacy =
            !kEnforceVintfManifest || (trebleTestingOverride && isDebuggable());

    // VINTF 未声明,但系统允许 legacy,则进入 legacy 路径
    const bool vintfLegacy = (transport == Transport::EMPTY) && allowLegacy;

    // 如果没有强制启用 VINTF 校验,打印强烈警告
    // 原因:
    // - HAL 若未写入 VINTF
    // - 且启动较慢
    // 则 client 可能永远无法获取到 HAL(竞态条件)
    if (!kEnforceVintfManifest) {
        ALOGE("getService: Potential race detected. The VINTF manifest is not being enforced. If "
              "a HAL server has a delay in starting and it is not in the manifest, it will not be "
              "retrieved. Please make sure all HALs on this device are in the VINTF manifest and "
              "enable PRODUCT_ENFORCE_VINTF_MANIFEST on this device (this is also enabled by "
              "PRODUCT_FULL_TREBLE). PRODUCT_ENFORCE_VINTF_MANIFEST will ensure that no race "
              "condition is possible here.");
        // 粗暴但有效的兜底,给 HAL 一点启动时间
        sleep(1);
    }

    // =========================
    // HWBinder / Legacy 获取路径
    // =========================
    //
    // 条件说明:
    // - !getStub           : 调用方期望获取真正的 HAL(非 stub)
    // - vintfHwbinder      : 正规 HWBinder HAL
    // - vintfLegacy        : legacy HAL(通过 hwservicemanager 暴露)
    for (int tries = 0; !getStub && (vintfHwbinder || vintfLegacy); tries++) {

        // 第二次及以后尝试时,创建 Waiter
        // 用于等待 HAL service 注册完成
        if (waiter == nullptr && tries > 0) {
            waiter = new Waiter(descriptor, instance, sm);
        }

        // 每一轮尝试前重置 waiter 状态
        if (waiter != nullptr) {
            waiter->reset();  // 顺序不能乱,reset 内部有同步假设
        }

        // 从 hwservicemanager 获取 HAL
        Return<sp<IBase>> ret = sm->get(descriptor, instance); // -----------------重点3 拿到 DevicesFactory 对象
		--------- system/libhidl/transport/ServiceManagement.cpp
		+	Return<sp<IBase>> get(const hidl_string& fqName,
		+	                      const hidl_string& name) override {
		+	    // 最终返回的 HAL 实例(IBase 是所有 HIDL 接口的根)
		+	    sp<IBase> ret = nullptr;
		+	
		+	    /**
		+	     * openLibs 的职责:
		+	     * 1. 根据 fqName(例如 android.hardware.audio@6.0::IDevicesFactory)
		+	     * 2. 找到所有"可能匹配"的 HAL so
		+	     * 3. 逐个 dlopen
		+	     * 4. 对每个 so,回调下面这个 lambda
		+	     *
		+	     * ⚠️ 这是一个"遍历 + 尝试"的模型,不是只试一次
		+	     */
		+	    openLibs(fqName, [&](void* handle,
		+	                         const std::string& lib,
		+	                         const std::string& sym) {
		+	
		+	        /**
		+	         * 每个 Passthrough HAL so 必须导出一个"工厂函数"
		+	         * 典型名字类似:
		+	         *   HIDL_FETCH_IDevicesFactory
		+	         *
		+	         * 这个函数的签名是:
		+	         *   IBase* factory(const char* instanceName)
		+	         */
		+	        IBase* (*generator)(const char* name);
		+	
		+	        // 用 dlsym 从 so 里找这个 factory 符号
		+	        *(void **)(&generator) = dlsym(handle, sym.c_str()); 
		+	
		+	        if (!generator) {
		+	            // so 打开成功,但符号不存在
		+	            // 说明这个 so 并不是我们要的 HAL 实现
		+	            const char* error = dlerror();
		+	            LOG(ERROR)
		+	                << "Passthrough lookup opened " << lib
		+	                << " but could not find symbol " << sym
		+	                << ": " << (error == nullptr ? "unknown error" : error)
		+	                << ". Keeping library open.";
		+	
		+	            /**
		+	             * 为什么不 dlclose?
		+	             *
		+	             * - Passthrough HAL 可能被多个线程同时访问
		+	             * - dlclose + 并发调用 极其容易崩
		+	             * - Android 这里选择"宁愿泄露,也不炸进程"
		+	             */
		+	            return true;  // 继续尝试下一个 so
		+	        }
		+	
		+	        /**
		+	         * 调用 factory 函数
		+	         * name 是 instance name,比如:
		+	         *   "default"
		+	         *   "primary"
		+	         */
		+	        ret = (*generator)(name.c_str()); // 这里会直接调用 HIDL_FETCH_IDevicesFactory 函数 ,这里创建了 DevicesFactory 对象;  -----------------重点1 DevicesFactory 对象创建
		+	
		+	        if (ret == nullptr) {
		+	            // 这个 HAL so 存在,但不支持这个 instance
		+	            LOG(ERROR)
		+	                << "Could not find instance '" << name.c_str()
		+	                << "' in library " << lib
		+	                << ". Keeping library open.";
		+	
		+	            // 同样不 dlclose,理由同上
		+	            return true;  // 继续找其他 so
		+	        }
		+	
		+	        /**
		+	         * ⚠️ 关键设计点:
		+	         *
		+	         * fqName 是"期望的接口名"
		+	         * 但 ret 可能是:
		+	         *   - 子类
		+	         *   - vendor 扩展实现
		+	         *
		+	         * 所以这里要:
		+	         *   1. 通过反射拿到"真实 descriptor"
		+	         *   2. 用真实 fqName 做引用注册
		+	         */
		+	        using ::android::hardware::details::getDescriptor;
		+	        std::string actualFqName = getDescriptor(ret.get());
		+	
		+	        CHECK(actualFqName.size() > 0);
		+	
		+	        /**
		+	         * registerReference 的作用:
		+	         * - 建立 "fqName + instance" → 实例 的映射
		+	         * - 防止重复创建
		+	         * - 便于调试 / 生命周期管理
		+	         */
		+	        registerReference(actualFqName, name);
		+	
		+	        // false 表示:已经找到合适实现,停止遍历
		+	        return false;
		+	    });
		+	----------
		+	+	static void openLibs(
		+	+	        const std::string& fqName,
		+	+	        const std::function<bool /* continue */ (
		+	+	            void* /* handle */,
		+	+	            const std::string& /* lib */,
		+	+	            const std::string& /* sym */)>& eachLib) {
		+	+	
		+	+	    /**
		+	+	     * fqName 形如:
		+	+	     *   android.hardware.audio@7.0::IDevicesFactory
		+	+	     *
		+	+	     * 这是 HIDL 的"完全限定接口名":
		+	+	     *   package + version + interface
		+	+	     *
		+	+	     * Passthrough 模式下:
		+	+	     *   - 不通过 hwservicemanager
		+	+	     *   - 只能靠 fqName 来"反推出" HAL so 的名字
		+	+	     */
		+	+	
		+	+	    // 找到 "::",分离 package/version 与 interface 名
		+	+	    size_t idx = fqName.find("::");
		+	+	
		+	+	    // 如果 fqName 格式非法,直接失败
		+	+	    if (idx == std::string::npos ||
		+	+	            idx + strlen("::") + 1 >= fqName.size()) {
		+	+	        LOG(ERROR) << "Invalid interface name passthrough lookup: " << fqName;
		+	+	        return;
		+	+	    }
		+	+	
		+	+	    /**
		+	+	     * packageAndVersion:
		+	+	     *   android.hardware.audio@6.0
		+	+	     *
		+	+	     * ifaceName:
		+	+	     *   IDevicesFactory
		+	+	     */
		+	+	    std::string packageAndVersion = fqName.substr(0, idx);
		+	+	    std::string ifaceName = fqName.substr(idx + strlen("::"));
		+	+	
		+	+	    /**
		+	+	     * Passthrough HAL 的 so 命名约定:
		+	+	     *
		+	+	     *   <package>@<version>-impl*.so
		+	+	     *
		+	+	     * 举例:
		+	+	     *   android.hardware.audio@6.0-impl.so
		+	+	     *   android.hardware.audio@6.0-impl-primary.so
		+	+	     *
		+	+	     * prefix 用于后面文件扫描
		+	+	     */
		+	+	    const std::string prefix = packageAndVersion + "-impl";
		+	+	
		+	+	    /**
		+	+	     * Passthrough HAL 必须导出的 factory 符号名:
		+	+	     *
		+	+	     *   HIDL_FETCH_<InterfaceName>
		+	+	     *
		+	+	     * 举例:
		+	+	     *   HIDL_FETCH_IDevicesFactory
		+	+	     *
		+	+	     * 这是 HIDL 在编译期"强制约定"的 ABI
		+	+	     */
		+	+	    const std::string sym = "HIDL_FETCH_" + ifaceName;
		+	+	
		+	+	    constexpr int dlMode = RTLD_LAZY;
		+	+	    void* handle = nullptr;
		+	+	
		+	+	    // 清空 dlerror,防止读到旧错误
		+	+	    dlerror();
		+	+	
		+	+	    /**
		+	+	     * HAL 搜索路径说明(非常重要):
		+	+	     *
		+	+	     * Passthrough HAL 是 vendor 提供的 so,
		+	+	     * 所以只能从「允许 vendor 访问的路径」加载
		+	+	     *
		+	+	     * 查找顺序体现了 Android 的分区优先级策略
		+	+	     */
		+	+	    static std::string halLibPathVndkSp = details::getVndkSpHwPath();
		+	+	
		+	+	    std::vector<std::string> paths = {
		+	+	        HAL_LIBRARY_PATH_ODM,      // /odm/lib(64)/hw
		+	+	        HAL_LIBRARY_PATH_VENDOR,   // /vendor/lib(64)/hw
		+	+	        halLibPathVndkSp,           // /system/lib(64)/vndk-sp/hw
		+	+	#ifndef __ANDROID_VNDK__
		+	+	        HAL_LIBRARY_PATH_SYSTEM,   // /system/lib(64)/hw(非 VNDK 构建)
		+	+	#endif
		+	+	    };
		+	+	
		+	+	    /**
		+	+	     * Treble Testing Override 场景:
		+	+	     *
		+	+	     * - VTS / CTS 测试
		+	+	     * - HAL 可能被"静态链接"进测试进程
		+	+	     * - 不存在真实的 so 文件
		+	+	     *
		+	+	     * 因此:
		+	+	     *   dlopen(nullptr) → 表示"当前进程本身"
		+	+	     */
		+	+	    if (details::isTrebleTestingOverride()) {
		+	+	        handle = dlopen(nullptr, dlMode);
		+	+	        if (handle == nullptr) {
		+	+	            const char* error = dlerror();
		+	+	            LOG(ERROR) << "Failed to dlopen self: "
		+	+	                       << (error == nullptr ? "unknown error" : error);
		+	+	        } else if (!eachLib(handle, "SELF", sym)) {
		+	+	            // 如果回调返回 false,表示已经找到目标 HAL
		+	+	            return;
		+	+	        }
		+	+	    }
		+	+	
		+	+	    /**
		+	+	     * 正常路径:
		+	+	     * 依次扫描每一个 HAL 目录
		+	+	     */
		+	+	    for (const std::string& path : paths) {
		+	+	
		+	+	        /**
		+	+	         * findFiles:
		+	+	         *   在指定目录下查找:
		+	+	         *     prefix + "*.so"
		+	+	         *
		+	+	         * 举例:
		+	+	         *   android.hardware.audio@7.0-impl*.so
		+	+	         */
		+	+	        std::vector<std::string> libs = findFiles(path, prefix, ".so");
		+	+	
		+	+	        for (const std::string &lib : libs) {
		+	+	            const std::string fullPath = path + lib;
		+	+	
		+	+	            /**
		+	+	             * recovery 或 system 分区:
		+	+	             *   使用普通 dlopen
		+	+	             *
		+	+	             * vendor / odm:
		+	+	             *   必须使用 android_load_sphal_library
		+	+	             *
		+	+	             * 原因:
		+	+	             *   - linker namespace 隔离
		+	+	             *   - 防止 vendor HAL 直接依赖 system 私有符号
		+	+	             */
		+	+	            if (kIsRecovery || path == HAL_LIBRARY_PATH_SYSTEM) {
		+	+	                handle = dlopen(fullPath.c_str(), dlMode);
		+	+	            } else {
		+	+	#if !defined(__ANDROID_RECOVERY__) && defined(__ANDROID__)
		+	+	                handle = android_load_sphal_library(fullPath.c_str(), dlMode);
		+	+	#endif
		+	+	            }
		+	+	
		+	+	            if (handle == nullptr) {
		+	+	                const char* error = dlerror();
		+	+	                LOG(ERROR) << "Failed to dlopen " << lib << ": "
		+	+	                           << (error == nullptr ? "unknown error" : error);
		+	+	                continue;
		+	+	            }
		+	+	
		+	+	            /**
		+	+	             * eachLib 回调的职责:
		+	+	             *   - dlsym(HIDL_FETCH_xxx)
		+	+	             *   - 调 factory(name)
		+	+	             *   - 判断是否是目标 instance
		+	+	             *
		+	+	             * 返回值含义:
		+	+	             *   true  → 继续找下一个 so
		+	+	             *   false → 已经找到,立即停止遍历
		+	+	             */
		+	+	            if (!eachLib(handle, lib, sym)) {
		+	+	                return;
		+	+	            }
		+	+	        }
		+	    }
		+	}
		+	---------->
		+	
		+	    // 如果找到了就返回实例,找不到就是 nullptr
		+	    return ret; //  -----------------重点2 返回 DevicesFactory 对象
		+	}
		--------->
        if (!ret.isOk()) {
            ALOGE("getService: defaultServiceManager()->get returns %s for %s/%s.",
                  ret.description().c_str(), descriptor.c_str(), instance.c_str());
            break;
        }

        sp<IBase> base = ret; // -----------------重点4 拿到 DevicesFactory 对象
        if (base != nullptr) {
            // 校验返回的 Binder 对象是否真的实现了期望的接口
            // 防止版本不匹配 / descriptor 错误
            Return<bool> canCastRet =
                details::canCastInterface(base.get(), descriptor.c_str(),
                                          true /* emitError */);

            // 接口匹配,成功获取 HAL
            if (canCastRet.isOk() && canCastRet) {
                if (waiter != nullptr) {
                    waiter->done();
                }
                // 返回 raw IBase(上层还会 wrap 成 BpXXX)
                return base; // -----------------重点5 拿到 DevicesFactory 对象
            }

            // 接口不匹配或发生严重错误,是否继续尝试由错误类型决定
            if (!handleCastError(canCastRet, descriptor, instance)) break;
        }

        // legacy HAL 或调用方不允许 retry,则不再等待
        if (vintfLegacy || !retry) break;

        // 等待 HAL server 注册(有超时)
        if (waiter != nullptr) {
            ALOGI("getService: Trying again for %s/%s...",
                  descriptor.c_str(), instance.c_str());
            waiter->wait(true /* timeout */);
        }
    }

    // 清理 waiter 状态
    if (waiter != nullptr) {
        waiter->done();
    }

    // =========================
    // Passthrough / Stub 路径
    // =========================
    //
    // 以下情况会进入:
    // - getStub == true          : 明确要求本地 stub
    // - vintfPassthru == true    : VINTF 声明为 passthrough
    // - vintfLegacy == true      : legacy HAL
    if (getStub || vintfPassthru || vintfLegacy) {

        // Passthrough Service Manager
        // 负责 dlopen HAL so 并创建实例
        const sp<IServiceManager1_0> pm = getPassthroughServiceManager();
        if (pm != nullptr) {
            sp<IBase> base = pm->get(descriptor, instance).withDefault(nullptr);

            // wrapPassthrough:
            // 将本地 C++ 对象包装成 Binder 风格接口,
            // 使上层代码无需关心 transport 类型
            if (!getStub || trebleTestingOverride) {
                base = wrapPassthrough(base);
            }
            return base;
        }
    }

    // 所有路径失败,返回 nullptr
    return nullptr;
}

6. 对象的创建:HIDL_FETCH_xxx 工厂

在加载 .so 后,系统通过约定好的符号名调用工厂函数。

c 复制代码
IDevicesFactory* HIDL_FETCH_IDevicesFactory(const char* name) {
    return strcmp(name, "default") == 0 ? new DevicesFactory() : nullptr;
}

6.1 继承关系

DevicesFactory 实现类必须严格继承自生成的 HIDL 接口类。

c 复制代码
class DevicesFactory : public IDevicesFactory 
struct IDevicesFactory : public ::android::hidl::base::V1_0::IBase

7. 服务发现:AudioFlinger 如何连接 HAL

AudioFlinger 启动时,并不会由于 HAL 启动较慢而挂起。它采用了一种"注册-监听"的异步模式。

7.1 订阅通知

c 复制代码
void DevicesFactoryHalHidl::onFirstRef() {
    // 获取 hwservicemanager 的 Binder 代理
    sp<IServiceManager> sm = IServiceManager::getService();
    ALOG_ASSERT(sm != nullptr, "Hardware service manager is not running");
    
    // 创建一个监听器对象
    sp<ServiceNotificationListener> listener = new ServiceNotificationListener(this);
    // 向 hwservicemanager 注册监听
    Return<bool> result = sm->registerForNotifications(
            IDevicesFactory::descriptor, "", listener);
    if (result.isOk()) {
        ALOGE_IF(!static_cast<bool>(result),
                "Hardware service manager refused to register listener");
    } else {
        ALOGE("Failed to register for hardware service manager notifications: %s",
                result.description().c_str());
    }
}

7.2 异步获取服务

hwservicemanager 通知 HAL 已上线,AudioFlinger 才会真正调用 getService()

c 复制代码
class ServiceNotificationListener : public IServiceNotification {
  public:
    explicit ServiceNotificationListener(sp<DevicesFactoryHalHidl> factory)
            : mFactory(factory) {}

	// 当 DevicesFactory 注册这里就会拿到通知 
    Return<void> onRegistration(const hidl_string& /*fully_qualified_name*/,
            const hidl_string& instance_name,
            bool /*pre_existing*/) override {
        if (static_cast<std::string>(instance_name) == "default") return Void();
        sp<DevicesFactoryHalHidl> factory = mFactory.promote();
        if (!factory) return Void();
        sp<IDevicesFactory> halFactory = IDevicesFactory::getService(instance_name); // 这里 获得了 DevicesFactory 的 Bp 代理
        if (halFactory) {
            factory->addDeviceFactory(halFactory, true /*needToNotify*/);
        }
        return Void();
    }

  private:
    wp<DevicesFactoryHalHidl> mFactory;
};

8. 总结

Android Audio HAL 的启动是一套严密的流程:

  1. 进程就绪init 开启沙盒进程。
  2. 环境搭建main 配置多种 Binder 池。
  3. 动态扫描 :利用 dlopen 寻找厂商实现的 .so
  4. 服务挂名 :在 hwservicemanager 注册。
  5. 异步握手AudioFlinger 通过监听机制最终实现跨进程调用。

这种设计保证了系统服务与厂商驱动的强解耦,即便硬件驱动初始化缓慢,也不会导致上层核心服务崩溃。

相关推荐
似霰1 小时前
Android 日志系统6——logd 读日志过程分析
android·log
技术摆渡人1 小时前
Android CPU调度优化完整剖析指南
android
雪球Snowball1 小时前
【Android关键流程】Window相关类及属性
android
我命由我123451 小时前
Android多进程开发 - AIDL 最简单的实现、传递数据大小限制
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
冬奇Lab9 小时前
Android系统启动流程深度解析:从Bootloader到Zygote的完整旅程
android·源码阅读
泓博10 小时前
Android中仿照View selector自定义Compose Button
android·vue.js·elementui
zhangphil11 小时前
Android性能分析中trace上到的postAndWait
android
十里-12 小时前
vue2的web项目打包成安卓apk包
android·前端
p***199412 小时前
MySQL——内置函数
android·数据库·mysql