版權說明:本文為 開開向前沖 原創文章,轉載請注明出處;
注:限于作者水平有限,文中有不對的地方還請指教
前言:現在是智能電子時代,五花八門的智能電子設備隨處可見,這些電子設備如何實現各自的特色呢?硬件支持,比如智能手機,手表;那么問題來了,我們的硬件提供的服務就在那里,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
一應俱全,把相應的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:應用程序訪問;