版权说明:本文为 开开向前冲 原创文章,转载请注明出处;
注:限于作者水平有限,文中有不对的地方还请指教
前言:现在是智能电子时代,五花八门的智能电子设备随处可见,这些电子设备如何实现各自的特色呢?硬件支持,比如智能手机,手表;那么问题来了,我们的硬件提供的服务就在那里,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需要实现那些内容呢???
- 在Java层实现一个AIDL和native Service 相对应;
- 调用ServiceManager.getService("hello.service")获取HelloService::instantiate()中注册的服务;
- 封装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
platform.png
一应俱全,把相应的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.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:应用程序访问;
网友评论