【问题标题】:AIDL interface between Java and C++Java和C++之间的AIDL接口
【发布时间】:2021-03-24 19:40:07
【问题描述】:

我是 AIDL 界面的新手。 我想在使用 gradle 构建的 Java(应用程序层)和使用 cmake 构建的 C++(本机层)之间实现 AIDL 接口。我需要使用 AIDL 接口在这两个进程之间传递大量数据。我能够在应用层实现 .aidl 文件并能够创建服务。我需要在本机层实现一个aidl客户端并且需要传递数据。谁能建议我如何实现要构建 cmake 的 AIDL。

【问题讨论】:

  • AOSP 附带一个工具aidl-cpp,用于从 AIDL 文件生成 C++ 标头和存根。您必须在调用 aidl-cpp 的 cmake 构建脚本中添加代码生成阶段。在您的本机代码中,您将包含生成的标头并链接到生成的存根。
  • 我参考页面link 获取aidl-cpp。在这个页面中,他们提到使用 android.mk 文件,但我们使用 cmake 在本机层上构建我们的插件。是否可以使用 cmake 构建系统构建aidl-cpp
  • AOSP 附带的 Android.mk 文件有一些用于调用aidl-cpp 的宏,但可以在没有Android.mk 的情况下调用aidl-cpp。因此,您必须以某种方式获取aidl-cpp 二进制文件(例如,通过一次检查 AOSP 并构建aidl-cpp)。获得二进制文件后,您可以将其放置在您的 CMakeLists.txt 可以访问它的位置,并向您的 CMakeLists.txt 添加代码生成阶段(参见例如here,了解如何使用 cmake 进行代码生成)。
  • 感谢您的建议。我使用 Android Studio 在 Java 应用程序中创建了该服务。现在我需要在本机层(c++ 代码)上创建一个客户端,该客户端将与在 java 应用程序中创建的服务链接,并且需要 onBind() 和 serviceConnection() 以根据 link 与服务连接。是否可以在本机层添加这些 api 并在 AIDL 服务和客户端之间创建连接以传输大量数据。你能给我建议吗?
  • @Srinivas,我需要更清楚你的问题。您有一个实现 AIDL 接口的 Android 绑定服务 (Java)。您的 Java 客户端组件(如 Activity)需要绑定到服务并通过ServiceConnection 获取IBinder。然后,您需要通过 JNI 将该 Java Binder 传递给本机 C++ 层,并从 C++ 层与服务通信。如果这是您需要做的,可以使用 Android 的新 NdkBinder API(从 API 级别 29 起可用)来完成。

标签: java c++ interface aidl


【解决方案1】:

这个例子由一个 Activity 组成,它绑定一个 Service (Java) 并将 IBinder 对象传递给 C++ JNI 层。使用 NDK Binder API 在 JNI 层中与 Service 进行通信。

AIDL。

src/main/aidl/com/example/IMyService.aidl

package com.example;

import com.example.ComplexType;

interface IMyService
{
    ComplexType returnComplexType(int anInt,
                    long aLong, boolean aBoolean,
                    float aFloat, double aDouble,
                    String aString);
}

src/main/aidl/com/example/ComplexType.aidl

package com.example;

parcelable ComplexType cpp_header "ComplexType.h";

build.gradle 文件有一个 Gradle 任务 (compileAidlNdk) 为 IMyService.aidl 自动生成 NDK C++ binder 源文件。

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion '29.0.3'
    
    defaultConfig {
        minSdkVersion 29
        
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++17"
            }
        }
    }
    
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
    
    ...
}

task compileAidlNdk() {
    doLast {
        def aidlCpp = [android.sdkDirectory,
                       'build-tools',
                       android.buildToolsVersion,
                       'aidl'].join(File.separator)

        def outDir = [projectDir.absolutePath,
                     'src', 'main', 'cpp', 'aidl'].join(File.separator)

        def headerOutDir = [projectDir.absolutePath,
                           'src', 'main', 'cpp', 'includes'].join(File.separator)

        def searchPathForImports = [projectDir.absolutePath,
                                   'src', 'main', 'aidl'].join(File.separator)

        def aidlFile = [projectDir.absolutePath,
                       'src', 'main', 'aidl',
                       'com', 'example', 'IMyService.aidl'].join(File.separator)

        exec {
            executable(aidlCpp)
            args('--lang=ndk',
                 '-o', outDir,
                 '-h', headerOutDir,
                 '-I', searchPathForImports,
                 aidlFile)
        }
    }
}

afterEvaluate {
    preBuild.dependsOn(compileAidlNdk)
}

src/main/java/com/example/ndkbinderclient/MainActivity.java

package com.example.ndkbinderclient;

public class MainActivity extends AppCompatActivity implements ServiceConnection
{
    static
    {
        System.loadLibrary("native-lib");
    }
    
    private volatile boolean mIsServiceConnected = false;
    private final ConditionVariable mServiceConnectionWaitLock = new ConditionVariable();
    
    public native void onServiceConnected(IBinder binder);
    public native void onServiceDisconnected();
    public native void talkToService();
    
    @Override
    protected void onResume()
    {
        super.onResume();

        Intent intent = new Intent();
        intent.setClassName("com.example",
                "com.example.javabinderservice.MyService");

        bindService(intent, this, BIND_AUTO_CREATE);

        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                // Not connected to service yet?
                while(!mIsServiceConnected)
                {
                    // waits for service connection
                    mServiceConnectionWaitLock.block();
                }

                talkToService();
            }
        }).start();
    }
    
    @Override
    protected void onPause()
    {
        unbindService(this);

        mIsServiceConnected = false;

        onServiceDisconnected();

        super.onPause();
    }
    
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder)
    {
        onServiceConnected(iBinder);

        mIsServiceConnected = true;

        // breaks service connection waits
        mServiceConnectionWaitLock.open();
    }
    
    @Override
    public void onServiceDisconnected(ComponentName componentName)
    {
        mIsServiceConnected = false;

        onServiceDisconnected();
    }
}

src/main/cpp/native-lib.cpp

#include <jni.h>
#include <aidl/com/example/IMyService.h>
#include <android/binder_ibinder_jni.h>

std::shared_ptr<IMyService> g_spMyService;

extern "C" JNIEXPORT void JNICALL
Java_com_example_ndkbinderclient_MainActivity_onServiceConnected(
        JNIEnv* env,
        jobject /* this */,
        jobject binder)
{
    AIBinder* pBinder = AIBinder_fromJavaBinder(env, binder);

    const ::ndk::SpAIBinder spBinder(pBinder);
    g_spMyService = IMyService::fromBinder(spBinder);
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_ndkbinderclient_MainActivity_onServiceDisconnected(
        JNIEnv* env,
        jobject /* this */)
{
    g_spMyService = nullptr;
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_ndkbinderclient_MainActivity_talkToService(
        JNIEnv* env,
        jobject /* this */)
{
    ComplexType returnedComplexObject;

    ScopedAStatus returnComplexTypeResult = g_spMyService->returnComplexType(2021,
            65535000, true, 3.14f, 3.141592653589793238,
            "Hello, World!", &returnedComplexObject);
}

src/main/cpp/includes/ComplexType.h

#pragma once

#include <android/binder_status.h>

class ComplexType
{
public:
    int i_Int;
    long l_Long;
    bool b_Boolean;
    float f_Float;
    double d_Double;
    std::string s_String;
    
public:
    binder_status_t readFromParcel(const AParcel* pParcel)
    {
        int32_t iNotNull;
        AParcel_readInt32(pParcel, &iNotNull);

        AParcel_readInt32(pParcel, &i_Int);

        AParcel_readInt64(pParcel, &l_Long);

        AParcel_readBool(pParcel, &b_Boolean);

        AParcel_readFloat(pParcel, &f_Float);

        AParcel_readDouble(pParcel, &d_Double);

        ndk::AParcel_readString(pParcel, &s_String);

        return STATUS_OK;
    }
    
    binder_status_t writeToParcel(AParcel* pParcel) const
    {
        int32_t iNotNull = 1;
        AParcel_writeInt32(pParcel, iNotNull);

        AParcel_writeInt32(pParcel, i_Int);

        AParcel_writeInt64(pParcel, l_Long);

        AParcel_writeBool(pParcel, b_Boolean);

        AParcel_writeFloat(pParcel, f_Float);

        AParcel_writeDouble(pParcel, d_Double);

        ndk::AParcel_writeString(pParcel, s_String);

        return STATUS_OK;
    }
};

src/main/cpp/CMakeLists.txt

cmake_minimum_required(VERSION 3.10.2)

add_library (
        native-lib
        SHARED
        native-lib.cpp
        aidl/com/example/IMyService.cpp
)

target_include_directories (
        native-lib
        PRIVATE
        includes
)

target_link_libraries (
        native-lib
        binder_ndk
)

src/main/java/com/example/ndkbinderservice/MyService.java

package com.example.javabinderservice;

public class MyService extends Service
{
    private IBinder mBinder;
    
    @Override
    public void onCreate()
    {
        super.onCreate();

        mBinder = new MyServiceBinder();
    }
    
    @Override
    public IBinder onBind(Intent intent)
    {
        return mBinder;
    }
    
    private static class MyServiceBinder extends IMyService.Stub
    {
        @Override
        public ComplexType returnComplexType(int anInt,
                               long aLong, boolean aBoolean,
                               float aFloat, double aDouble,
                               String aString) throws RemoteException
        {
            return new ComplexType(anInt, aLong, aBoolean, aFloat,
                                   aDouble, aString);
        }
    }
}

src/main/java/com/example/ComplexType.java

public class ComplexType implements Parcelable
{
    public final int mInt;
    public final long mLong;
    public final boolean mBoolean;
    public final float mFloat;
    public final double mDouble;
    public final String mString;
    
    protected ComplexType(Parcel in)
    {
        mInt = in.readInt();
        mLong = in.readLong();
        mBoolean = in.readBoolean();
        mFloat = in.readFloat();
        mDouble = in.readDouble();
        mString = in.readString();
    }
    
    @Override
    public void writeToParcel(Parcel parcel, int i)
    {
        parcel.writeInt(mInt);
        parcel.writeLong(mLong);
        parcel.writeBoolean(mBoolean);
        parcel.writeFloat(mFloat);
        parcel.writeDouble(mDouble);
        parcel.writeString(mString);
    }
    
    ...
}

src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example">
    
    <application>
    
        <activity android:name=".ndkbinderclient.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <service
            android:name=".ndkbinderservice.MyService"
            android:exported="true">
        </service>
    
    </application>

</manifest>

完整的源代码可以在https://github.com/lakinduboteju/AndroidNdkBinderExamples找到

【讨论】:

    【解决方案2】:

    要获取aidl-cpp 二进制文件,您必须使用指令here 和前几条指令here 设置AOSP 源代码。设置好构建环境后,您可以使用make aidl-cpp 构建二进制文件。然后可以找到二进制文件,例如在out/host/linux-x86/bin/aidl-cpp。您只需要这样做一次,在您获得二进制文件后,您不再需要 AOSP 代码(尽管有代码可以快速搜索示例)。

    关于 CMake 部分,正如评论中所讨论的,一旦构建了 aidl-cpp 二进制文件,就可以使用 CMake 命令 add_custom_target 进行代码生成。

    aidl-cpp 命令提供以下说明:

    usage: aidl-cpp INPUT_FILE HEADER_DIR OUTPUT_FILE
    
    OPTIONS:
       -I<DIR>   search path for import statements
       -d<FILE>  generate dependency file
       -ninja    generate dependency file in a format ninja understands
    
    INPUT_FILE:
       an aidl interface file
    HEADER_DIR:
       empty directory to put generated headers
    OUTPUT_FILE:
       path to write generated .cpp code
    

    所以当你从 cmake 调用命令时,你必须提供你的 AIDL,然后是生成的头文件应该存储的目录,以及生成的 cpp 文件应该存储的名称。例如。 aidl-cpp ISomeInterface.aidl ./generated-headers ./generated-cpp/ISomeInterface.cpp。如果您想使用自定义数据类型(请参阅此答案的最后一部分),您还必须传递 -I 标志以指向自定义数据类型的声明。

    在 C++ 中创建活页夹客户端

    使用aidl-cpp 生成头文件和cpp 文件后,您就可以从C++ 连接到您的服务。下面是一个来自 AOSP 源代码的例子:

    • 有一个名为 IDropboxManagerService 的 AIDL 文件。

    • AIDL定义的接口在Java中实现为DropboxManagerService

    • 从 C++ 调用服务here:

      • 请注意文件开头的 &lt;com/android/internal/os/IDropBoxManagerService.h 包含 (link)。这是aidl-cpp从AIDL文件中生成的头文件。

      • 默认服务管理器用于查找名为"dropbox"的服务:

        defaultServiceManager()->getService(android::String16("dropbox"))
        
      • getService 的结果被强制转换为生成的头文件中定义的接口:

        sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(...)
        
      • service 对象现在包含一个代理对象,您可以在该对象上调用 AIDL 中定义的方法。然后,代理对象会通过 Binder 将这些调用转发给 Java 服务(这在 aidl-cpp 生成的 cpp 文件中实现)。

        例如,AIDL 定义了一个方法void add(in DropBoxManager.Entry entry);。 C++ 代码使用Status status = service-&gt;add(entry); 调用此方法。您可以通过status.isOk()查看交易结果。

    发送自定义数据类型

    关于数据类型,您可以找到一张表,其中包含 C++ 类型到 Java 类型的映射here

    如果要发送自定义对象,则必须定义一个 C++ 类和一个 Java 类,它们都实现了Parcelable 接口。该接口需要在 C++ 中实现 writeToParcel / readFromParcel 和在 Java 中实现 writeToParcel / createFromParcel 两个方法。您必须在每一侧提供匹配的实现,以便调用例如C++ 对象上的writeToParcel 生成一个平面二进制编码,Java 端的readFromParcel 可以读取该编码。

    您还必须使用 cpp_header 指令提供指向自定义对象标头的 AIDL 文件。请参见以下示例:

    • 在上一节的DropBoxManagerService 示例中,我们看到了Entry 类型的对象。

      • 在 C++ 端,该类声明为 here 并实现 here

      • 在 Java 端,该类定义为 here

    • 在 AIDL 中,parcelable 声明为 here。请注意,这没有定义任何实现细节,它只是声明有一个同名的类。它还指向 C++ 标头,aidl-cpp 将包含该标头。

    • 我们在前面的示例中看到的DropBoxManagerService 的 AIDL 导入了可包裹 here 的 AIDL。这允许在 AIDL 方法声明中使用此数据类型。

    基于这些步骤,然后可以将 C++ 中定义的 Entry 类从 C++ 发送到 Java。当在 C++ 中将Entry 对象传递给代理对象时,由aidl-cpp 生成的cpp 将使用为Entry 类实现的writeToParcel 函数将类序列化为平面二进制编码。在 Java 端,接收到编码并根据编码构造一个Entry Java 对象。

    【讨论】:

    • 如果我使用命令aidl-cpp INPUT_FILE HEADER_DIR OUTPUT_FILE来构建.aidl文件,那么真的需要cmake吗?
    • 你也可以手动运行一次,这样你就不用从cmake调用aidl-cpp了。唯一的缺点是,如果对 AIDL 文件进行了更改,您必须记住重新运行该命令。而如果你从你的 cmake 调用命令,它应该会自动重新运行。所以如果AIDL文件很少改动,那么你真的不需要去适配cmake文件,如果经常改动,那么适配cmake文件会更方便。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-27
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 2012-01-26
    • 1970-01-01
    相关资源
    最近更新 更多