Android车载——VehicleHal初始化(Android 11)

1 概述

VehicleHal是AOSP中车辆服务相关的hal层服务。它主要定义了与汽车硬件交互的标准化接口和属性管理,是一个独立的进程。

2 进程启动

VehicleHal相关代码在源码树中的hardware/interfaces/automotive目录下

首先看下Android.bp文件:

cpp 复制代码
cc_binary {
    name: "android.hardware.automotive.vehicle@2.0-service",
    defaults: ["vhal_v2_0_target_defaults"],
    vintf_fragments: [
        "android.hardware.automotive.vehicle@2.0-service.xml",
    ],
    init_rc: ["android.hardware.automotive.vehicle@2.0-service.rc"],
    vendor: true,
    relative_install_path: "hw",
    srcs: ["VehicleService.cpp"],
    shared_libs: [
        "libbase",
        "libjsoncpp",
        "libprotobuf-cpp-lite",
    ],
    static_libs: [
        "android.hardware.automotive.vehicle@2.0-manager-lib",
        "android.hardware.automotive.vehicle@2.0-default-impl-lib",
        "android.hardware.automotive.vehicle@2.0-libproto-native",
        "libqemu_pipe",
    ],
}

标准的hal服务层定义,入口在VehicleService.cpp,其他依赖文件在static_libs中定义。服务的可执行文件编译完成之后的名称是android.hardware.automotive.vehicle@2.0-service。

进程是hal服务进程,由init通过解析rc文件进行拉起

cpp 复制代码
service vendor.vehicle-hal-2.0 /vendor/bin/hw/android.hardware.automotive.vehicle@2.0-service
    class hal
    user vehicle_network
    group system inet

进程名vendor.vehicle-hal-2.0,执行的就是/vendor/bin/hw/android.hardware.automotive.vehicle@2.0-service这个可执行文件,class为hal,用户是vehicle_network,用户组是system和inet。

在init中class_start hal的时候启动该hal进程。

3 VHAL初始化

VHAL进程的入口在VehicleService.cpp中的main函数
hardware/interfaces/automotive/vehicle/2.0/default/VehicleService.cpp

cpp 复制代码
// xy:VHAL的入口函数,由init进程启动
int main(int /* argc */, char* /* argv */ []) {
    // xy:缓存属性值的地方
    auto store = std::make_unique<VehiclePropertyStore>();
    // xy:模拟与真实车辆的连接
    auto connector = std::make_unique<impl::EmulatedVehicleConnector>();
    // xy:模拟Hal,Hal的具体实现
    auto hal = std::make_unique<impl::EmulatedVehicleHal>(store.get(), connector.get());
    // xy:汽车模拟类,模拟车辆信号
    auto emulator = std::make_unique<impl::VehicleEmulator>(hal.get());
    // xy:VHAL的服务实现入口
    auto service = std::make_unique<VehicleHalManager>(hal.get());
    // xy:设置存储属性值的池子,便于重复使用
    connector->setValuePool(hal->getValuePool());

    // xy:设置binder线程数量
    configureRpcThreadpool(4, false /* callerWillJoin */);

    ALOGI("Registering as service...");
    // xy:将当前服务注册到HwServiceManager中
    status_t status = service->registerAsService();

    if (status != OK) {
        ALOGE("Unable to register vehicle service (%d)", status);
        return 1;
    }

    // Setup a binder thread pool to be a car watchdog client.
    // xy:watchDog设置
    ABinderProcess_setThreadPoolMaxThreadCount(1);
    ABinderProcess_startThreadPool();
    sp<Looper> looper(Looper::prepare(0 /* opts */));
    std::shared_ptr<WatchdogClient> watchdogClient =
            ndk::SharedRefBase::make<WatchdogClient>(looper, service.get());
    // The current health check is done in the main thread, so it falls short of capturing the real
    // situation. Checking through HAL binder thread should be considered.
    if (!watchdogClient->initialize()) {
        ALOGE("Failed to initialize car watchdog client");
        return 1;
    }
    ALOGI("Ready");
    while (true) {
        looper->pollAll(-1 /* timeoutMillis */);
    }

    return 1;
}

接下来逐步解析各个模块的初始化

3.1 VehiclePropertyStore初始化

VehiclePropertyStore类的主要职责是缓存车辆数据,采用默认构造函数,构造函数中没有初始化逻辑。

cpp 复制代码
using PropertyMap = std::map<RecordId, VehiclePropValue>;
std::unordered_map<int32_t /* VehicleProperty */, RecordConfig> mConfigs;
PropertyMap mPropertyValues;  // Sorted map of RecordId : VehiclePropValue.

主要初始化了这两个数据对象,其中mConfigs用于存储属性配置,mPropertyValues用于存储属性值。

3.2 EmulatedVehicleConnector初始化

也是采用无参构造,初始化了一个对象

cpp 复制代码
EmulatedUserHal mEmulatedUserHal;

3.3 EmulatedVehicleHal初始化

cpp 复制代码
EmulatedVehicleHal(VehiclePropertyStore* propStore, VehicleHalClient* client,
                       EmulatedUserHal* emulatedUserHal = nullptr);

这个类只有一个三个参数的构造函数,第三个参数有默认值,其实现如下:

cpp 复制代码
EmulatedVehicleHal::EmulatedVehicleHal(VehiclePropertyStore* propStore, VehicleHalClient* client,
                                       EmulatedUserHal* emulatedUserHal)
    : mPropStore(propStore),
      mHvacPowerProps(std::begin(kHvacPowerProperties), std::end(kHvacPowerProperties)),
      mRecurrentTimer(std::bind(&EmulatedVehicleHal::onContinuousPropertyTimer, this,
                                std::placeholders::_1)),
      mVehicleClient(client),
      mEmulatedUserHal(emulatedUserHal) {
    initStaticConfig();
    for (size_t i = 0; i < arraysize(kVehicleProperties); i++) {
        mPropStore->registerProperty(kVehicleProperties[i].config);
    }
    mVehicleClient->registerPropertyValueCallback(std::bind(&EmulatedVehicleHal::onPropertyValue,
                                                            this, std::placeholders::_1,
                                                            std::placeholders::_2));
}

这个构造函数初始化的时候传入的两个参数是在main函数中创建的VehiclePropertyStore对象和EmulatedVehicleConnector对象,而这个构造函数的第二个参数却是VehicleHalClient,这是怎么回事呢?

cpp 复制代码
class EmulatedVehicleConnector : public IPassThroughConnector<VehicleHalClient, VehicleHalServer>

template <typename VehicleClientType, typename VehicleServerType>
class IPassThroughConnector : public VehicleClientType, public VehicleServerType

从上面可以看出,EmulatedVehicleConnector继承自IPassThroughConnector,而IPassThroughConnector定义了两个模板,IPassThroughConnector继承这两个模板类。所以EmulatedVehicleConnector继承VehicleHalClient和VehicleHalServer。所以EmulatedVehicleConnector是VehicleHalClient的子类。

接着分析EmulatedVehicleHal的构造函数,这里用传入的VehiclePropertyStore对象初始化mPropStore。

cpp 复制代码
std::unordered_set<int32_t> mHvacPowerProps;

const int32_t kHvacPowerProperties[] = {
    toInt(VehicleProperty::HVAC_FAN_SPEED),
    toInt(VehicleProperty::HVAC_FAN_DIRECTION),
};

mHvacPowerProps(std::begin(kHvacPowerProperties), std::end(kHvacPowerProperties))

然后初始化这个成员变量,将数组中的两个空调相关的property的propId添加到mHvacPowerProps这个vector中。

cpp 复制代码
RecurrentTimer mRecurrentTimer;

mRecurrentTimer(std::bind(&EmulatedVehicleHal::onContinuousPropertyTimer, this,
                                std::placeholders::_1)),

RecurrentTimer(const Action& action) : mAction(action) {
        mTimerThread = std::thread(&RecurrentTimer::loop, this, action);
    }

这个是一个执行定时任务相关的类,初始化成员变量mRecurrentTimer为一个RecurrentTimer对象,这个对象在初始化的时候会创建一个线程,这个线程中会定时执行传入的函数。具体的分析见3.7小结。

cpp 复制代码
mVehicleClient(client)

然后初始化mVehicleClient为main函数中创建的EmulatedVehicleConnector对象。

cpp 复制代码
mEmulatedUserHal(emulatedUserHal)

这个使用默认参数,空指针。

cpp 复制代码
void EmulatedVehicleHal::initStaticConfig() {
    for (auto&& it = std::begin(kVehicleProperties); it != std::end(kVehicleProperties); ++it) {
        const auto& cfg = it->config;
        VehiclePropertyStore::TokenFunction tokenFunction = nullptr;

        switch (cfg.prop) {
            case OBD2_FREEZE_FRAME: {
                tokenFunction = [](const VehiclePropValue& propValue) {
                    return propValue.timestamp;
                };
                break;
            }
            default:
                break;
        }

        mPropStore->registerProperty(cfg, tokenFunction);
    }
}

然后初始化属性配置,kVehicleProperties是一个定义了车辆属性配置的结构体数组,以下是其中的一个元素,表示车辆的空调温度设置的属性配置:

cpp 复制代码
{.config = {.prop = toInt(VehicleProperty::HVAC_TEMPERATURE_SET),
            .access = VehiclePropertyAccess::READ_WRITE,
            .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
            .areaConfigs = {VehicleAreaConfig{
                                    .areaId = HVAC_LEFT,
                                    .minFloatValue = 16,
                                    .maxFloatValue = 32,
                            },
                            VehicleAreaConfig{
                                    .areaId = HVAC_RIGHT,
                                    .minFloatValue = 16,
                                    .maxFloatValue = 32,
                            }}},
 .initialAreaValues = {{HVAC_LEFT, {.floatValues = {16}}},
                       {HVAC_RIGHT, {.floatValues = {20}}}}},

OBD2_FREEZE_FRAME表示冻结帧,跟诊断相关,暂时不清楚,暂不看这块的处理。然后会将所有的属性配置注册到VehiclePropertyStore中。

这些属性配置就是vhal中支持的属性,如果没有在这个结构体数组中定义,则该功能不支持,供应商提供的新的需要在这个结构体中新增。

后面的for循环和initStaticConfig中的逻辑一样,跳过,这块应该没什么意义的。

cpp 复制代码
mVehicleClient->registerPropertyValueCallback(std::bind(&EmulatedVehicleHal::onPropertyValue,
                                                            this, std::placeholders::_1,
                                                            std::placeholders::_2));

最后注册callback函数到EmulatedVehicleConnector对象中,回调函数是EmulatedVehicleHal::onPropertyValue。

至此,就初始化完成了,主要做的就是创建EmulatedVehicleHal对象,并且注册一些回调函数,然后比较重要的一点是加载了所有的属性配置到VehiclePropertyStore中。

3.4 VehicleEmulator初始化

cpp 复制代码
VehicleEmulator::VehicleEmulator(EmulatedVehicleHalIface* hal) : mHal{hal} {
    mHal->registerEmulator(this);

    ALOGI("Starting SocketComm");
    mSocketComm = std::make_unique<SocketComm>(this);
    mSocketComm->start();

    if (android::base::GetBoolProperty("ro.kernel.qemu", false)) {
        ALOGI("Starting PipeComm");
        mPipeComm = std::make_unique<PipeComm>(this);
        mPipeComm->start();
    }
}

持有EmulatedVehicleHal对象,创建SocketComm或者PipeComm,并启动,这是模拟的与VHAL连接的客户端的通信类。

3.5 VehicleHalManager初始化

cpp 复制代码
VehicleHalManager(VehicleHal* vehicleHal)
        : mHal(vehicleHal),
          mSubscriptionManager(std::bind(&VehicleHalManager::onAllClientsUnsubscribed,
                                         this, std::placeholders::_1)) {
        init();
    }

先将EmulatedVehicleHal保存至mHal变量中,然后会初始化一个订阅相关的类SubscriptionManager,最后调用init函数。

SubscriptionManager初始化见3.6小节,接下来分析init函数

cpp 复制代码
hidl_vec<VehiclePropValue> mHidlVecOfVehiclePropValuePool;
------------------------------------------------------------------------------------------------------------------------
void VehicleHalManager::init() {
    ALOGI("VehicleHalManager::init");

	//初始化mHidlVecOfVehiclePropValuePool为20,用于存储VehiclePropValue
    mHidlVecOfVehiclePropValuePool.resize(kMaxHidlVecOfVehiclPropValuePoolSize);

	//批处理相关的初始化
    mBatchingConsumer.run(&mEventQueue,
                          kHalEventBatchingTimeWindow,
                          std::bind(&VehicleHalManager::onBatchHalEvent,
                                    this, _1));
	//事件处理相关初始化
    mHal->init(&mValueObjectPool,
               std::bind(&VehicleHalManager::onHalEvent, this, _1),
               std::bind(&VehicleHalManager::onHalPropertySetError, this,
                         _1, _2, _3));

    // Initialize index with vehicle configurations received from VehicleHal.
    auto supportedPropConfigs = mHal->listProperties();
    mConfigIndex.reset(new VehiclePropConfigIndex(supportedPropConfigs));

    std::vector<int32_t> supportedProperties(
        supportedPropConfigs.size());
    //
    for (const auto& config : supportedPropConfigs) {
        supportedProperties.push_back(config.prop);
    }
}

VehiclePropConfigIndex初始化见3.7小节

3.5.1 批处理初始化

cpp 复制代码
ConcurrentQueue<VehiclePropValuePtr> mEventQueue;
constexpr std::chrono::milliseconds kHalEventBatchingTimeWindow(10);
------------------------------------------------------------------------------------------------------------------------
mBatchingConsumer.run(&mEventQueue,
                      kHalEventBatchingTimeWindow,
                      std::bind(&VehicleHalManager::onBatchHalEvent,
                                this, _1));
------------------------------------------------------------------------------------------------------------------------
void run(ConcurrentQueue<T>* queue,
         std::chrono::nanoseconds batchInterval,
         const OnBatchReceivedFunc& func) {
    mQueue = queue;
    mBatchInterval = batchInterval;

    mWorkerThread = std::thread(
        &BatchingConsumer<T>::runInternal, this, func);
}

先看这部分代码,mQueue=mEventQueue,用于添加事件,是VehiclePropValuePtr类型事件。mBatchInterval=kHalEventBatchingTimeWindow=10,然后创建了一个线程,执行的函数是BatchingConsumer::runInternal,传入的参数是VehicleHalManager::onBatchHalEvent。

cpp 复制代码
void runInternal(const OnBatchReceivedFunc& onBatchReceived) {
        if (mState.exchange(State::RUNNING) == State::INIT) {
            while (State::RUNNING == mState) {
                mQueue->waitForItems();
                if (State::STOP_REQUESTED == mState) break;

                std::this_thread::sleep_for(mBatchInterval);
                if (State::STOP_REQUESTED == mState) break;

                std::vector<T> items = mQueue->flush();

                if (items.size() > 0) {
                    onBatchReceived(items);
                }
            }
        }

        mState = State::STOPPED;
    }

批处理这个类的主要作用就是循环执行mQueue中的事件,如果有事件到来就执行,没有就休眠。mQueue事件什么时候添加后续分析。

3.5.2 初始化现有属性值

cpp 复制代码
VehiclePropValuePool mValueObjectPool;

VehiclePropValuePool(size_t maxRecyclableVectorSize = 4) :
        mMaxRecyclableVectorSize(maxRecyclableVectorSize) {};
----------------------------------------------------------------------------------------------
mHal->init(&mValueObjectPool,
           std::bind(&VehicleHalManager::onHalEvent, this, _1),
           std::bind(&VehicleHalManager::onHalPropertySetError, this,
                     _1, _2, _3));
----------------------------------------------------------------------------------------------
void init(
    VehiclePropValuePool* valueObjectPool,
    const HalEventFunction& onHalEvent,
    const HalErrorFunction& onHalError) {
    mValuePool = valueObjectPool;
    mOnHalEvent = onHalEvent;
    mOnHalPropertySetError = onHalError;

    onCreate();
}

mValuePool存储的是VehiclePropValuePool对象,是用于VehiclePropValue解析的池子,方便循环利用。mOnHalEvent是onHalEvent函数,mOnHalPropertySetError是onHalPropertySetError函数,然后调用onCreate函数。onCreate是一个虚函数,由实际的VehicleHal类实现,即EmulatedVehicleHal中的实现:

cpp 复制代码
// Parse supported properties list and generate vector of property values to hold current values.
void EmulatedVehicleHal::onCreate() {
    static constexpr bool shouldUpdateStatus = true;

	//遍历所有的属性配置
    for (auto& it : kVehicleProperties) {
        VehiclePropConfig cfg = it.config;
        int32_t numAreas = cfg.areaConfigs.size();

        if (isDiagnosticProperty(cfg)) {
            // do not write an initial empty value for the diagnostic properties
            // as we will initialize those separately.
            continue;
        }

        // A global property will have only a single area
        if (isGlobalProp(cfg.prop)) {
            numAreas = 1;
        }

		//对于分区属性的处理
        for (int i = 0; i < numAreas; i++) {
            int32_t curArea;

            if (isGlobalProp(cfg.prop)) {
                curArea = 0;
            } else {
                curArea = cfg.areaConfigs[i].areaId;
            }

            // Create a separate instance for each individual zone
            //初始化VehiclePropValue
            VehiclePropValue prop = {
                    .areaId = curArea,
                    .prop = cfg.prop,
            };
			
			//设置初始属性值
            if (it.initialAreaValues.size() > 0) {
                auto valueForAreaIt = it.initialAreaValues.find(curArea);
                if (valueForAreaIt != it.initialAreaValues.end()) {
                    prop.value = valueForAreaIt->second;
                } else {
                    ALOGW("%s failed to get default value for prop 0x%x area 0x%x",
                            __func__, cfg.prop, curArea);
                }
            } else {
                prop.value = it.initialValue;
            }
            //属性值写入VehiclePropertyStore
            mPropStore->writeValue(prop, shouldUpdateStatus);
        }
    }
    initObd2LiveFrame(*mPropStore->getConfigOrDie(OBD2_LIVE_FRAME));
    initObd2FreezeFrame(*mPropStore->getConfigOrDie(OBD2_FREEZE_FRAME));
}

这块主要就是将根据默认配置里面的属性配置,将初始化的属性值写入到VehiclePropertyStore中进行缓存。

3.6 SubscriptionManager初始化

SubscriptionManager是在VehicleHalManager中创建并持有的。

cpp 复制代码
mSubscriptionManager(std::bind(&VehicleHalManager::onAllClientsUnsubscribed,
                     this, std::placeholders::_1))
----------------------------------------------------------------------------------------------
SubscriptionManager(const OnPropertyUnsubscribed& onPropertyUnsubscribed)
        : mOnPropertyUnsubscribed(onPropertyUnsubscribed),
            mCallbackDeathRecipient(new DeathRecipient(
                std::bind(&SubscriptionManager::onCallbackDead, this, std::placeholders::_1)))
{}

传入的参数是一个函数,保存在mOnPropertyUnsubscribed中,并初始化mCallbackDeathRecipient为一个DeathRecipient对象,这个对象构造时的参数为onCallbackDead函数。

cpp 复制代码
DeathRecipient(const OnClientDead& onClientDead)
            : mOnClientDead(onClientDead) {}

onCallbackDead保存在mOnClientDead中。

VehicleHalManager中创建SubscriptionManager对象,并对其进行管理。

3.7 RecurrentTimer初始化

RecurrentTimer由EmulatedVehicleHal的构造函数初始化,并适时回调EmulatedVehicleHal中的回调函数。

cpp 复制代码
RecurrentTimer mRecurrentTimer;

mRecurrentTimer(std::bind(&EmulatedVehicleHal::onContinuousPropertyTimer, this,
                                std::placeholders::_1)),

RecurrentTimer(const Action& action) : mAction(action) {
        mTimerThread = std::thread(&RecurrentTimer::loop, this, action);
    }

using Action = std::function<void(const std::vector<int32_t>& cookies)>;

接着3.3节中分析,RecurrentTimer对象创建后,赋值给mRecurrentTimer。RecurrentTimer创建时,传入的参数是一个function类型数据,包含的是一个函数。EmulatedVehicleHal::onContinuousPropertyTimer,bind函数的第二个参数传入this,因为是一个成员函数,然后是一个参数占位符,因为该函数需要传入一个参数。

然后RecurrentTimer的构造函数中,创建了一个线程,执行的函数是RecurrentTimer::loop,传入的参数是Action对象,即onContinuousPropertyTimer

cpp 复制代码
void loop(const Action& action) {
    static constexpr auto kInvalidTime = TimePoint(Nanos::max());

    std::vector<int32_t> cookies;

    while (!mStopRequested) {
        auto now = Clock::now();
        auto nextEventTime = kInvalidTime;
        cookies.clear();

        {
            std::unique_lock<std::mutex> g(mLock);

            for (auto&& it : mCookieToEventsMap) {
            	//获取定时上报事件
                RecurrentEvent& event = it.second;
                if (event.absoluteTime <= now) {
                    event.updateNextEventTime(now);
                    cookies.push_back(event.cookie);
                }

                if (nextEventTime > event.absoluteTime) {
                    nextEventTime = event.absoluteTime;
                }
            }
        }

        if (cookies.size() != 0) {
            action(cookies);
        }

        std::unique_lock<std::mutex> g(mLock);
        mCond.wait_until(g, nextEventTime);  // nextEventTime can be nanoseconds::max()
    }
}

mStopRequested没有其他地方赋值,有默认值为false,所以会进入while循环。

这里主要是定时事件上报的处理逻辑,如果到时间了,就会加入到cookies这个变量中,并调用action这个回到函数,即onContinuousPropertyTimer这个回调函数去处理所有到时间的定时事件。

4 初始化流程图

plantuml代码:

cpp 复制代码
@startuml

participant init
box
participant VehicleService
participant VehicleHalManager
participant VehicleEmulator
participant EmulatedVehicleHal
participant EmulatedVehicleConnector
participant VehiclePropertyStore
participant SocketComm
participant SubscriptionManager
participant DeathRecipient
participant BatchingConsumer
endbox

init -> VehicleService: 拉起服务
VehicleService -> VehiclePropertyStore: new VehiclePropertyStore()
VehicleService -> EmulatedVehicleConnector: new EmulatedVehicleConnector()
VehicleService -> EmulatedVehicleHal: new EmulatedVehicleHal(VehiclePropertyStore* propStore, \n\tVehicleHalClient* client, EmulatedUserHal* emulatedUserHal = nullptr);
EmulatedVehicleHal -> EmulatedVehicleHal: initStaticConfig()
EmulatedVehicleHal -> VehiclePropertyStore: registerProperty(const VehiclePropConfig& config, \n\tVehiclePropertyStore::TokenFunction tokenFunc)
EmulatedVehicleHal -> EmulatedVehicleConnector: registerPropertyValueCallback(PropertyCallBackType&& callback)
VehicleService -> VehicleEmulator: new VehicleEmulator(EmulatedVehicleHalIface* hal)
VehicleEmulator -> EmulatedVehicleHal: registerEmulator(this)
VehicleEmulator -> SocketComm: start()
VehicleService -> VehicleHalManager: new VehicleHalManager(VehicleHal* vehicleHal)
VehicleHalManager -> SubscriptionManager: new (const OnPropertyUnsubscribed& onPropertyUnsubscribed\n\t: mOnPropertyUnsubscribed(onPropertyUnsubscribed),\n\tmCallbackDeathRecipient(new DeathRecipient(\n\tstd::bind(&SubscriptionManager::onCallbackDead, this, std::placeholders::_1)))
SubscriptionManager -> DeathRecipient: new DeathRecipient(const OnClientDead& onClientDead)
VehicleHalManager -> VehicleHalManager: init()
VehicleHalManager -> BatchingConsumer: run()
loop
    BatchingConsumer -> BatchingConsumer: runInternal(const OnBatchReceivedFunc& onBatchReceived)
end loop
VehicleHalManager -> EmulatedVehicleHal: init( \n\tVehiclePropValuePool* valueObjectPool, \n\tconst HalEventFunction& onHalEvent, \n\tconst HalErrorFunction& onHalError)
EmulatedVehicleHal -> EmulatedVehicleHal: onCreate()
EmulatedVehicleHal -> VehiclePropertyStore: writeValue(const VehiclePropValue& propValue, bool updateStatus)
VehicleService -> EmulatedVehicleConnector: setValuePool(VehiclePropValuePool* valuePool)
VehicleService -> VehicleHalManager: registerAsService()
@enduml

流程图

相关推荐
萌面小侠Plus30 分钟前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农31 分钟前
Android Profiler 内存分析
android
大风起兮云飞扬丶31 分钟前
Android——多线程、线程通信、handler机制
android
L725638 分钟前
Android的Handler
android
清风徐来辽38 分钟前
Android HandlerThread 基础
android
HerayChen2 小时前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野2 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing11232 小时前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件2 小时前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio
dj15402252033 小时前
group_concat配置影响程序出bug
android·bug