Android 远程服务 实现动态修改配置

一切都是为了满足需求

最近应公司要求写个功能模块,实现测试人员使用公司的工具App能开启日志,查看配置参数,修改部分配置。
功能做完了,现在写个总结!

思路

1.app对app进行通信,那就是AIDL通信了,绑定服务
2.服务暴露出去,如何实现通信安全,采用的是数字信封方式

知识准备

AIDL通信

Android Interface definition language(aidl,android接口定义语言),其目的实现跨进程的调用。
每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。每个进程之间都你不知我,我不知你,就像是隔江相望的两座小岛一样,都在同一个世界里,但又各自有着自己的世界。而AIDL,就是两座小岛之间沟通的桥梁。说白了就是类似java的RPC远程过程调用。
总之,通过这门语言,我们可以愉快的在一个进程访问另一个进程的数据,甚至调用它的一些方法,当然,只能是特定的方法。

其实AIDL这门语言非常的简单,主要有下面这些点:

文件类型:

用AIDL书写的文件的后缀是 .aidl

数据类型:

默认支持的数据类型包括:

  1. Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char
  2. Bundle 类型
  3. String CharSequence类型
  4. List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型
  5. Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的
  6. 定向tag:in /out /inout --> in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通(开销大慎用)
  7. Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in
  8. 除了这些类型之外的数据类型,可以定义如Book.java的Book.aidl这样的javaBean,但我一般直接用json通讯,最实用(需求时刻变)。

注:所有的非默认支持数据类型必须通过AIDL文件定义才能被使用。

使用:

编写好接口AIDL文件,编译一下,开发工具会自动帮我们生成对应的java文件,只需要在服务端实例化然后实现定义的抽象方法;

数字信封

为了保证信息传送的真实性、完整性和不可否认性,需要对要传送的信息进行数字加密和数字签名。其传送过程如下:
发送者A:

  1.     A准备要传送的数字信息(明文)
    
  2.     A对数字信息(明文)进行MD5运算,得到一信息摘要。
    
  3.     A用自己的【私钥(SK)】对信息摘要进行加密得到A的数字签名,并将其附在数字信息上。(数字签名)
    
  4.     A随机产生一个加密钥(AES密钥),并用此密钥对要发送的信息(明文)进行加密,形成密文。(对称加密)
    
  5.     A用B的【公钥(PK)】对刚才随机产生的加密密钥进行加密,将加密后的AES密钥连同密文一起传送给B。(数字信封)
    

接收者B:

  1.     B收到A传送过来的密文和加过密的AES密钥,先用自己的私钥(SK)对加密的AES密钥进行解密,得到AES密钥。
    
  2.     B然后用AES密钥对受到的密文进行解密,得到明文的数字信息,然后将AES密钥抛弃(即AES密钥作废)。
    
  3.     B用A的公钥(PK)对A的数字签名进行解密,得到信息摘要。
    
  4.     B用相同的MD5算法对收到的明文再进行一次MD5运算,得到一个新的信息摘要。
    
  5.     B将收到的信息摘要和新生成的信息摘要进行比较,如果一致,说明收到的信息没有被修改过。
    

Android Service

  1. Service分为本地服务(LocalService)和远程服务(RemoteService),根据实际的需求使用不同的类型,本地服务同App本身是同一个进程,远程服务需要在启动时配置android:process字符串,使用的一个独立进程,注意appliction的多次初始化问题,远程服务必须使用AIDL通讯,或者广播(不建议使用);
  2. 启动方式:startService 启动的服务:主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService;
  3. 启动方式:bindService 启动的服务:该方法启动的服务可以进行通信。停止服务使用unbindService;
  4. 启动方式: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 以上核心代码就写完了。其实没有多少东西,主要是想做个总结;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值