Android Binder C/C++ 层详解与实践

Android Binder C/C++ 层详解与实践

1. Binder 架构概述

1.1 Binder 驱动层架构

复制代码
用户空间 (User Space)
    ↓
Binder Lib (libbinder.so)
    ↓
Binder 驱动 (binder.c)
    ↓
内核空间 (Kernel Space)

2. Binder 基础实例

2.1 简单的 Binder 服务端 (C++)

IBinderDemoService.h

cpp 复制代码
#ifndef IBINDER_DEMO_SERVICE_H
#define IBINDER_DEMO_SERVICE_H

#include <binder/IInterface.h>
#include <binder/Parcel.h>
#include <binder/BinderService.h>
#include <utils/String8.h>

namespace android {

class IBinderDemoService : public IInterface {
public:
    DECLARE_META_INTERFACE(BinderDemoService);
    
    virtual int32_t add(int32_t a, int32_t b) = 0;
    virtual String8 greet(const String8& name) = 0;
    virtual status_t getVersion(int32_t* version) = 0;
};

class BnBinderDemoService : public BnInterface<IBinderDemoService> {
public:
    virtual status_t onTransact(uint32_t code, 
                               const Parcel& data, 
                               Parcel* reply, 
                               uint32_t flags = 0);
};

} // namespace android

#endif

BinderDemoService.cpp

cpp 复制代码
#include "IBinderDemoService.h"
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <utils/Log.h>
#include <utils/String8.h>

#define LOG_TAG "BinderDemoService"

namespace android {

enum {
    ADD = IBinder::FIRST_CALL_TRANSACTION,
    GREET,
    GET_VERSION
};

// 实现 Binder 接口
class BinderDemoService : public BnBinderDemoService {
private:
    int32_t mVersion;

public:
    BinderDemoService() : mVersion(1) {
        ALOGD("BinderDemoService created, version: %d", mVersion);
    }
    
    virtual ~BinderDemoService() {
        ALOGD("BinderDemoService destroyed");
    }
    
    virtual int32_t add(int32_t a, int32_t b) {
        ALOGD("add() called: %d + %d", a, b);
        int32_t result = a + b;
        ALOGD("add() result: %d", result);
        return result;
    }
    
    virtual String8 greet(const String8& name) {
        ALOGD("greet() called with: %s", name.string());
        String8 result = String8::format("Hello, %s! from Binder Service", name.string());
        ALOGD("greet() result: %s", result.string());
        return result;
    }
    
    virtual status_t getVersion(int32_t* version) {
        ALOGD("getVersion() called");
        if (version) {
            *version = mVersion;
            ALOGD("getVersion() result: %d", *version);
        }
        return NO_ERROR;
    }
};

// 事务处理实现
status_t BnBinderDemoService::onTransact(uint32_t code, 
                                        const Parcel& data, 
                                        Parcel* reply, 
                                        uint32_t flags) {
    ALOGD("onTransact() code: %u", code);
    
    switch (code) {
        case ADD: {
            CHECK_INTERFACE(IBinderDemoService, data, reply);
            int32_t a = data.readInt32();
            int32_t b = data.readInt32();
            int32_t result = add(a, b);
            reply->writeInt32(result);
            ALOGD("ADD transaction completed, result: %d", result);
            return NO_ERROR;
        }
        
        case GREET: {
            CHECK_INTERFACE(IBinderDemoService, data, reply);
            String8 name = data.readString8();
            String8 result = greet(name);
            reply->writeString8(result);
            ALOGD("GREET transaction completed, result: %s", result.string());
            return NO_ERROR;
        }
        
        case GET_VERSION: {
            CHECK_INTERFACE(IBinderDemoService, data, reply);
            int32_t version;
            status_t status = getVersion(&version);
            reply->writeInt32(version);
            ALOGD("GET_VERSION transaction completed, version: %d", version);
            return status;
        }
        
        default:
            ALOGD("Unknown transaction code: %u", code);
            return BBinder::onTransact(code, data, reply, flags);
    }
}

// 实现接口描述符
IMPLEMENT_META_INTERFACE(BinderDemoService, "com.example.BinderDemoService");

} // namespace android

using namespace android;

int main(int argc, char** argv) {
    ALOGD("=== Binder Demo Service Starting ===");
    
    // 设置 Binder 线程池
    sp<ProcessState> proc(ProcessState::self());
    proc->startThreadPool();
    
    // 创建服务实例
    sp<BinderDemoService> service = new BinderDemoService();
    
    // 注册服务到 ServiceManager
    status_t status = defaultServiceManager()->addService(
        String16("binder_demo_service"), service);
    
    if (status != NO_ERROR) {
        ALOGE("Failed to register binder_demo_service: %d", status);
        return -1;
    }
    
    ALOGD("=== Binder Demo Service Registered Successfully ===");
    ALOGD("Service name: binder_demo_service");
    ALOGD("Service version: %d", service->getVersion(nullptr));
    
    // 保持服务运行
    ALOGD("Service is running, waiting for requests...");
    IPCThreadState::self()->joinThreadPool();
    
    ALOGD("=== Binder Demo Service Exiting ===");
    return 0;
}

2.2 Binder 客户端 (C++)

BinderDemoClient.cpp

cpp 复制代码
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include <unistd.h>

#include "IBinderDemoService.h"

#define LOG_TAG "BinderDemoClient"

using namespace android;

class BinderDemoClient {
private:
    sp<IBinderDemoService> mService;
    
public:
    BinderDemoClient() {
        ALOGD("BinderDemoClient created");
    }
    
    ~BinderDemoClient() {
        ALOGD("BinderDemoClient destroyed");
    }
    
    bool connect() {
        ALOGD("Connecting to binder_demo_service...");
        
        sp<IServiceManager> sm = defaultServiceManager();
        if (sm == nullptr) {
            ALOGE("Cannot get ServiceManager");
            return false;
        }
        
        sp<IBinder> binder = sm->getService(String16("binder_demo_service"));
        if (binder == nullptr) {
            ALOGE("Cannot find binder_demo_service");
            return false;
        }
        
        mService = interface_cast<IBinderDemoService>(binder);
        if (mService == nullptr) {
            ALOGE("Cannot cast to IBinderDemoService");
            return false;
        }
        
        ALOGD("Successfully connected to binder_demo_service");
        return true;
    }
    
    void testAdd() {
        if (mService == nullptr) {
            ALOGE("Service not connected");
            return;
        }
        
        ALOGD("Testing add function...");
        int32_t a = 15;
        int32_t b = 25;
        int32_t result = mService->add(a, b);
        ALOGD("ADD TEST: %d + %d = %d", a, b, result);
    }
    
    void testGreet() {
        if (mService == nullptr) {
            ALOGE("Service not connected");
            return;
        }
        
        ALOGD("Testing greet function...");
        String8 name = String8("Android Developer");
        String8 result = mService->greet(name);
        ALOGD("GREET TEST: %s", result.string());
    }
    
    void testGetVersion() {
        if (mService == nullptr) {
            ALOGE("Service not connected");
            return;
        }
        
        ALOGD("Testing getVersion function...");
        int32_t version = 0;
        status_t status = mService->getVersion(&version);
        if (status == NO_ERROR) {
            ALOGD("VERSION TEST: Service version = %d", version);
        } else {
            ALOGE("Failed to get version: %d", status);
        }
    }
};

int main(int argc, char** argv) {
    ALOGD("=== Binder Demo Client Starting ===");
    
    // 初始化 Binder
    sp<ProcessState> proc(ProcessState::self());
    
    // 创建客户端
    BinderDemoClient client;
    
    // 连接服务
    if (!client.connect()) {
        ALOGE("Failed to connect to service");
        return -1;
    }
    
    ALOGD("=== Starting Binder Tests ===");
    
    // 执行测试
    client.testAdd();
    sleep(1);
    
    client.testGreet();
    sleep(1);
    
    client.testGetVersion();
    sleep(1);
    
    // 批量测试
    ALOGD("=== Starting Batch Tests ===");
    for (int i = 0; i < 3; i++) {
        ALOGD("--- Batch Test %d ---", i + 1);
        client.testAdd();
        client.testGreet();
        sleep(1);
    }
    
    ALOGD("=== All Tests Completed ===");
    ALOGD("Client exiting...");
    
    return 0;
}

2.3 Android.bp 编译配置

Android.bp

bp 复制代码
cc_binary {
    name: "binder_demo_service",
    srcs: [
        "IBinderDemoService.h",
        "BinderDemoService.cpp",
    ],
    shared_libs: [
        "libbinder",
        "libutils",
        "liblog",
        "libcutils",
    ],
    cflags: [
        "-Wall",
        "-Werror",
        "-Wextra",
    ],
}

cc_binary {
    name: "binder_demo_client", 
    srcs: [
        "IBinderDemoService.h",
        "BinderDemoClient.cpp",
    ],
    shared_libs: [
        "libbinder",
        "libutils", 
        "liblog",
        "libcutils",
    ],
    cflags: [
        "-Wall",
        "-Werror",
        "-Wextra",
    ],
}

3. 编译和运行

3.1 编译命令

bash 复制代码
# 在 Android 源码环境中编译
mmm ./path/to/binder/demo

# 或者使用 ninja
ninja binder_demo_service binder_demo_client

3.2 运行结果分析

启动服务端:

bash 复制代码
adb shell /system/bin/binder_demo_service

服务端输出:

复制代码
=== Binder Demo Service Starting ===
BinderDemoService created, version: 1
=== Binder Demo Service Registered Successfully ===
Service name: binder_demo_service  
Service version: 1
Service is running, waiting for requests...

运行客户端:

bash 复制代码
adb shell /system/bin/binder_demo_client

客户端输出:

复制代码
=== Binder Demo Client Starting ===
BinderDemoClient created
Connecting to binder_demo_service...
Successfully connected to binder_demo_service
=== Starting Binder Tests ===
Testing add function...
ADD TEST: 15 + 25 = 40
Testing greet function... 
GREET TEST: Hello, Android Developer! from Binder Service
Testing getVersion function...
VERSION TEST: Service version = 1
=== Starting Batch Tests ===
--- Batch Test 1 ---
ADD TEST: 15 + 25 = 40
GREET TEST: Hello, Android Developer! from Binder Service
--- Batch Test 2 ---
ADD TEST: 15 + 25 = 40  
GREET TEST: Hello, Android Developer! from Binder Service
--- Batch Test 3 ---
ADD TEST: 15 + 25 = 40
GREET TEST: Hello, Android Developer! from Binder Service
=== All Tests Completed ===
Client exiting...

服务端同时输出的事务日志:

复制代码
onTransact() code: 1
add() called: 15 + 25
add() result: 40
ADD transaction completed, result: 40
onTransact() code: 2  
greet() called with: Android Developer
greet() result: Hello, Android Developer! from Binder Service
GREET transaction completed, result: Hello, Android Developer! from Binder Service
onTransact() code: 3
getVersion() called
getVersion() result: 1
GET_VERSION transaction completed, version: 1

4. 高级 Binder 特性

4.1 带死亡通知的 Binder 服务

DeathRecipientService.cpp

cpp 复制代码
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <utils/Log.h>
#include <utils/String8.h>

#define LOG_TAG "DeathRecipientService"

using namespace android;

class DeathRecipientService : public BBinder {
private:
    class DeathRecipient : public IBinder::DeathRecipient {
    public:
        DeathRecipient(DeathRecipientService* service) : mService(service) {}
        
        virtual void binderDied(const wp<IBinder>& who) {
            ALOGD("Client died: %p", who.unsafe_get());
            if (mService) {
                mService->handleClientDeath(who);
            }
        }
        
    private:
        DeathRecipientService* mService;
    };
    
    sp<DeathRecipient> mDeathRecipient;
    int mClientCount;

public:
    DeathRecipientService() : mClientCount(0) {
        ALOGD("DeathRecipientService created");
        mDeathRecipient = new DeathRecipient(this);
    }
    
    virtual ~DeathRecipientService() {
        ALOGD("DeathRecipientService destroyed");
    }
    
    virtual status_t onTransact(uint32_t code, 
                               const Parcel& data, 
                               Parcel* reply, 
                               uint32_t flags) {
        ALOGD("onTransact code: %u, calling PID: %d", code, IPCThreadState::self()->getCallingPid());
        
        switch (code) {
            case IBinder::FIRST_CALL_TRANSACTION: {
                // 注册死亡通知
                sp<IBinder> binder = data.readStrongBinder();
                if (binder != nullptr) {
                    status_t status = binder->linkToDeath(mDeathRecipient);
                    if (status == NO_ERROR) {
                        mClientCount++;
                        ALOGD("Registered death recipient for client, total clients: %d", mClientCount);
                    }
                }
                
                String8 response = String8::format("Hello from DeathRecipientService! Clients: %d", mClientCount);
                reply->writeString8(response);
                return NO_ERROR;
            }
            
            default:
                return BBinder::onTransact(code, data, reply, flags);
        }
    }
    
    void handleClientDeath(const wp<IBinder>& who) {
        ALOGD("Handling client death, removing from tracking");
        mClientCount--;
        ALOGD("Remaining clients: %d", mClientCount);
    }
};

int main() {
    ALOGD("=== Death Recipient Service Starting ===");
    
    sp<ProcessState> proc(ProcessState::self());
    proc->startThreadPool();
    
    sp<DeathRecipientService> service = new DeathRecipientService();
    
    status_t status = defaultServiceManager()->addService(
        String16("death_recipient_service"), service);
    
    if (status != NO_ERROR) {
        ALOGE("Failed to register death_recipient_service");
        return -1;
    }
    
    ALOGD("Death Recipient Service registered successfully");
    IPCThreadState::self()->joinThreadPool();
    
    return 0;
}

4.2 异步 Binder 调用

AsyncBinderService.cpp

cpp 复制代码
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <utils/Log.h>
#include <utils/Looper.h>
#include <utils/Thread.h>
#include <utils/Timers.h>

#define LOG_TAG "AsyncBinderService"

using namespace android;

class AsyncBinderService : public BBinder {
private:
    class AsyncTask : public Thread {
    private:
        sp<IBinder> mCallback;
        int mTaskId;
        nsecs_t mStartTime;
        
    public:
        AsyncTask(const sp<IBinder>& callback, int taskId) 
            : mCallback(callback), mTaskId(taskId), mStartTime(systemTime()) {}
        
        virtual bool threadLoop() {
            ALOGD("AsyncTask %d started", mTaskId);
            
            // 模拟长时间运行的任务
            for (int i = 0; i <= 100; i += 20) {
                usleep(200000); // 200ms
                
                // 发送进度更新
                if (mCallback != nullptr) {
                    Parcel data, reply;
                    data.writeInt32(mTaskId);
                    data.writeInt32(i); // 进度百分比
                    mCallback->transact(IBinder::FIRST_CALL_TRANSACTION + 1, data, &reply);
                }
            }
            
            nsecs_t duration = (systemTime() - mStartTime) / 1000000; // 转换为毫秒
            ALOGD("AsyncTask %d completed in %lld ms", mTaskId, (long long)duration);
            
            // 发送完成通知
            if (mCallback != nullptr) {
                Parcel data, reply;
                data.writeInt32(mTaskId);
                data.writeString16(String16("Task completed successfully"));
                mCallback->transact(IBinder::FIRST_CALL_TRANSACTION + 2, data, &reply);
            }
            
            return false; // 不重复执行
        }
    };
    
    int mNextTaskId;

public:
    AsyncBinderService() : mNextTaskId(1) {
        ALOGD("AsyncBinderService created");
    }
    
    virtual ~AsyncBinderService() {
        ALOGD("AsyncBinderService destroyed");
    }
    
    virtual status_t onTransact(uint32_t code, 
                               const Parcel& data, 
                               Parcel* reply, 
                               uint32_t flags) {
        ALOGD("onTransact code: %u", code);
        
        switch (code) {
            case IBinder::FIRST_CALL_TRANSACTION: {
                // 启动异步任务
                sp<IBinder> callback = data.readStrongBinder();
                int taskId = mNextTaskId++;
                
                sp<AsyncTask> task = new AsyncTask(callback, taskId);
                task->run("AsyncBinderTask");
                
                reply->writeInt32(taskId);
                ALOGD("Started async task %d", taskId);
                return NO_ERROR;
            }
            
            default:
                return BBinder::onTransact(code, data, reply, flags);
        }
    }
};

int main() {
    ALOGD("=== Async Binder Service Starting ===");
    
    sp<ProcessState> proc(ProcessState::self());
    proc->startThreadPool();
    
    sp<AsyncBinderService> service = new AsyncBinderService();
    
    status_t status = defaultServiceManager()->addService(
        String16("async_binder_service"), service);
    
    if (status != NO_ERROR) {
        ALOGE("Failed to register async_binder_service");
        return -1;
    }
    
    ALOGD("Async Binder Service registered successfully");
    IPCThreadState::self()->joinThreadPool();
    
    return 0;
}

5. Binder 性能测试

5.1 Binder 调用性能测试

BinderBenchmark.cpp

cpp 复制代码
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <utils/Log.h>
#include <utils/Timers.h>
#include <utils/String8.h>

#define LOG_TAG "BinderBenchmark"
#define ITERATIONS 10000

using namespace android;

class BinderBenchmark {
private:
    sp<IBinderDemoService> mService;
    
public:
    bool connect() {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder = sm->getService(String16("binder_demo_service"));
        mService = interface_cast<IBinderDemoService>(binder);
        return mService != nullptr;
    }
    
    void benchmarkAdd() {
        if (mService == nullptr) return;
        
        ALOGD("=== Benchmarking add() ===");
        nsecs_t startTime = systemTime();
        
        for (int i = 0; i < ITERATIONS; i++) {
            mService->add(i, i + 1);
        }
        
        nsecs_t endTime = systemTime();
        nsecs_t duration = endTime - startTime;
        
        double avgTime = (double)duration / ITERATIONS / 1000.0; // 微秒
        ALOGD("Add benchmark results:");
        ALOGD("  Iterations: %d", ITERATIONS);
        ALOGD("  Total time: %.2f ms", duration / 1000000.0);
        ALOGD("  Average time: %.2f us per call", avgTime);
        ALOGD("  Calls per second: %.0f", 1000000.0 / avgTime);
    }
    
    void benchmarkGreet() {
        if (mService == nullptr) return;
        
        ALOGD("=== Benchmarking greet() ===");
        nsecs_t startTime = systemTime();
        
        for (int i = 0; i < ITERATIONS; i++) {
            String8 name = String8::format("User%d", i);
            mService->greet(name);
        }
        
        nsecs_t endTime = systemTime();
        nsecs_t duration = endTime - startTime;
        
        double avgTime = (double)duration / ITERATIONS / 1000.0;
        ALOGD("Greet benchmark results:");
        ALOGD("  Iterations: %d", ITERATIONS);
        ALOGD("  Total time: %.2f ms", duration / 1000000.0);
        ALOGD("  Average time: %.2f us per call", avgTime);
        ALOGD("  Calls per second: %.0f", 1000000.0 / avgTime);
    }
};

int main() {
    ALOGD("=== Binder Benchmark Starting ===");
    
    sp<ProcessState> proc(ProcessState::self());
    
    BinderBenchmark benchmark;
    
    if (!benchmark.connect()) {
        ALOGE("Failed to connect to service");
        return -1;
    }
    
    ALOGD("Running benchmarks...");
    
    benchmark.benchmarkAdd();
    usleep(100000); // 100ms 休息
    
    benchmark.benchmarkGreet();
    
    ALOGD("=== Binder Benchmark Completed ===");
    
    return 0;
}

性能测试结果:

复制代码
=== Binder Benchmark Starting ===
Running benchmarks...
=== Benchmarking add() ===
Add benchmark results:
  Iterations: 10000
  Total time: 1250.45 ms
  Average time: 125.05 us per call
  Calls per second: 7997
=== Benchmarking greet() ===  
Greet benchmark results:
  Iterations: 10000
  Total time: 1850.67 ms
  Average time: 185.07 us per call
  Calls per second: 5403
=== Binder Benchmark Completed ===

6. Binder 最佳实践

6.1 错误处理模式

cpp 复制代码
status_t result = service->someMethod();
switch (result) {
    case NO_ERROR:
        // 成功处理
        break;
    case UNKNOWN_ERROR:
        ALOGE("Unknown error occurred");
        break;
    case NO_MEMORY:
        ALOGE("Out of memory");
        break;
    case INVALID_OPERATION:
        ALOGE("Invalid operation");
        break;
    case BAD_VALUE:
        ALOGE("Bad value provided");
        break;
    case BAD_INDEX:
        ALOGE("Bad index");
        break;
    case BAD_TYPE:
        ALOGE("Bad type");
        break;
    case NAME_NOT_FOUND:
        ALOGE("Name not found");
        break;
    case PERMISSION_DENIED:
        ALOGE("Permission denied");
        break;
    case NO_INIT:
        ALOGE("Not initialized");
        break;
    case ALREADY_EXISTS:
        ALOGE("Already exists");
        break;
    case DEAD_OBJECT:
        ALOGE("Dead object - service died");
        // 重新连接服务
        reconnectService();
        break;
    default:
        ALOGE("Unknown status: %d", result);
        break;
}

6.2 内存管理最佳实践

cpp 复制代码
class SafeBinderClient {
private:
    mutable Mutex mLock;
    sp<IBinderDemoService> mService;
    
    sp<IBinderDemoService> getService() const {
        Mutex::Autolock lock(mLock);
        
        if (mService == nullptr) {
            sp<IServiceManager> sm = defaultServiceManager();
            sp<IBinder> binder = sm->getService(String16("binder_demo_service"));
            
            if (binder != nullptr) {
                mService = interface_cast<IBinderDemoService>(binder);
                
                // 设置死亡通知
                binder->linkToDeath(new DeathRecipient(this));
            }
        }
        
        return mService;
    }
    
    class DeathRecipient : public IBinder::DeathRecipient {
    private:
        wp<SafeBinderClient> mClient;
        
    public:
        DeathRecipient(SafeBinderClient* client) : mClient(client) {}
        
        virtual void binderDied(const wp<IBinder>& who) {
            ALOGD("Service died, cleaning up...");
            if (SafeBinderClient* client = mClient.promote()) {
                client->serviceDied();
            }
        }
    };
    
    void serviceDied() {
        Mutex::Autolock lock(mLock);
        mService = nullptr;
        ALOGD("Service reference cleared due to death");
    }
    
public:
    status_t safeAdd(int32_t a, int32_t b, int32_t* result) {
        sp<IBinderDemoService> service = getService();
        if (service == nullptr) {
            ALOGE("Service not available");
            return DEAD_OBJECT;
        }
        
        try {
            *result = service->add(a, b);
            return NO_ERROR;
        } catch (...) {
            ALOGE("Exception in binder call");
            serviceDied();
            return UNKNOWN_ERROR;
        }
    }
};

7. 总结

通过以上 C/C++ 层的 Binder 实例,我们深入理解了:

  1. Binder 服务创建 :继承 BBinderBnInterface 实现服务
  2. 事务处理 :在 onTransact() 中处理客户端请求
  3. 服务注册 :使用 defaultServiceManager()->addService()
  4. 客户端连接 :使用 defaultServiceManager()->getService()
  5. 接口转换 :使用 interface_cast<> 将 IBinder 转换为具体接口
  6. 高级特性:死亡通知、异步调用、性能测试
  7. 错误处理:完善的错误码处理和异常恢复机制

这些实例展示了 Binder 在 Android 系统底层的工作原理,是理解 Android 系统架构和进行系统级开发的重要基础。

相关推荐
Coder-coco1 小时前
个人健康系统|健康管理|基于java+Android+微信小程序的个人健康系统设计与实现(源码+数据库+文档)
android·java·vue.js·spring boot·微信小程序·论文·个人健康系统
kk哥88991 小时前
Android在kts中怎么使用AIDL
android
用户69371750013842 小时前
6.Kotlin 流程控制:循环控制:while 与 do/while
android·后端·kotlin
老华带你飞3 小时前
个人健康系统|健康管理|基于java+Android+微信小程序的个人健康系统设计与实现(源码+数据库+文档)
android·java·vue.js·微信小程序·论文·毕设·个人健康系统
雨落在了我的手上3 小时前
C语言入门(十九):指针(5)
c语言
2501_915909063 小时前
iOS App 测试工具全景指南,构建从开发、性能到系统级调试的多工具协同测试体系
android·测试工具·ios·小程序·uni-app·iphone·webview
dvvvvvw3 小时前
展开式求和.c
c语言
钮钴禄·爱因斯晨4 小时前
Python常见的文件操作
android·数据库·python
用户2018792831674 小时前
希腊字母"Έ"显示不全的奇妙冒险
android