基于 MsgSender和 Binder 并发可信解耦透传框架设计篇

基于 MsgSender和 Binder 同步并发可信解耦透传框架设计篇

MsgSender是基于蓝牙Socket封装的一套API,但是手表链路有时候不稳定会造成丢包,需要上层业务自己去保证,这样上层业务需要做很多不必要操作,而且MsgSender 是异步回调接口,对于习惯Android Binder 同步RPC 颇为不习惯,而且MsgSender 有强烈的耦合关系,这里曾经在做公交卡中深有体会,为了扩展一个接口就要改四个APK,对于DMA 这种还需要系统签名,DM的微信和QQ支付需要使用正式签名!由于第三方提供的APK有严格的签名验证,所以在手表端就不能使用DMA 直接bind 第三方apk获取服务! share uid 问题 确定不了包名,所以就存在代理端,调试颇为不方便,耦合性太高.简直是痛不欲生!为了达到iOS和安卓的通用性,用JCE来打包数据,对于习惯了AIDL的我颇为不习惯.针对MsgSender问题,本文基于MsgSender封装一套API 供业务使用.像安卓使用别的进程的Service一样,也是基于AIDL的,针对iOS和安卓都需要的业务,可以在参数中直接传byte数组这样也可以达到公用的效果,不过这样牺牲了AIDL的部分数据打包和解包功能.尤其是注册远程CallBack的功能.

- 同步: 是这套API是同步接口,想android中使用Service一样,如果Service里面有耗时操作,客户端线程休眠等待.所以要关注ANR情况.
- 并发: 这套接口是支持多客户端多线程并发访问,基于Binder多线程的特性,理论上支持16线程并发访问.由于蓝牙是小水管!消息排队出去,这里只是提供了多客户端调用的能力!
- 可信: 这是一套超时机制接口,基于蓝牙不稳定的情况,上层业务其实是不care蓝牙的状态,也有可能中途蓝牙偶然断开丢包的情况,该接口如果调用成功返回没有异常,则本次通讯一定可靠,如果蓝牙一直断开根据实际情况,可以返回超时异常或者蓝牙断开异常,客户端在异常处理分支catch 异常就ok
- 安全: 由于是基于binder 可以根据uid pid 校验访问者权限或者签名而达到安全的作用.
- 解耦: 业务不需要在耦合在DMA或者DM,也就是以后不用改DM或者DMA来增加业务,DM和DMA的只是起到搬运的作用

[TOC]

这里我总共实现了三套通用RPC框架,前两套原理基本类似,但是觉得可能引起DM和DMA的 binder线程池过度忙碌,造成DM的所有binder调用的阻塞等待,所以实现第三套方案,占用客户端工作线程的异步回调机制.

  • 基于AIDL进行数据的序列化和反序列化,基于命令字分发业务,基于Biner实现的跨进程分发RPC框架(由于基于AIDL进行打包解包,对iOS可能不太友好)
  • 基于JCE进行数据的序列化和反序列化,基于命令字分发业务,基于Biner实现的跨进程分发RPC框架(基于JCE所以iOS也能使用,但是和上一种方案,可能存在部分缺陷,)
  • 基于JCE进行数据的序列化和反序列化,基于命令字分发业务,基于Biner实现的跨进程分发RPC框架(这种主要依靠CallBack进行分发,只会占用一个binder线程,而且还能很快的返回回收)

一二两种方案和第三种最大的区别是,一二种方案挂起代理服务端的binder线程池,造成代理端binder线程全部忙碌,第三种设计请求和回调的时候只会占用一个binder线程,而且很快的返回,而且是基于异步回调机制,对接iOS现有命令字分发框架更好对接,第三种方案是借鉴映理和jiulingli思路进行实现扩展.

基于JCE binder跨进程的异步回调RPC框架.

这里只讲使用具体的通过代码机型分析

  • 1.定义RPC通讯Interface
  • 服务端实现Interface
  • 客户端获取代理服务进行请求数据封包

客户端服务端通用接口定义

这里有一种限制是接口如果有方法参数,方法参数必须是继承JceStruct(现阶段暂时不支持传入null),返回是Call来实现同步异步调用.(Call里面的泛型可以是Void或者是JceStruct, 来返回服务端返回数据)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface RPCCall {
@OneAnnotation(methodId=1,timeOut=1000)
Call<classes> getStudent(student student) ;
@OneAnnotation(methodId=2,timeOut=1000)
Call<price> getPrice(desktop desktop) ;
@OneAnnotation(methodId=3,timeOut=10000)
Call<Void> TestOutPutVoid(desktop desktop) ;
@OneAnnotation(methodId=4,timeOut=10000)
Call<classes> TestInPutVoid() ;
@OneAnnotation(methodId=5,timeOut=10000)
Call<Void> TestInPutOutVoid() ;
}

2 服务端实现接口和注册服务.

  • 服务端要继承BasePipeService
  • 并且实现服务接口 重写抽象方法.
  • 服务的Service注册(以前服务端必须在一个新的进程才可以,现在完全没有进程限制,主要是考虑部分业务可能和DM同一个进程,这里原理讲解时候具体分析)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class RemoteService extends BasePipeService implements RPCCall {
@Override
protected Class<?> getInterface() {
return RPCCall.class;
}
@Override
protected Object getObject() {
return this;
}
@Override
public Call<classes> getStudent(student student) {
Log.d("fine", student.toString());
classes cls = new classes();
cls.iMajor = 1;
cls.sMajorName = "你好啊";
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return new CallWrapper<classes>(cls);
}
@Override
public Call<price> getPrice(desktop desktop) {
// TODO Auto-generated method stub
Log.d("fine", desktop.toString());
price pr = new price();
pr.iPrice = 1000;
pr.sCPU = "i7";
return new CallWrapper<price>(pr);
}
@Override
public Call<Void> TestOutPutVoid(desktop desktop) {
Log.d("fine", "TestOutPutVoid" + desktop.toString());
return new CallWrapper<Void>(null);
}
@Override
public Call<classes> TestInPutVoid() {
// TODO Auto-generated method stub
Log.d("fine", "TestInPutVoid");
classes cls = new classes();
cls.iMajor = 1;
cls.sMajorName = "你好啊";
return new CallWrapper<classes>(cls);
}
@Override
public Call<Void> TestInPutOutVoid() {
// TODO Auto-generated method stub
Log.d("fine", "TestInPutOutVoid");
return null;
}
}

注册服务

1
2
3
4
5
<service android:name="com.pacewear.jceserver.RemoteService" >
<intent-filter >
<action android:name="com.pacewear.jceserver.action"/>
</intent-filter>
</service>

客户端调用远程服务

客户端只要使用其实和使用Android Service一样,这里我专门封装了一下

1
2
3
4
5
6
messgeApi = new PaceMessageAPI.Builder(this.getApplicationContext())
.setAction("com.pacewear.jceserver.action")
.setInterface(RPCCall.class)
.setPackage("com.pacewear.jceserver")
.build();
rpccall = (RPCCall) messgeApi.getInterface();

接口的同步异步调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
student s = new student(12, "fine", 165);
try {
classes p = rpccall.getStudent(s).execute().body();
Toast.makeText(getApplicationContext(), "sync Call return: " + p.toString(), Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "sync Call return: Exception:" + e.getMessage(), Toast.LENGTH_LONG).show();
}
Call<classes> calls = rpccall.getStudent(s);
calls.enqueue(new Callback<classes>() {
@Override
public void onResponse(Call<classes> call, Response<classes> response) {
if (response.isSuccessful()) {
Toast.makeText(getApplicationContext(), "async getStudent Call return: " + response.body().toString(), Toast.LENGTH_LONG).show();
}
}
@Override
public void onFailure(Call<classes> call, Throwable t) {
Toast.makeText(getApplicationContext(), "sync getStudent Call return: Exception:" + t.getMessage(), Toast.LENGTH_LONG).show();
}
});
break;

客户端异常处理

同步调用是通过throw异常给客户端调用,异步调用是使用onFailure 回调给客户端异常

  • 超时异常
  • 服务端挂掉异常
  • 蓝牙断开异常
  • 服务端和客户端接口没对齐异常
  • 服务端没找到异常

独立通讯

独立通讯只需要把业务代码搬到手表端.客户端只需调用一个setIndepent(true)就可以无缝对接

1
2
3
4
5
6
messgeApi = new PaceMessageAPI.Builder(this.getApplicationContext())
.setAction("com.pacewear.jceserver.action")
.setInterface(RPCCall.class)
.setPackage("com.pacewear.jceserver")
.setIndepent(true) ;
.build();

基于AIDL的进行数据打包和接包RPC框架实现.

2 总体框架

2 代理服务框架

3 相关数据结构

基于 MsgSender和 Binder 同步并发可信解耦透传框架实现篇

消息解耦,利用现有的AIDL工具自动生成自动打包数据解包数据的帮助类.像使用本手机别进程的Service一样.同步RPC调用.基本上和使用安卓Service用法一样.通讯服务独自一个进程,这样DM挂掉也能保证透传等通讯能力!

1 消息解耦设计思路

消息解耦就是我们以后不需要在改动DM或者DMA,增删接口只要保证自己client端和Server端一致就ok,DM和DMA只是搬运工! 不过也会小小偷窥下数据,不然是不能找到对端Server.进行通讯,包括Binder其实也是这样实现的.也会偷窥部分数据!这是协议约定的,所以我也有约定.比如两个关键字,和超时参数.

1.1 NFC HCE 使用

这里的消息解耦之的是以后增加业务并不需要改DMA,或者DM DM和DMA只是一个搬运工,能准确帮你帮数据传到对端,并把返回数据返回来.所以这里在DM端和DMA端提供一个代理服务来路由请求和反馈!,这里借鉴了NFC HCE相关的实现.比如你对某一类业务Care 你只需要在你的应用中配置 ACTION和AID List 并继承提供的Service即可!

1
2
3
4
5
6
7
8
9
10
11
12
<service
android:name=".MyHostApduService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE" >
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
</intent-filter>
<meta-data
android:name="android.nfc.cardemulation.host_apdu_service"
android:resource="@xml/apduservice" />
</service>

几个关键点:

服务授权:android.permission.BIND_NFC_SERVICE
initent-filter:android.nfc.cardemulation.action.HOST_APDU_SERVICE
meta-data:指定服务的细节,见apduservice.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="servicedesc"
android:requireDeviceUnlock="false" >
<aid-group
android:category="other"
android:description="aiddescription" >
<aid-filter android:name="F0010203040506" />
<aid-filter android:name="F0394148148100" />
</aid-group>
</host-apdu-service>

1.2 bind Remote 服务

我这里为了遵循原生,和普通Service一样 配置Action即可!

1
2
3
4
5
6
7
8
<service
android:name="com.tenect.testbinderserver.BTServer"
android:label="BTServer" >
<intent-filter>
<action android:name="com.tenect.testbinderserver.ACTION"/>
</intent-filter>
</service>

也许你会好奇我是怎么找到这个Service了 其实最终是DMA或者DM去bind 这个Service, 也许更好奇他们是怎么找到了,其实很简单!是客户端你自己告诉代理端的,安卓在bindService里面会传入intent ,intent需要setAction 和pacakge.只不过我这里不需要传入了,我在AIDL里面增加两个各关键字.

action com.tenect.testbinderserver.ACTION ;
remotepackage com.tenect.testbinderserver ;
1
2
3
4
5
6
7
8
9
10
11
12
action com.tenect.testbinderserver.ACTION ;
remotepackage com.tenect.testbinderserver ;
package com.tenect.testbinderserver;
interface IMsgSenderTest {
int onSendData( int size , String test ) ;
int getCPLC( ) ;
}

AIDL工具已经被我改了,兼容之前的功能.只是扩展了这两个关键子,可以不写action 和 remotepackage就是一般的AIDL 如果写必须在前面,这两个关键字顺序的无要求,加了AIDL里面会自动为我们定义的接口加上超时字段,这里借鉴了Xposed框架,他需要传入不定长参数,但是callback是最后一个,我也这样定义.这样在代理服务端就可以挂起客户端线程,实现同步等待,不同的业务有不同的超时,所以业务端根据需求自己定义.

客户端用的时候需要bind 不用的时候unbind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public <T extends IInterface> Boolean bindRemoteService(Context context,
final RemoteServiceConnection con, final Class<T> aidlClass) {
if (context.getApplicationContext() == null) {
throw new RuntimeException("ApplicationContext is null ");
}
Intent intent = new Intent();
String proxyPakage = isPkgInstalled(context, DM_PACKAGE) ? DM_PACKAGE : DMA_PACKAGE;
intent.setPackage(proxyPakage);
intent.setAction(PROXY_SERVER_ACTION);
return context.getApplicationContext().bindService(intent, new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
con.onServiceDisconnected(name);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
String decrptor = getDescriptor(aidlClass, service);
// realBinderRemoteService(service, decrptor);
con.onServiceConnected(name, service);
con.setmRemoteDescriptor(decrptor);
con.setmLoaclConection(this);
con.setmProxyBinder(service);
}
}, Context.BIND_AUTO_CREATE);
}

unbind接口 这样最终会是Remote端 回调onDestroy

1
2
3
4
5
6
7
public void unBindRemoteService(Context context, final RemoteServiceConnection rCon) {
if (context.getApplicationContext() == null) {
throw new RuntimeException("ApplicationContext is null ");
}
realUnBinderRemoteService(rCon.getmProxyBinder(), rCon.getmRemoteDescriptor());
context.getApplicationContext().unbindService(rCon.getmLoaclConection());
}

1.3 AIDL 改动点

为了改AIDL和特意去学了一天lex和yacc 据说很多编译器都是基于这个,好强悍!可是无奈我只会C ,C++只会读不会写!所以这里多亏永龙的帮助,哇嘎嘎!搞定,还兼容之前的模式! 最后感叹下lex yacc 语法解析语意解析好强悍!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
From ba57e2ae29995e5fdcd6b54cdc6ba16f61abd229 Mon Sep 17 00:00:00 2001
From: Fine <1002657321@qq.com>
Date: Wed, 4 Jan 2017 10:31:07 +0800
Subject: [PATCH] add action remotepackge support ! by fine
---
aidl.cpp | 4 +++-
aidl_language.h | 3 +++
aidl_language_l.l | 8 ++++++++
aidl_language_y.y | 47 +++++++++++++++++++++++++++++++++++++++++++----
generate_java_binder.cpp | 23 ++++++++++++++++++++++-
5 files changed, 79 insertions(+), 6 deletions(-)
diff --git a/aidl.cpp b/aidl.cpp
index 45dd23b..1f976f6 100644
--- a/aidl.cpp
+++ b/aidl.cpp
@@ -124,7 +124,7 @@ main_import_parsed(buffer_type* statement)
static ParserCallbacks g_mainCallbacks = {
&main_document_parsed,
- &main_import_parsed
+ &main_import_parsed
};
char*
@@ -166,6 +166,8 @@ static ParserCallbacks g_importCallbacks = {
&import_import_parsed
};
+
+
// ==========================================================
static int
check_filename(const char* filename, const char* package, buffer_type* name)
diff --git a/aidl_language.h b/aidl_language.hxiumian
index de1370c..92e702e 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -1,7 +1,10 @@
#ifndef DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H
#define DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H
+#define NULL 0
+extern char * gAction ;
+extern char * gPakcage ;
typedef enum {
NO_EXTRA_TEXT = 0,
SHORT_COMMENT,
diff --git a/aidl_language_l.l b/aidl_language_l.l
index 3d33e7a..91e2150 100644
--- a/aidl_language_l.l
+++ b/aidl_language_l.l
@@ -59,6 +59,14 @@ idvalue (0|[1-9][0-9]*)
SET_BUFFER(IMPORT);
return IMPORT;
}
+^{whitespace}?action{whitespace}[^ \t\r\n]+{whitespace}?; {
+ SET_BUFFER(ACTION);
+ return ACTION;
+ }
+^{whitespace}?remotepackage{whitespace}[^ \t\r\n]+{whitespace}?; {
+ SET_BUFFER(RPPACKAGE);
+ return RPPACKAGE;
+ }
^{whitespace}?package{whitespace}[^ \t\r\n]+{whitespace}?; {
do_package_statement(yytext);
SET_BUFFER(PACKAGE);
diff --git a/aidl_language_y.y b/aidl_language_y.y
index 9b40d28..d0a4f85 100644
--- a/aidl_language_y.y
+++ b/aidl_language_y.y
@@ -7,6 +7,7 @@
int yyerror(char* errstr);
int yylex(void);
extern int yylineno;
+char* parse_pakage_statement(const char* text) ;
static int count_brackets(const char*);
@@ -26,6 +27,8 @@ static int count_brackets(const char*);
%token OUT
%token INOUT
%token ONEWAY
+%token ACTION
+%token RPPACKAGE
%%
document:
@@ -34,11 +37,19 @@ document:
;
headers:
- package { }
- | imports { }
- | package imports xiangjie { }
+ intent package { }
+ | intent imports { }
+ | intent package imports { }
+ ;
+
+intent:
+ | RPPACKAGE ACTION { printf("remote package %s \n",parse_pakage_statement((&($1.buffer))->data) ); printf("remote action %s \n",parse_pakage_statement((&($2.buffer))->data) );
+ gAction = parse_pakage_statement((&($2.buffer))->data) ; gPakcage = parse_pakage_statement((&($1.buffer))->data);
+ }
+ | ACTION RPPACKAGE { printf("remote package %s \n",parse_pakage_statement((&($2.buffer))->data) ); printf("remote action %s \n",parse_pakage_statement((&($1.buffer))->data) );
+ gAction = parse_pakage_statement((&($1.buffer))->data) ; gPakcage = parse_pakage_statement((&($2.buffer))->data);
+ }
;
-
package:
PACKAGE { }
;
@@ -371,3 +382,31 @@ static int count_brackets(const char* s)
}
return n;
}
+
+
+char* parse_pakage_statement(const char* text)
+{
+ const char* end;
+ int len;
+
+ while (isspace(*text)) {
+ text++;
+ } try {
+ while (!isspace(*text)) {
+ text++;
+ }
+ while (isspace(*text)) {
+ text++;
+ }
+ end = text;
+ while (!isspace(*end) && *end != ';') {
+ end++;
+ }
+ len = end-text;
+
+ char* rv = (char*)malloc(len+1);
+ memcpy(rv, text, len);
+ rv[len] = '\0';
+
+ return rv;
+}
\ No newline at end of file
diff --git a/generate_java_binder.cpp b/generate_java_binder.cpp
index f291ceb..2aa515e 100644
--- a/generate_java_binder.cpp
+++ b/generate_java_binder.cpp
@@ -4,13 +4,20 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include "aidl_language.h"
+#include <iostream>
+using namespace std;
+char * gAction = NULL ;
+ char * gPakcage = NULL;
// =================================================
class StubClass : public Class
{
public:
StubClass(Type* type, Type* interfaceType);
virtual ~StubClass();
+ //extern char * gAction ;
+ //extern char * gPakcage ;
Variable* transact_code;
Variable* transact_data;
@@ -34,7 +41,21 @@ StubClass::StubClass(Type* type, Type* interfaceType)
// descriptor
Field* descriptor = new Field(STATIC | FINAL | PRIVATE,
new Variable(STRING_TYPE, "DESCRIPTOR"));
- descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
+
+ if( gAction == NULL && gPakcage == NULL)
+ {
+ descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
+ }
+ else{
+ string tmpPackage;
+ tmpPackage.assign(gPakcage, strlen(gPakcage));
+ cout << __FUNCTION__ << __LINE__ << " tmpPackage = " << tmpPackage << ", gPakcage:" << gPakcage << ", len = " << strlen(gPakcage) << endl;
+ string tmpAction;
+ tmpAction.assign(gAction, strlen(gAction));
+ cout << __FUNCTION__ << __LINE__ << " tmpAction = " << tmpAction << ", gAction:" << gAction << ", len = " << strlen(gAction) << endl;
+
+ descriptor->value = "\"" + interfaceType->QualifiedName() + ":" + tmpPackage + ":"+ tmpAction + "\"";
+ }
this->elements.push_back(descriptor);
// ctor
--
1.9.1

这里为什么这么改,在代理服务章节详解!

2 代理服务实现

代理服务是一个同时运行在客户端和服务端的Service,其实怎么说现在DM是单进程的,很容易由于占用太多资源被不知不觉的Kill,而且如果DM挂掉,透传API完全失效,所以更好做法是多进程,只要服务不挂透传API就没问题!

2.1 代理端拦截客户端请求

其实也不是请求拦截,其实我的API 根本其实binder的是我代理的服务,只不过在我的服务里面,我只是对数据进行打包通过MsgSender传到对端,并挂起当前线程,以达到挂起客户端线程,达到同步操作.这里我自己手动改了AIDL生成的java文件,不然是无法拦截到数据的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws android.os.RemoteException {
switch (code) {
default: {
data.readInt();
String destDesciptor = data.readString();
data.setDataPosition(0);
data.enforceInterface(destDesciptor);
Log.d("fine", "send data to remote: " + destDesciptor);
data.setDataPosition(0);
this.onSendData(code, flags, destDesciptor, data, reply);
return true;
}
}
// return super.onTransact(code, data, reply, flags);
}

没改动的工具生成方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@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_onSendData:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
java.lang.String _arg1;
_arg1 = data.readString();
int _arg2;
_arg2 = data.readInt();
int _result = this.onSendData(_arg0, _arg1, _arg2);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_getCPLC:
{
data.enforceInterface(DESCRIPTOR);
int _result = this.getCPLC();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

自己观察AIDL工具你会发现,这里不管code是啥 统统回调一个接口! 就是onSendData 里面传进去code flags ,data ,reply 其实最重要的是code 和data这个parcel 这些数据会通过蓝牙送到对端! 这里特别注意到这些操作,这个才是关键,要通过校验必须这个搞!

1
2
3
4
5
data.readInt();
String destDesciptor = data.readString();
data.setDataPosition(0);
data.enforceInterface(destDesciptor);
Log.d("fine", "send data to remote: " + destDesciptor);

至于为啥要先readint 在readyString 这个是通过源码得出,这里因为client放的aidl文件和代理服务端的用的不一样!如果不这么搞就会抛出异常给client端 没记错是”interface case excption!!! “ 这里我只是读出自己校验自己肯定能过!

client DESCRIPTOR 字段

1
private static final java.lang.String DESCRIPTOR = "com.tenect.testbinderserver.IMsgSenderTest:com.tenect.testbinderserver:com.tenect.testbinderserver.ACTION";

代理服务端

1
private static final java.lang.String DESCRIPTOR = "IMsgSender";

这里工具生成肯定都一样由于我放的aidl文件不一样,所以这里要自己写打包解包数据方法!

2.2 代理服务端数据中转同步操作

服务端只是把数据封包送到对端服务端!去bind 对端Service!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Override
public void onSendData(int code, int flag, final String decriptor,
Parcel data, Parcel reply) throws RemoteException {
int timeout = ParcelUtil.getTimeOutSetting(data);
// Log.d("fine", "time out:" + timeout);
byte[] rpcdata = ParcelUtil.wrapRemoteReqData(code, flag, data);
// Log.d(TAG, "send data parcel : ::: --->" + ParcelUtil.parcelToBytes(data));
RPCInvokeInfor rpcInfor = new RPCInvokeInfor();
rpcInfor.setmClientRPCCode(code);
rpcInfor.setmClientUid(Binder.getCallingUid());
rpcInfor.setmClientPid(Binder.getCallingPid());
rpcInfor.setmClientLock(new Object());
rpcInfor.setmTimeOut(timeout);
rpcInfor.setmRPCData(rpcdata);
postRequstToQueue(rpcInfor);
switch (code) {
case BIND_SERVICE:
Log.d("fine", "BIND_SERVICE");
reply.writeNoException();
return;
case UNBIND_SERVICE:
Log.d("fine", "UNBIND_SERVICE");
reply.writeNoException();
return;
default:
break;
}
synchronized (rpcInfor.getmClientLock()) {
try {
Log.d(TAG, "rpc call block here . rpcinfor : " + rpcInfor.toString());
rpcInfor.getmClientLock().wait(timeout);
mLocalClientReqSucess.remove(rpcInfor.getlReqId());
if (rpcInfor.getmReplyData() != null) {
ParcelUtil.copyParcelToParcel(rpcInfor.getmReplyData(), reply);
} else {
Log.d(TAG, "send time out msg to clinet !" + rpcInfor.getlReqId());
ParcelUtil.writeTimeOutException(reply, "msg time out !");
}
Log.d(TAG, "rpc call wake up ! remove IReqId" + rpcInfor.getlReqId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

主要关注数据打包了 发送,以及挂起当前客户端线程操作!

1
2
int timeout = ParcelUtil.getTimeOutSetting(data);
byte[] rpcdata = ParcelUtil.wrapRemoteReqData(code, flag, data);

发送数据到对端

1
postRequstToQueue(rpcInfor);

由于蓝牙小管道所以有一个线程在读阻塞队列发送数据到对端,没数据就休眠!

挂起服务端超时等待,这里只是初步,没考虑太多情况,其实可以加入蓝牙状态异常等

1
2
3
4
5
6
7
8
9
10
rpcInfor.getmClientLock().wait(timeout);
mLocalClientReqSucess.remove(rpcInfor.getlReqId());
if (rpcInfor.getmReplyData() != null) {
ParcelUtil.copyParcelToParcel(rpcInfor.getmReplyData(), reply);
} else {
Log.d(TAG, "send time out msg to clinet !" + rpcInfor.getlReqId());
ParcelUtil.writeTimeOutException(reply, "msg time out !");
}
Log.d(TAG, "rpc call wake up ! remove IReqId" + rpcInfor.getlReqId())

2.3 对端服务bind Service 以及调用代理接口!

这里数据到达对端,对端要塞到对应的服务,这里涉及bind对端Service和调用对端接口的过程

找到对端服务,其实就一个bindService的过程!
记得我们增加两个aidl关键字! 对 就是pacakge 和action 其实这个数据最终会反馈到生成类的

1
private static final java.lang.String DESCRIPTOR = "com.tenect.testbinderserver.IMsgSenderTest:com.tenect.testbinderserver:com.tenect.testbinderserver.ACTION";

:分开 来获取Aciton和Package !
接下来就看看对端代理服务的实现数据回塞的过程!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
protected void sendMsgToLocalService() {
final RequstParam req;
try {
req = mDispatcherReqQueue.take();
} catch (InterruptedException e1) {
e1.printStackTrace();
return;
}
bindLocalServiceIfNeeded(req);
invokeRPC(req);
}
```
由于分发要模拟请求的并发情况,所以会有多个线程来读阻塞队列!理论上可以搞16个分发线程!
``` java
mFixedThreadPool = Executors.newFixedThreadPool(POOLSIZE);
for (int i = 0; i < POOLSIZE - 3; i++) {
// dispatch remote request
mFixedThreadPool.execute(new Runnable() {
@Override
public void run() {
while (true) {
sendMsgToLocalService();
}
}
});
}
```
``` java
private void bindLocalServiceIfNeeded(RequstParam req) {
if (mLocalServer.get(req.getDescriptor()) != null) {
Log.d(TAG, "already bind service decriptor :" + req.getDescriptor());
return;
}
String[] strings = req.getDescriptor().split(":");
Intent intent = new Intent();
String pacakge = strings[1];
String action = strings[2];
intent.setAction(action);
intent.setPackage(pacakge);
Log.d(TAG, " bind local server :" + "package:" + pacakge + " action :" + action);
LocalServiceConnection conn = new LocalServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "Local Server Die !!!!!" + name.toString());
if (this.getmServiceDescriptor() != null) {
Log.d(TAG, "remove local server so that can rebind again ! " + this.getmServiceDescriptor());
mLocalServer.remove(this.getmServiceDescriptor());
}
}
};
conn.addPenddingRequst(req);
conn.setmServiceDescriptor(req.getDescriptor());
conn.setmGlobalDispatcherQueue(mDispatcherReqQueue);
mLocalServer.put(req.getDescriptor(), conn);
boolean success = bindService(intent, conn, Context.BIND_AUTO_CREATE);
if (!success) {
mLocalServer.remove(req.getDescriptor());
}
}
```
``` java
private void invokeRPC(RequstParam req) {
IBinder remote = mLocalServer.get(req.getDescriptor()).getmRemote();
if (remote != null) {
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
try {
req.getData().setDataPosition(0);
remote.transact(req.getCode(), req.getData(), _reply, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
RPCRspInfor info = new RPCRspInfor();
info.setIReqId(req.getIReqId());
info.setmReplyData(ParcelUtil.wrapRemoteRspData(req.getIReqId(), _reply));
mWriteRspuesteQueue.offer(info);
} finally {
_reply.recycle();
req.getData().recycle();
}
}
}

2.4 服务返回数据的回传过程

1
2
3
4
RPCRspInfor info = new RPCRspInfor();
info.setIReqId(req.getIReqId());
info.setmReplyData(ParcelUtil.wrapRemoteRspData(req.getIReqId(), _reply));
mWriteRspuesteQueue.offer(info);

其实也是有一个阻塞队列,有一个线程读取来用MsgSender回馈的过程!

2.5 client端代理服务的返回回传client端过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
protected void dispatcherRsp() {
final RspParm rsp;
final RPCInvokeInfor _info;
try {
rsp = mDispatcherRspQueue.take();
} catch (InterruptedException e1) {
e1.printStackTrace();
return;
}
_info = mLocalClientReqSucess.get(rsp.getlReqId());
if (_info == null) {
Log.d(TAG, "lReqId msg time out !!!!!");
return;
}
synchronized (_info.getmClientLock()) {
_info.setmReplyData(rsp.getReply());
_info.getmClientLock().notify();
}
}
```
只不过这里notify 休眠的线程而已!每个请求线程都有一把属于自己的锁!不会造成死锁问题!
然后就是代理返回client端的过程了!
``` java
Log.d(TAG, "rpc call block here . rpcinfor : " + rpcInfor.toString());
rpcInfor.getmClientLock().wait(timeout);
mLocalClientReqSucess.remove(rpcInfor.getlReqId());
if (rpcInfor.getmReplyData() != null) {
ParcelUtil.copyParcelToParcel(rpcInfor.getmReplyData(), reply);
} else {
Log.d(TAG, "send time out msg to clinet !" + rpcInfor.getlReqId());
ParcelUtil.writeTimeOutException(reply, "msg time out !");
}
Log.d(TAG, "rpc call wake up ! remove IReqId" + rpcInfor.getlReqId());

哪里睡就哪里起来!

3 iOS 兼容方案

其实iOS兼容也可以做到,我不太熟悉iOS所以还不太清楚,一种是在AIDL接口方法里面传byte[] 这个这个jce转换成的!,只不过调用和解包的时候自己解包!这样也可以实现不改DMA.

另一种,如果不考虑效率可以写一个JCETOParcel的工具类,改aidl工具,自动生成方法,扩展协议等等!

4 使用方法

4.1 更换AIDL工具

换更改的AIDL工具 这个不用担心,和原生的兼用

4.2 编写AIDL

主要是写自己的action和 remotepackage 关键字

1
2
3
4
5
6
7
8
9
10
11
12
action com.tenect.testbinderserver.ACTION ;
remotepackage com.tenect.testbinderserver ;
package com.tenect.testbinderserver;
interface IMsgSenderTest {
int onSendData( int size , String test , int timeout) ;
int getCPLC( ) ;
}

4.3 实现服务端Service 注册Service

这个不多说,和一般Service一样该怎么写就怎么写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind ");
return new IMsgSenderTest.Stub() {
@Override
public int onSendData(int size, String test, int timeout) throws RemoteException {
Log.d(TAG, "onSendData size: " + size + " test String :" + test + " timeout: " + timeout);
return size;
}
@Override
public int getCPLC() throws RemoteException {
// TODO Auto-generated method stub
return 123;
}
};
}

4.4 绑定解绑远程服务

使用其实和使用Service基本一致,先bindService和unbindService,只不过这里用封装过的API

1
2
RemoteServiceHelper.getInstance().unBindRemoteService(this, mRemoteConnection);
RemoteServiceHelper.getInstance().bindRemoteService(this, mRemoteConnection, IMsgSenderTest.class);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private RemoteServiceConnection mRemoteConnection = new RemoteServiceConnection() {
@Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
mRemote = IMsgSenderTest.Stub.asInterface(arg1);
Log.d("fine", "componentName :" + arg0.getPackageName() + " " + arg0.getClassName());
Log.d("fine", "bind remote serice sucess");
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
}
};
```
``` xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tenect.testbinderserver"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="21" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<service
android:name="com.tenect.testbinderserver.BTServer"
android:label="BTServer" >
<intent-filter>
<action android:name="com.tenect.testbinderserver.ACTION"/>
</intent-filter>
</service>
</application>
</manifest>

4.5 client端异常处理

其实就是处理超时异常,蓝牙异常,以及对端抛出的运行时异常等等!运行一样就是对端服务出问题,自己处理,这里直接抛出RuntimeExcption!

1
2
3
4
5
6
7
8
case R.id.id_send_data:
try {
int ret = mRemote.onSendData(1, "test !!!!", 1000*10);
Log.d("fine", "client receive return " + ret);
} catch (Exception e) {
e.printStackTrace();
ParcelUtil.getExceptionType(e) ;
}