SDK封装AIDL访问Native Service

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

前言:现在是智能电子时代,五花八门的智能电子设备随处可见,这些电子设备如何实现各自的特色呢?硬件支持,比如智能手机,手表;那么问题来了,我们的硬件提供的服务就在那里,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:应用程序访问;

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 157,624评论 24 688
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 121,040评论 16 134
  • 1:InputChannel提供函数创建底层的Pipe对象 2: 1)客户端需要新建窗口 2)new ViewRo...
    自由人是工程师阅读 3,859评论 0 17
  • 最近加入了一个读书会,读的第一本书就是之前被安利无数遍的《天才在左疯子在右》,这本书居然是一位中学辍学的人写的。我...
    遇见生命的美好阅读 660评论 0 2
  • 我这次回正阳,感恩以前同事于洪在她家里亲自做了一桌好菜,召集了老家几位好朋友,相聚一起,相互诉说思念的衷肠,回...
    雪域红梅阅读 51评论 0 1