美文网首页
SDK封装AIDL访问Native Service

SDK封装AIDL访问Native Service

作者: 开开向前冲 | 来源:发表于2017-09-21 22:48 被阅读0次

    版权说明:本文为 开开向前冲 原创文章,转载请注明出处;
    注:限于作者水平有限,文中有不对的地方还请指教

    前言:现在是智能电子时代,五花八门的智能电子设备随处可见,这些电子设备如何实现各自的特色呢?硬件支持,比如智能手机,手表;那么问题来了,我们的硬件提供的服务就在那里,APP层如何去访问这些服务呢?第一时间想到JNI-没问题,正如前面所说,JNI可以访问native,但是这里将会介绍另外一种实现:AIDL——>Native Service;

    实现原理:Native Service实现Binder通信架构,向ServiceManager注册,向外提供通讯接口,Java层定义AIDL,剩下的事情利用Binder 框架完成。

    1 实现Native Service

    前一篇transac——>onTransact 文章最后有提到如何实现一个Native Service,Native Service 实现步骤如下:

    1.实现一个接口文件,IXXXService,继承IInterface
    2.定义BnXXX,继承BnInterface<IXXXService>。实现一个XXXService,继承BnXXX,并实现onTransact()函数。
    3.定义BpXXX,继承BpInterface<IXXXService>。

    这里我实现一个HelloWorld的native Service;

    1.1 实现IXXXService接口
    ------> IHelloService.h
    #include <utils/RefBase.h>
    #include <binder/IInterface.h>
    #include <binder/Parcel.h>
    namespace android
    {
        class IHelloService : public IInterface
        {
        public:
            DECLARE_META_INTERFACE(HelloService); //使用宏,申明HelloService
            virtual void HelloWorld()=0; //定义方法
        };
    
        //定义命令字段
        enum
        {
            HELLO_CMD = IBinder::FIRST_CALL_TRANSACTION,//为0
        };
    
        //申明客户端BpMyService
        class BpHelloService: public BpInterface<IHelloService> {
        public:
            BpHelloService(const sp<IBinder>& impl);
            virtual void HelloWorld();
        };
    
        //申明服务端BnHelloService
        class BnHelloService: public BnInterface<IHelloService> {
        public:
            virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
                    uint32_t flags = 0);
            virtual void HelloWorld();
        };
    }
    

    文件名为IHelloService.h;1:class IHelloService继承于IInterface;2:定义了待实现的接口方法HelloWorld();3:使用宏DECLARE_META_INTERFACE(HelloService)声明HelloService;4:使用enum 声明命令code HELLO_CMD ;

    1.2 定义BnXXX
    1.2.1 定义BnXXX, BnXXX定义在IHelloService.h文件中;
        //申明服务端BnHelloService
        class BnHelloService: public BnInterface<IHelloService> {
        public:
            virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
                    uint32_t flags = 0);
            virtual void HelloWorld();
        };
    
    1.2.2 实现XXXService

    XXXService继承于BnXXX,也就是实现BnXXX中的方法;onTransact可以放到IHellSerivce.cpp中实现,也可以在这里实现;

    ------>  HelloService.h =========>定义HelloService,继承BnHelloService
    #include "IHelloService.h"
    #include <cutils/log.h>
    #include <utils/RefBase.h>
    #include <binder/IServiceManager.h>
    #include <binder/IPCThreadState.h>
    
    namespace android {
        class HelloService : public BnHelloService
        {
            public:
                static int instantiate();
            private:
                HelloService();
                virtual ~HelloService();    
                virtual void HelloWorld();
        };
    };
    
    ---------> HelloService.cpp =======>实现HelloService,即实现BnXXX中的方法
    #include "HelloService.h"
    #include <cutils/log.h>
    #include <binder/IServiceManager.h>
    #include <binder/IPCThreadState.h>
    
    namespace android {
        HelloService::HelloService() { 
        }
        HelloService::~HelloService() {
        }
        int HelloService::instantiate() {
            int ret = defaultServiceManager()->addService(
                          String16("hello.service"), new HelloService());//向外提供服务
            return ret;
        }
    
        // 实现服务端HelloWorld方法
        void HelloService::HelloWorld() {
            printf("HelloService::HelloWorld\n");
        }
    }
    
    1.2.3 BpXXX的定义和实现实现放到IHelloService.cpp中;

    IHelloService.cpp是核心文件,该文件中需要调用IMPLEMENT_META_INTERFACE实现前面的DECLARE_META_INTERFACE,下面列出完整的IHelloService.cpp,IHelloService.cpp实现BpXXX;

    -------> IHelloService.cpp
    #include "IHelloService.h"
    #include <utils/RefBase.h>
    #include <binder/IInterface.h>
    #include <binder/Parcel.h>
    namespace android
    {
        //定义客户端BpHelloService
        class BpHelloService: public BpInterface<IHelloService> {
            public:
                BpHelloService(const sp<IBinder>& impl) 
                    : BpInterface<IHelloService>(impl) {
                }
                virtual void HelloWorld() {
                    Parcel data, reply;
                    data.writeInterfaceToken(IHelloService::getInterfaceDescriptor());
                    remote()->transact(HELLO_CMD, data, &reply);
                    printf("get num from BnHelloService: %d\n", reply.readInt32());
                }
        
        };
        IMPLEMENT_META_INTERFACE(HelloService, "com.keiven.binder.IHelloService");//核心核心,这里的字符串很重要
    
        //服务端,接收远程消息,onTransact方法处理Client传递过来的消息
        //这里onTransact是属于BnHelloService,即使将该方法实现
        //放到HelloService中实现也不能写成 HelloService::onTransact
        status_t BnHelloService::onTransact(uint_t code,      
                                            const Parcel& data, 
                                            Parcel* reply, 
                                            uint32_t flags) {
            CHECK_INTERFACE(IHelloService, data, reply);
            reply->writeNoException();//如果没有writeNoException(),则应用程序访问过程会获得异常
            switch (code) {
            case HELLO_CMD: {    //收到HELLO_CMD命令的处理流程,这个值从Client端传过来
                printf("HelloService:: got the client helloworld\n");
                CHECK_INTERFACE(IHelloService, data, reply);
                HelloWorld(); //这里HelloWorld()方法会调用前面HelloService.cpp中实现的HelloWorld方法
                reply->writeInt32(2015);
                return NO_ERROR;
                }
                break;
            default:
                break;
            }
            return NO_ERROR;
        }
    
    }
    

    IHelloService.h定义了BpHelloService 和 BnHelloService,IHelloService.cpp实现了BpHelloService , BnHelloService 通过HelloService继承实现,也可以不继承直接实现,到这里这个native Service 基本设计完成,还有很重要一点没实现,我们需要这个native Service 能对Java 提供接口,Java 应用层通过ServiceManager.getService接口获取系统服务,所以这里我们需要将该native Service注册到ServiceManager,
    defaultServiceManager()->addService(String16("hello.service"), new HelloService());

    这里就完成了Native Service的创建,我们将该这个native Service 编译成一个so库,由于我们代码中有依赖其他库中的内容,比如libbinder,liblog,libcutils等,所以编写Android.mk编译脚本时需要将这些库导入;

    2 建立服务端server
    ------> HelloServer.cpp
    #include <binder/IPCThreadState.h>
    #include <binder/ProcessState.h>
    #include <binder/IServiceManager.h>
    #include <stdio.h>
    #include <HelloService.h>
    using namespace android;
    
    int main(void)
    {
        printf("Hello server - main() begin\n");
        sp<ProcessState> proc(ProcessState::self());
        int ret = HelloService::instantiate();//注册HelloService到ServiceManager
        printf("Hello server -Hello Service::Instance return %d\n", ret);
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
    
        return 0;
    }
    

    将HelloServer.cpp 编译成一个可执行程序helloserver,然后push 到/system/bin 下执行;

    3 封装一个SDK jar 完成对native Service的调用

    IMPLEMENT_META_INTERFACE(HelloService, "com.keiven.binder.IHelloService")中指定HelloService实现接口的NAME="com.keiven.binder.IHelloService";NAME会给I##INTERFACE::descriptor赋值,到这里我们知道我们的AIDL的包名和命名了,AIDL的名字应该叫IHelloService.aidl,包名为"com.keiven.binder",这样利用AIDL工具生成的JAVA文件就为com.keiven.binder.IHelloService.java,DESCRIPTOR为"com.keiven.binder.IHelloService";

    -------> IInterface.h
    // ----------------------------------------------------------------------
    
    #define DECLARE_META_INTERFACE(INTERFACE)                               \
        static const android::String16 descriptor;                          \
        static android::sp<I##INTERFACE> asInterface(                       \
                const android::sp<android::IBinder>& obj);                  \
        virtual const android::String16& getInterfaceDescriptor() const;    \
        I##INTERFACE();                                                     \
        virtual ~I##INTERFACE();                                            \
    
    
    #define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
        const android::String16 I##INTERFACE::descriptor(NAME);             \
        const android::String16&                                            \
                I##INTERFACE::getInterfaceDescriptor() const {              \
            return I##INTERFACE::descriptor;                                \
        }                                                                   \
        android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
                const android::sp<android::IBinder>& obj)                   \
        {                                                                   \
            android::sp<I##INTERFACE> intr;                                 \
            if (obj != NULL) {                                              \
                intr = static_cast<I##INTERFACE*>(                          \
                    obj->queryLocalInterface(                               \
                            I##INTERFACE::descriptor).get());               \
                if (intr == NULL) {                                         \
                    intr = new Bp##INTERFACE(obj);                          \
                }                                                           \
            }                                                               \
            return intr;                                                    \
        }                                                                   \
        I##INTERFACE::I##INTERFACE() { }                                    \
        I##INTERFACE::~I##INTERFACE() { }                                   \
    
    
    #define CHECK_INTERFACE(interface, data, reply)                         \
        if (!data.checkInterface(this)) { return PERMISSION_DENIED; }       \
    
    // ----------------------------------------------------------------------
    // No user-serviceable parts after this...
    
    3.1 封装一个二进制SDK Jar包用于访问 native Service

    对于Native Service,这里封装一个SDK去调用,SDK需要实现那些内容呢???

    1. 在Java层实现一个AIDL和native Service 相对应;
    2. 调用ServiceManager.getService("hello.service")获取HelloService::instantiate()中注册的服务;
    3. 封装SDK 为一个二进制jar(目的:核心代码不对外公开)。

    下面是二进制Jar 包的源码目录结构,由于会用到ServiceManager,所以这里新建一个包和系统ServiceManager的包名一样,这个类只有一个方法,getService(String name);这个方法不需要实现,直接返回null就可以;


    目录结构.png
    ------> IHelloService.aidl
    // IHelloService.aidl
    package com.keiven.binder;
    
    interface IHelloService {
         void helloWorld();
    }
    

    AIDL 生成的文件如下:
    static final int TRANSACTION_helloWorld = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

    ------> IHelloService.java
    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: E:\\Projects\\apk\\TestJar\\app\\src\\main\\aidl\\com\\keiven\\binder\\IHelloService.aidl
     */
    package com.keiven.binder;
    public interface IHelloService extends android.os.IInterface {
        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements com.keiven.binder.IHelloService {
            private static final java.lang.String DESCRIPTOR = "com.keiven.binder.IHelloService";
            /**
             * Construct the stub at attach it to the interface.
             */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
            /**
             * Cast an IBinder object into an com.keiven.binder.IHelloService interface,
             * generating a proxy if needed.
             */
            public static com.keiven.binder.IHelloService asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof com.keiven.binder.IHelloService))) {
                    return ((com.keiven.binder.IHelloService) iin);
                }
                return new com.keiven.binder.IHelloService.Stub.Proxy(obj);
            }
    
            @Override
            public android.os.IBinder asBinder() {
                return this;
            }
            @Override
            public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, 
                                      int flags) throws android.os.RemoteException {
                switch (code) {
                    case INTERFACE_TRANSACTION: {
                        reply.writeString(DESCRIPTOR);
                        return true;
                    }
                    case TRANSACTION_helloWorld: {
                        data.enforceInterface(DESCRIPTOR);
                        this.helloWorld();
                        reply.writeNoException();
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags);
            }
            private static class Proxy implements com.keiven.binder.IHelloService {
                private android.os.IBinder mRemote;
                Proxy(android.os.IBinder remote) {
                    mRemote = remote;
                }
                @Override
                public android.os.IBinder asBinder() {
                    return mRemote;
                }
                public java.lang.String getInterfaceDescriptor() {
                    return DESCRIPTOR;
                }
                @Override
                public void helloWorld() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_helloWorld, _data, _reply, 0);//java 部分会调用这里
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
            }
            static final int TRANSACTION_helloWorld = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
          //TRANSACTION_helloWorld 必须要和HELLO_CMD 的值相等,BnHelloService.cpp中就是根据这个值去调用相应方法;
        }
        public void helloWorld() throws android.os.RemoteException;
    }
    

    这里helloWorld()方法调用mRemote.transact(Stub.TRANSACTION_helloWorld, _data, _reply, 0);最终会在BnHelloService::onTransact()方法中的switch(code) { case HELLO_CMD:}中得到处理,所以TRANSACTION_helloWorld 必须要和HELLO_CMD相等;

    aidl 很简单,只定义了一个helloWorld()方法;HelloManager用于获取前面在HelloServer中注册的服务,HelloManager中会调用ServiceManager.getService();HelloService 用于对外提供接口;下面是代码;

    ------> ServiceManager.java
    package android.os;
    
    public class ServiceManager {
        public static IBinder getService(String name) { return null; }//没有真正实现,
        //这里的类和方法是当做系统ServiceManager的代理
    }
    
    
    ------> HelloManager.java
    package com.keiven.binder;
    
    import android.os.IBinder;
    import android.os.ServiceManager;
    import android.util.Log;
    
    public class HelloManager {
        private static String HELLO_SERVICE_NAME = "hello.service";//这个名字是前面注册时候的名字
        private static IHelloService helloService = null;
        public static IHelloService getHelloServiceStub() {
            IBinder binder = ServiceManager.getService(HELLO_SERVICE_NAME);//获取服务
            if (helloService == null) {
                helloService = IHelloService.Stub.asInterface(binder);//利用Binder 进行对象转换
            }
            Log.e("Keiven-Chen","get helloservice Success");
            return helloService;
        }
    }
    
    package com.keiven.binder;
    import android.os.RemoteException;
    public class HelloService {
        private HelloService() {
        }
        public static HelloService getInstance() {
            return SingleHolder.instance;
        }
        private static class SingleHolder {
            private static HelloService instance = new HelloService(); //用于单例管理
        }
        public void helloWorld() { //这个类是应用程序想调用的
            try {
                HelloManager.getHelloServiceStub().helloWorld();//调用aidl 的方法,实现跨进程调用
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    

    到这里SDK 的逻辑代码完成,还需要编译成Jar包,在Android Studio 中可以利用Gradle脚本完成编译,这里我将该文件编译成二进制的dex Jar;下面是gradle 核心代码;

    ------> build.gradle
    android {
    ......
        defaultConfig {
                applicationId "com.keiven.binder" //defaultConfig 域中applicationId 值很重要,后续会用到
                ......
        }
    }
    ......
    task class_jar(type: Jar) {
        from "build/intermediates/classes/release/" //核心,将.class 文件编译成Jar包
        from 'src/main/aidl/'
        archiveName 'sdk_tmp.jar'
        doFirst{
            println "Generate sdk_tmp.jar..."
        }
    }
    //Convert the class file to dex file
    task dx_jar(dependsOn:class_jar,type:Exec) {
        workingDir 'build/libs'
        def osType = System.getProperty("os.name").toUpperCase();
        if(osType.contains("LINUX")){
            commandLine 'bash', '-c', "dx --dex --output=sdk_test.jar sdk_tmp.jar"
        }else if(osType.contains("WINDOWS")){
            commandLine 'cmd', '/c', "dx --dex --output=sdk_test.jar sdk_tmp.jar"
        }
        doFirst{
            println "Generate sdk_test.jar..."
        }
    }
    //Delete the temp jar
    task jar(dependsOn: dx_jar, type: Exec) {
        workingDir 'build/libs'
        def osType = System.getProperty("os.name").toUpperCase();
        if(osType.contains("LINUX")){
            commandLine 'bash', '-c', "rm -rf sdk_tmp.jar"
        }else if(osType.contains("WINDOWS")){
            commandLine 'cmd', '/c', "del sdk_tmp.jar"
        }
        doFirst{
            println "Delete sdk_tmp.jar..."
        }
    }
    

    脚本根据操作系统的不同执行不同的命令;生成的Jar包为sdk_test.jar,可以通过在Android Studio的Terminal中运行gradlew build jar进行编译到这里我们的SDK Jar 包制作完成,我们如何使用这个SDK 呢???由于这是一个二进制的dex Jar,所以无法直接在gradle脚本的dependencies选项中直接使用,需要通过代理jar 包的形式,就像我们的前面在我们自己的类中导入ServiceManager一样;

    SDK Jar包制作完成后,这个Jar包不是直接给应用程序调用,我们将它预制到系统目录/system/jar/

    3.2 封装普通SDK Jar代理二进制SDK

    即为二进制sdk_test.jar 生成一个代理jar
    代理Jar包源码很简单,前面AIDL文件中定义了helloWorld()方法在HelloService.java 的helloWorld方法中被调用,应用程序通过执行HelloService.java 中的helloWorld来执行AIDL的helloWorld方法,但是前面的HelloService.java被编译成二进制 Jar,无法被应用程序访问,所以必须要对HelloService.java 进行代理;代理的方式就像前面的ServiceManager.java 代理一样,代理类和被代理类的包名,类名必须一模一样;所以这里新建的HelloService.java的包名和和类名都必须和前面编译二进制Jar包工程中的HelloService.java一模一样,包和类新建完成后,一般针对应用程序需要调用的接口进行代理,这里应用程序想调用helloWorld()方法,所以就代理helloWorld()方法,可以在代理方法中
    直接throw new RuntimeException() 或者返回null都可以;


    代理jar.png
    ------> 代理HelloService.java
    package com.keiven.binder;
    public final class HelloService {
        private HelloService(){
            throw new RuntimeException();
        }
        public static HelloService getInstance() {
            throw new RuntimeException();
        }
        public void helloWorld() { //代理接口
            throw new RuntimeException();
        }
    }
    

    没错,代理文件就是这么简单,总结起来就是代理类需要和被代理类包名,类名一模一样,代理类中实现需要被代理的方法,方法实现很简单,直接throw new RuntimeException() 或者return null 都可以

    ------>代理类的build.gradle
    android {
    ......
       defaultConfig {
              applicationId "com.keiven.binder" //很重要,和二进制Jar包的applicationId一样
            ......
       }
    }
    //Actually created the .jar file
    task jar(type: Jar) {
        //from android.sourceSets.main.java
        from 'build/intermediates/classes/release/'
        archiveName 'proxyjar.jar'
    }
    

    这里将代理类HelloService打包成普通的Jar包供应用程序调用;到这里代理Jar包创建成功;代理Jar包的名字为proxyjar.jar,我们可以新建应用,在应用的依赖中添加这个proxyjar.jar就可以使用里面的方法;

    app ------ > MainActivity.java
    package com.dynamictest;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import com.keiven.binder.HelloService;
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            try {
                HelloService.getInstance().helloWorld();//核心,跨进程调用
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    上述代码HelloService.getInstance().helloWorld()的调用流程如下:
    MainActivity.java——>HelloService.java(代理)——>HelloService.java——>IHelloService.java(AIDL生成)——>IBnHelloService.cpp(onTransact)——>HelloService.cpp;

    应用程序从HelloService.java(代理)调用到系统的HelloService.java,必须说明应用程序访问的目标,即需要在应用程序的AndroidManifest.xml中配置<uses-library>xxxxxx</uses-library>,"xxxxxx"是库的applicationId,是不是很熟悉,前面是在sdk_test.jar和proxyjar.jar工程中配置的applicationId为"com.keiven.binder"; 所以需要在应用程序的AndroidManifest.xml的application中添加如下代码:
    <uses-library android:name="com.keiven.binder" android:required="true"/>

    到这里我们的二进制Jar,代理Jar,服务端可执行程序(用于向ServiceManager注册),APP
    一应俱全,把相应的Jar包和可执行程序push 到系统对应位置,先运行helloserver,在终端中用adb install APP,会弹出 INSTALL_FAILED_MISSING_SHARED_LIBRARY,什么???应用安装不成功,把AndroidManifest.xml中的<uses-library>去掉后编译能正常安装,问题肯定出在uses-library这个标签,网上看了几圈,找到了眉目,由于我们是sdk_test.jar我们是push 到/system/jar目录下面的,外部应用程序需要访问这个库就需要在目录/etc/permissions/下的platform.xml 配置相关内容(真正编译ROM时需要去frameworks/base/data/etc目录下的platform.xml修改编译生效):

    platform.png
    在platform.xml文件中有如下注释:This is a list of all the libraries available for applicationcode to link against.,标明给应用使用的Library 都需要在这里配置;

    adb pull 出手机里面的platform.xml文件,在该文件中添加上述截图代码后adb push 回相关位置,重启手机,重新adb install APP,成功安装;

    应用安装成功后adb shell 进手机目录,1:到/system/bin目录下执行helloserver;2:启动APP;shell 终端中会输出如下截图内容,这些内容是在native Service中调用prinf打印的;


    Log.png

    自此完成了从Java层应用程序到native Service的完整调用过程,总结起来步骤如下:
    1:建立native Service;
    2:根据 native Service中IMPLEMENT_META_INTERFACE声明创建AIDL;封装二进制Jar;
    3:制作代理Jar;
    4:应用程序访问;

    相关文章

      网友评论

          本文标题:SDK封装AIDL访问Native Service

          本文链接:https://www.haomeiwen.com/subject/lcyvsxtx.html