一切都是为了满足需求
最近应公司要求写个功能模块,实现测试人员使用公司的工具App能开启日志,查看配置参数,修改部分配置。
功能做完了,现在写个总结!
思路
1.app对app进行通信,那就是AIDL通信了,绑定服务
2.服务暴露出去,如何实现通信安全,采用的是数字信封方式
知识准备
AIDL通信
Android Interface definition language(aidl,android接口定义语言),其目的实现跨进程的调用。
每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。每个进程之间都你不知我,我不知你,就像是隔江相望的两座小岛一样,都在同一个世界里,但又各自有着自己的世界。而AIDL,就是两座小岛之间沟通的桥梁。说白了就是类似java的RPC远程过程调用。
总之,通过这门语言,我们可以愉快的在一个进程访问另一个进程的数据,甚至调用它的一些方法,当然,只能是特定的方法。
其实AIDL这门语言非常的简单,主要有下面这些点:
文件类型:
用AIDL书写的文件的后缀是 .aidl
数据类型:
默认支持的数据类型包括:
- Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char
- Bundle 类型
- String CharSequence类型
- List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型
- Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的
- 定向tag:in /out /inout --> in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通(开销大慎用)
- Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in
- 除了这些类型之外的数据类型,可以定义如Book.java的Book.aidl这样的javaBean,但我一般直接用json通讯,最实用(需求时刻变)。
注:所有的非默认支持数据类型必须通过AIDL文件定义才能被使用。
使用:
编写好接口AIDL文件,编译一下,开发工具会自动帮我们生成对应的java文件,只需要在服务端实例化然后实现定义的抽象方法;
数字信封
为了保证信息传送的真实性、完整性和不可否认性,需要对要传送的信息进行数字加密和数字签名。其传送过程如下:
发送者A:
A准备要传送的数字信息(明文)
A对数字信息(明文)进行MD5运算,得到一信息摘要。
A用自己的【私钥(SK)】对信息摘要进行加密得到A的数字签名,并将其附在数字信息上。(数字签名)
A随机产生一个加密钥(AES密钥),并用此密钥对要发送的信息(明文)进行加密,形成密文。(对称加密)
A用B的【公钥(PK)】对刚才随机产生的加密密钥进行加密,将加密后的AES密钥连同密文一起传送给B。(数字信封)
接收者B:
B收到A传送过来的密文和加过密的AES密钥,先用自己的私钥(SK)对加密的AES密钥进行解密,得到AES密钥。
B然后用AES密钥对受到的密文进行解密,得到明文的数字信息,然后将AES密钥抛弃(即AES密钥作废)。
B用A的公钥(PK)对A的数字签名进行解密,得到信息摘要。
B用相同的MD5算法对收到的明文再进行一次MD5运算,得到一个新的信息摘要。
B将收到的信息摘要和新生成的信息摘要进行比较,如果一致,说明收到的信息没有被修改过。
Android Service
- Service分为本地服务(LocalService)和远程服务(RemoteService),根据实际的需求使用不同的类型,本地服务同App本身是同一个进程,远程服务需要在启动时配置android:process字符串,使用的一个独立进程,注意appliction的多次初始化问题,远程服务必须使用AIDL通讯,或者广播(不建议使用);
- 启动方式:startService 启动的服务:主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService;
- 启动方式:bindService 启动的服务:该方法启动的服务可以进行通信。停止服务使用unbindService;
- 启动方式:startService 同时也 bindService 启动的服务:停止服务应同时使用stepService与unbindService
注:startService 在android P 以上的版本中被限制了,无法长时间工作。注意适配
代码实现
1.编写接口文件AIDL
package com.test.apptool;
//注意导包
import android.os.Bundle;
interface IAppService {
//客户端控制服务端开启日志
void openLog(in Bundle enable); //注意 in的使用
}
重新编译一下工具会帮我们自动生成对应的java文件。
这里入参使用的Bundle而不是基本类型是因为我们要加密,根据上面数字信封的介绍,每一次的通信数据都分有:key(AES秘钥) title(内容摘要) body(加密数据) 三个参数
2.服务端Service
public class AppService extends Service {
private IAppService.Stub stubBinder = new IAppService.Stub() {
private IAppService.Stub stubBinder = new IAppService.Stub() {
@Override
public void openLog(Bundle enableBundle) throws RemoteException {
//将接受到的数据解密
String enable= EncodeUtil.receiverMsg(
//将bundle转为Map
EncodeUtil.bundleToMap(isEnableBundle),
//服务端私钥
AppConstant.SERVIC_PRIVATE_KEY,
//客户端公钥
AppConstant.CLIENT_PUBLIC_KEY);
log("服务端接受到客户端请求开启日志:" + enable);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stubBinder;
}
}
代码很简单,实例化一个Binder给onBind方法,IAppService.Stub就是通过编写的AIDL文件系统自动生成的类。
在清单文件中注册一下service:
<application>
<service
android:name=".AppService"
android:enabled="true"
android:exported="true"
android:label="AppService"
android:process=":appservice">
<intent-filter>
<action android:name="com.test.AppService" />
</intent-filter>
</service>
这样服务端就准备好了。
3.客户端Activity
public class AppAct extends Activity {
//是否已经连接成功的标识
private boolean remoteConnection = false;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
try {
//生成接口实例
appServiceImpel = IAppService.Stub.asInterface(iBinder);
if (appServiceImpel != null) {
log("客户端获取serviceImple成功");
remoteConnection = true;
} else {
log("appToolServiceImple为空");
}
} catch (Exception e) {
log("客户端获取serviceImple失败" + e.getMessage());
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
log("客户端获取onServiceDisconnected");
remoteConnection = false;
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bindRemoteService();
}
protected void bindRemoteService() {
Intent intent = new Intent();
intent.setAction("com.test.AppService");
intent.setComponent(new ComponentName("包名", "类全面"));
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
protected void openLog(boolean openLog) {
if (remoteConnection) {
try {
//加密
Map<String, String> map = EncodeUtil.sendData(
openLog + "", CLIENT_PRIVSTE_KEY, SERVICE_PUBLIC_KEY);
//使用远程服务接口实例调用开启日志的方法
appServiceImpel .openLogTag(EncodeUtil.mapToBundle(map));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
客户端也是很简单,绑定远程服务,实现connection。
使用IAppService.Stub.asInterface生成远程服务接口实例appServiceImpel
通过实例实现RPC调用。
OK 以上核心代码就写完了。其实没有多少东西,主要是想做个总结;