Android四大组件之Service 远程服务 通过AIDL进行进程间复杂类型数据交换
继上一篇介绍如何通过AIDL在进程间传递基础类型数据(Android 远程服务解析,通过AIDL达到进程间通信交换数据),本篇继续介绍如何传递复杂类型数据,具体代码请看(https://github.com/Mangosir/RemoteServiceMath)我们先新建一个对象Result,实现Parceable接口,因为不同的进程之间不能直接传输对象数据,只能传基础类型等数据,在上一篇...
继上一篇介绍如何通过AIDL在进程间传递基础类型数据(Android 远程服务解析,通过AIDL达到进程间通信交换数据),
本篇继续介绍如何传递复杂类型数据,具体代码请看(https://github.com/Mangosir/RemoteServiceMath)
我们先新建一个对象Result,实现Parceable接口,因为不同的进程之间不能直接传输对象数据,只能传基础类型等数据,在上一篇有解释,要想传递,就得对对象数据进行序列化和反序列化,比如现在我们要将一个对象的数据从客户端传到服务端去,我们就可以在客户端对这个对象进行序列化的操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化的操作,从而还原其中包含的数据——通过这种方式,我们就达到了在一个进程中访问另一个进程的数据的目的。
public class Result implements Parcelable {
public long addResult;
public long subResult;
public long mulResult;
public double divResult;
public Result(long addResult, long subResult, long mulResult, double divResult) {
this.addResult = addResult;
this.subResult = subResult;
this.mulResult = mulResult;
this.divResult = divResult;
}
//从Parcel对象得到数据,拆包函数
public Result(Parcel parcel) {
addResult = parcel.readLong();
subResult = parcel.readLong();
mulResult = parcel.readLong();
divResult = parcel.readDouble();
}
@Override
public int describeContents() {
return 0;
}
//顾名思义 wiiteToParcel 打包函数
//将Result类内部的数据按照特定顺序写入Parcel对象 序列化操作
//写入顺序必须与构造函数读取顺序一致
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(addResult);
dest.writeLong(subResult);
dest.writeLong(mulResult);
dest.writeDouble(divResult);
}
/**
* 默认生成的模板类的对象只支持为 in 的定向 tag,因为只有writeToParcel方法
* 如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法
* 参数是一个Parcel,用它来存储与传输数据
* 注意,此处的读值顺序应当是和writeToParcel()方法中一致的
* @param dest
*/
public void readFromParcel(Parcel dest) {
addResult = dest.readLong();
subResult = dest.readLong();
mulResult = dest.readLong();
divResult = dest.readDouble();
}
//实现静态公共字段Creator,用来使用Parcel对象构造AllResult对象
public static final Parcelable.Creator<Result> CREATOR = new Creator<Result>() {
/*
* 将序列化的数据进行反序列化操作
* */
@Override
public Result createFromParcel(Parcel parcel) {
return new Result(parcel);
}
@Override
public Result[] newArray(int size) {
return new Result[size];
}
};
}
然后新建一个result.aidl文件,声明这个result对象,方便其它aidl文件调用
//第一类AIDL文件
//这个文件的作用是引入了一个序列化对象 Result 供其他的AIDL文件使用
//注意:Result.aidl与Result.java的包名应当是一样的
//在这里声明任何非默认类型 注意parcelable是小写
parcelable Result;
然后在IMathService.aidl文件增加几个方法
//导入所需要使用的非默认支持数据类型的包
import com.mangoer.remotemathservicedemo.Result;
//第二类AIDL文件
//作用是定义方法接口
//远程服务接口的定义
interface IMathInterface {
//所有的返回值前都不需要加任何东西,不管是什么数据类型
int add(int a,int b);
Result getResult(long a, long b);
List<Result> getListResult(long a, long b);
//Map里不支持泛型,aidl编译工具会提示编译失败
// Map<Integer,Result> getMapResult(long a, long b);
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
String putResult(in Result result);
}
注意,使用非基础类型数据一定要导包
使用Map时,key或者value不能是非基础类型数据
将非基础类型数据作为入参,一定要加上定向tag
这几个操作都要做,否则aidl工具编译通不过。
然后点击AS工具顶部栏Build中的Make Project,然后在远程服务类去实现接口中定义的方法
public class MathService extends Service {
private String TAG = "MathService";
/*
* 建立IMathInterface.Stub实例,并实现IMathInterface这个AIDL文件定义的远程服务接口
*在onBind方法中将myBind返回给远程调用者
* */
private IMathInterface.Stub mBind = new IMathInterface.Stub() {
@Override
public int add(int a, int b) throws RemoteException {
Log.e(TAG,"add");
return a+b;
}
@Override
public Result getResult(long a, long b) throws RemoteException {
Log.e(TAG,"getResult");
long addResult = a + b;
long subResult = a - b;
long mulResult = a * b;
double divResult = a / b;
return new Result(addResult,subResult,mulResult,divResult);
}
@Override
public List<Result> getListResult(long a, long b) throws RemoteException {
Log.e(TAG,"getListResult");
List<Result> list = new ArrayList<>();
long addResult = a + b;
long subResult = a - b;
long mulResult = a * b;
double divResult = a / b;
list.add(new Result(addResult,subResult,mulResult,divResult));
list.add(new Result(addResult,subResult,mulResult,divResult));
return list;
}
@Override
public String putResult(Result result) throws RemoteException {
Log.e(TAG,"putResult="+result);
return "put成功";
}
};
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate process id = " + Process.myPid());
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG,"onBind");
return mBind;
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG,"onUnbind");
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy");
}
}
此时再看客户端怎么调用的
@OnClick(R.id.math)
public void math() {
if (mathInterface == null) {
Toast.makeText(this,"远程服务未绑定",Toast.LENGTH_LONG).show();
return;
}
try {
int result = mathInterface.add(3,2);
Log.e(TAG,"result="+result);
Result result2 = mathInterface.getResult(10,10);
Log.e(TAG,"result2="+result2);
List<Result> result3 = mathInterface.getListResult(10,10);
Log.e(TAG,"result3="+result3);
String result4 = mathInterface.putResult(new Result(10,10,10,10));
Log.e(TAG,"result4="+result4);
} catch (RemoteException e) {
e.printStackTrace();
}
}
看MainActivity打印出来的日志
11-09 17:57:07.497 23936-23936/com.mangoer.remotemathservicedemo E/MainActivity: result=5
11-09 17:57:07.512 23936-23936/com.mangoer.remotemathservicedemo E/MainActivity: result2=Result{addResult=20, subResult=0, mulResult=100, divResult=1.0}
11-09 17:57:07.514 23936-23936/com.mangoer.remotemathservicedemo E/MainActivity: result3=[Result{addResult=20, subResult=0, mulResult=100, divResult=1.0}, Result{addResult=20, subResult=0, mulResult=100, divResult=1.0}]
11-09 17:57:07.514 23936-23936/com.mangoer.remotemathservicedemo E/MainActivity: result4=put成功
再看服务端打印的日志
11-09 17:57:07.497 24148-24169/com.mangoer.remotemathservicedemo:remote E/MathService: add
11-09 17:57:07.498 24148-24170/com.mangoer.remotemathservicedemo:remote E/MathService: getResult
11-09 17:57:07.513 24148-24169/com.mangoer.remotemathservicedemo:remote E/MathService: getListResult
11-09 17:57:07.514 24148-24170/com.mangoer.remotemathservicedemo:remote E/MathService: putResult=Result{addResult=10, subResult=10, mulResult=10, divResult=10.0}
说明数据是相互传递成功了,至此,通过AIDL进行进程间通信,不管是传递基础类型数据还是非基础类型数据,都成功了。
那我们接下来讲上一篇的定向tag问题
定向tag为 in :表示数据只能由客户端流向服务端,具体表现为服务端将会接收到客户端传过来A对象的完整数据,无论服务端对传过来的这个A对象怎么修改,客户端的A对象会保证完整性,数据不会变。
具体什么意思,来看代码
在aidl接口文件中定义这个方法
String putResult(in Result result);
这个入参前面的in表示定向tag,然后重新Make Project,然后在客户端调用
Result r = new Result(10,10,10,10);
String result4 = mathInterface.putResult(r);
Log.e(TAG,"r="+r);
Log.e(TAG,"result4="+result4);
然后在服务端对这个对象进行修改
@Override
public String putResult(Result result) throws RemoteException {
result.setAddResult(1);
result.setDivResult(1);
Log.e(TAG,"putResult="+result);
return "put成功";
}
然后运行打印客户端的日志
11-09 18:20:19.751 6627-6627/com.mangoer.remotemathservicedemo E/MainActivity: r=Result{addResult=10, subResult=10, mulResult=10, divResult=10.0}
11-09 18:20:19.751 6627-6627/com.mangoer.remotemathservicedemo E/MainActivity: result4=put成功
再看服务端打印的日志
11-09 18:20:19.750 6700-6721/com.mangoer.remotemathservicedemo:remote E/MathService: putResult=Result{addResult=1, subResult=10, mulResult=10, divResult=1.0}
看到没有,尽管服务端对这个对象进行了修改,但是客户端的这个对象没有变,这就是数据只能由客户端流向服务端,而不会回流。
定向tag为inout :表示数据可在服务端与客户端双向流通,具体表现为服务端将会接收到客户端传过来A对象的完整数据,无论服务端对传过来的这个A对象怎么修改,客户端的A对象会同步修改,我戏称为回流。
具体看代码,在aidl接口文件中定义这个方法
String putResult(inout Result result);
客户端与服务端代码不变,看客户端日志
11-09 18:26:03.207 10060-10060/com.mangoer.remotemathservicedemo E/MainActivity: r=Result{addResult=1, subResult=10, mulResult=10, divResult=1.0}
11-09 18:26:03.207 10060-10060/com.mangoer.remotemathservicedemo E/MainActivity: result4=put成功
再看服务端日志
11-09 18:26:03.205 10195-10215/com.mangoer.remotemathservicedemo:remote E/MathService: putResult=Result{addResult=1, subResult=10, mulResult=10, divResult=1.0}
看到没有,两端的Result数据是一样的,这就是数据能从客户端流向服务端,服务端修改后也能流回客户端。
定向tag为out :表示数据只能由服务端流向客户端,具体表现为服务端将会接收到客户端传过来的A的空对象,这个对象不能赋值,然后服务端对传过来的这个A对象赋值数据,客户端的A对象会同步变化。
具体看代码,在在aidl接口文件中定义这个方法
String putResult(out Result result);
然后客户端代码修改下,要传个无数据对象
Result r = new Result();
String result4 = mathInterface.putResult(r);
Log.e(TAG,"r="+r);
Log.e(TAG,"result4="+result4);
服务端代码不变,运行看客户端日志
11-09 18:32:13.394 13808-13808/com.mangoer.remotemathservicedemo E/MainActivity: r=Result{addResult=1, subResult=0, mulResult=0, divResult=1.0}
11-09 18:32:13.394 13808-13808/com.mangoer.remotemathservicedemo E/MainActivity: result4=put成功
再看服务端日志
11-09 18:32:13.393 13911-13931/com.mangoer.remotemathservicedemo:remote E/MathService: putResult=Result{addResult=1, subResult=0, mulResult=0, divResult=1.0}
看到没,客户端刚才这个对象是没有赋值的,但是调用结束后这个对象有值了,而且跟服务端是一样的,这下应该明白怎么回事了把。
这里分析下在Android中使用多进程
优点:
1.减小主进程的内存压力:当应用功能越做越多,内存开销越来越大,而Android系统对每个应用进程有内存限制,这样势必会影响主进程的运行,这时候将一些特殊的组件放到另一个进程中可以避免主进程被系统杀死
2.使应用常驻后台:即使应用主进程被杀,子进程依然可以执行任务(例如推送服务,启动主线程)
3.提高用户友好度:因为每个进程都在自己单独的虚拟机里,即使子进程崩溃了,主进程也能运行
缺点:
1.消耗用户更多的电量等手机资源
2.更多的消耗了手机资源,如果很多APP都这样干,那手机就运行的很不流畅了
3.对于开发者而言,多进程通信的处理让我们的开发工作变得复杂
首先明确一点,进程间的内存空间是不可见的,那就会带来一些陷进:
1.Application的多次重建
当应用启动时候(代表新建一个进程),系统会新建一个虚拟机,这时候会初始化Application;这时候再新建一个进程(比如启动一个远程服务),又新建一个虚拟机,又初始化了Application。此时就得在Application的onCreate方法里判断下是哪个进程初始化的再做相应处理:
int pid = android.os.Process.myPid();
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningApps =am.getRunningAppProcesses();
if (runningApps != null && !runningApps.isEmpty()) {
for(ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
if (procInfo.pid == pid) {
if(procInfo.processName.equals("com.mango.test")) {
//com.mango.test 这是我主进程的名称 当是主进程初始化的时候,做一些操作
}else{
//这里就是子线程的操作了,可以再具体通过名称判断是哪个子进程
}
}
}
}
2.静态数据和单例失效
因为是两个进程,他们之间拥有自己独立的内存空间,一个进程的组件修改了静态数据,另一个进程再读这个数据的时候并不会受影响;单例模式也是如此,因为他们访问的不是同一个内存,这时候就是用Intent或者AIDL去传输数据
3.文件共享问题
多进程的情况下有可能出现同一时间片两个进程都来访问同一个文件(比如数据库文件,本地文件,SharePreference),这样就容易照成数据的损毁的;这是由于Java的同步机制是由JVM来进行调度的,但是两个进程就是两个JVM,这样在多进程开发中就会失效,所以实际开发中尽量避免多进程访问文件,这时候需要多利用IPC中的C/S思想,提供服务,接口调用(使用AIDL,ContentProvider)。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)