Android系统中的应用程序之间是不能共享内存的,每一个应用程序都有自己独有的虚拟机,这样就保证了数据的安全性,但是这样就给两个应用程序之间进行通信带来了不便,所以我们就知道了两点:
- 两个进程是无法直接进行通信的
- 跨进程通信是通过Android系统底层进行间接通信
Android中的通信主要有下面四种:Activity,Broadcast,ContentProvider以及AIDL,我们可以发现这四种方法是分别基于四大组件进行实现的。
Activity
Activity的跨进程访问与进程内访问略有不同。虽然它们都需要Intent对象,但跨进程访问并不需要指定Context对象和Activity的 Class对象,而需要指定的是要访问的Activity所对应的Action。有些Activity还需要指定一个Uri(通过 Intent构造方法的第2个参数指定)。 在android系统中有很多应用程序提供了可以跨进程访问的Activity。
Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+ edit.getText().toString()));
startActivity(callIntent);
Broadcast
广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。在应用程序中发送广播很简单,只需要调用sendBroadcast方法即可。该方法需要一个Intent对象。通过Intent对象可以发送需要广播的数据。
ContentProvider
ContentProvider向我们提供了我们在应用程序之前共享数据的一种机制,而我们知道每一个应用程序都是运行在不同的应用程序的,数据和文件在不同应用程序之间达到数据的共享不是没有可能,而是显得比较复杂,而正好Android中的ContentProvider则达到了这一需求,比如有时候我们需要操作手机里的联系人,手机里的多媒体等一些信息,我们都可以用到这个ContentProvider来达到我们所需。
ContentProvider强调的是数据之间的共享,我们使用文件进行共享,也可以是认为这种方式进行进程间的通信。
ADIL
AIDL(Android Interface Definition Language):Android接口定义语言。他定义了客户端与服务端的一个标准规范,AIDL强调的是客户端调用Service的相关方法。我们要注意到AIDL毕竟是跨进程进行通信的,所以他所消耗的内存比较大,因此不能随意去使用AIDL,要不然会影响我们应用程序的效果。所以.只有当你需要来自不同应用的客户端通过IPC(进程间通信)通信来访问你的服务时,并且想在服务里处理多线程的业务,这时就需要使用AIDL。如果你不需要同时对几个应用进程IPC操作,你最好通过实现Binder接口来创建你的接口。如果你仍需要执行IPC操作,但不需要处理多线程,使用Messenger来实现接口即可。
- AIDL是用于多进程并发通信处理
- Binder是用于非多进程并发处理
- Messenger是用于多进程且下的非并发处理
适用范围:Messenger < Binder < AIDL 当然复杂度也依次增加。当然实际messenger的本质也是调用的AIDL,只是进行了封装,开发的时候不用再写.aidl文件。并且在Service端,Messenger处理Client端的请求是单线程的,而AIDL是多线程的。在Client端使用AIDL获取返回值是同步的,而Messenger是异步的。
接下来就来说说AIDL如何进行使用
AIDL基本语法
我们有三种方法生成AIDL:
- 直接用记事本进行编写,这里我们要注意到,要对应包名的文件夹嵌套,然后通过sdk中的build-tools目录下最新版本的文件夹下的aidl.exe进行编译(控制台)。
- 用eclipse进行编写,但是eclipse中没有对应的.aidl文件,所以我们直接new一个text文件,修改后缀就好了。然后我们就可以进行调用了,他已经帮我们生成好了对应的.java文件。
- 用Android Studio进行编写,在Android Studio中就没有eclipse在那么随便了,我们需要先new出一个AIDL Folder,之后在这个文件夹中new出.aidl文件,最后进行编译,因为它不同于eclipse,它生成的.java文件是默认的。
然后我们就来说下AIDL的基本语法,它是一个接口定义语言,所以我们要知道我们定义的是一个接口。下面就是直接生成的aidl文件:
// IMyAidlInterface.aidl
package com.gin.xjh.aidltest;//包名
// Declare any non-default types here with import statements
//interface 关键字
//IMyAidlInterface 接口名
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
然后我们对这个文件进行修改,再进行编译:
// IMyAidlInterface.aidl
package com.gin.xjh.aidltest;//包名
// Declare any non-default types here with import statements
//interface 关键字
//IMyAidlInterface 接口名
interface IMyAidlInterface {
//方法
int getPid(int text);
}
这样我们就能在MainActivity中进行调用了。然后我们可以在Project目录下看到这个生成的.java文件。
AIDL功能实现
我们主要是实现一个客户端输入两个数,我们将这两个数上传到服务器端,计算两个数的和,将结果返回客户端。所以首先我们要按照之前的步骤写好aidl文件:
package com.gin.xjh.aidl;
interface IAidl {
//计算两个int的和
int add(int num1,int num2);
}
然后我们就要定义一个类继承自Service,因为aidl要在借助Service进行实现。之后我们要实现我们所定义的接口,在Service的onBind方法中进行返回。
package com.gin.xjh.aidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
public class IRemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
private IBinder iBinder = new IAidl.Stub() {
@Override
public int add(int num1, int num2) throws RemoteException {
return num1 + num2;
}
};
}
但是我们要注意我们的服务端需要进行注册,并且设置允许远程才能正常使用,要不然之后客户端在请求结果的伤害就会奔溃。
<service android:name=".IRemoteService"
android:process=":remote"
android:exported="true"/>
接下来我们就要开始编写客户端。在客户端最重要的就是界面,界面我们就不多说了,特别简单的UML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="horizontal">
<EditText
android:id="@+id/ed_num1"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<TextView
android:text="+"
android:gravity="center"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/ed_num2"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<Button
android:text="="
android:id="@+id/submit"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_res"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
</LinearLayout>
然后我们将我们在服务器端写的aidl文件复制到客户端来,并且我们需要注意**客户端和服务端相同aidl文件的包名是一致的,要不然导致应用强制退出。**之后我们就需要在客户端对于服务端进行链接,之前我们已经说过了,服务器端是一个Service,所以我们先将Activity中的基本操作写好:
package com.gin.xjh.aidlclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.gin.xjh.aidl.IAidl;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText mEtNum1,mEtNum2;
private Button mBtSubmit;
private TextView mTvRes;
private IAidl iAidl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
bindService();
initEvent();
}
private void initView() {
mEtNum1 = findViewById(R.id.ed_num1);
mEtNum2 = findViewById(R.id.ed_num2);
mBtSubmit = findViewById(R.id.submit);
mTvRes = findViewById(R.id.tv_res);
}
private void initEvent() {
mBtSubmit.setOnClickListener(this);
}
@Override
public void onClick(View view) {
}
}
然后我们当然就要实现bindService方法,在这里面我们首先需要通过bind方式让Service与Activity进行绑定,并且我们不要忘记对于Service的解绑:
private void bindService() {
//获取到服务端
Intent intent = new Intent();
//显式Intent启动服务
//ComponentName组件名
intent.setComponent(new ComponentName("com.gin.xjh.aidl","com.gin.xjh.aidl.IRemoteService"));
bindService(intent,conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(conn);
}
我们现在就发现了我们需要一个ServiceConnection对象,这个就是让我们拿到IAidl对象,IAidl对象代表的就是远程服务的代理,让我们这点击时能将数据进行上传服务端进行处理。当然我们在服务断开的时候需要对资源进行回收,放在内存泄露。
private ServiceConnection conn = new ServiceConnection() {
//绑定服务时
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//拿到了远程的服务的代理
iAidl = IAidl.Stub.asInterface(iBinder);
}
//服务断开时
@Override
public void onServiceDisconnected(ComponentName componentName) {
//回收资源
iAidl = null;
}
};
最后就是对于OnClick方法的编写,我们获取到输入框的值,调用远程服务的代理,将数据传入就达到了我们想要的效果了。
@Override
public void onClick(View view) {
int num1 = Integer.parseInt(mEtNum1.getText().toString());
int num2 = Integer.parseInt(mEtNum2.getText().toString());
try {
int res = iAidl.add(num1, num2);
mTvRes.setText(res+"");
} catch (RemoteException e) {
e.printStackTrace();
mTvRes.setText("错误");
}
}
要注意这块代码:
int res = iAidl.add(num1, num2);
mTvRes.setText(res+"");
虽然我们aidl里面的代码是执行在Binder的子线程中,但是主线程中执行的代码需要等待结果的返回,所以说对应的Service的add方法中如果有耗时操作,这块代码也是会被阻塞的,所以说我们不能这样进行耗时操作。
如果我们已知对应的Service中有耗时操作并且我们不关心他的执行结构我们就可以在aidl文件中函数名前加一个关键字 oneway,但是如果使用了这个关键字我们就不能有返回值了,所以返回类型为void。
AIDL数据传递类型
因为AIDL是不能传递大型数据的,因为在操作系统底层所传递的消息是最基本的,它所支持的数据类型主要是有下面4种:
- 基本数据类型(除short以外,以外Parcel中没有对于writeShor方法)
- String,CharSequence
- List,Map(所放的元素一定要是上面出现过的类型,并且我们需要对这个参数进行描述:in,out,inout,代表的是输入修改,输出修改和输入输出修改的,因为作系统底层所传递的消息是最基本的,所以对于大型的数据会被打碎(打包),然后传递,再到另一方进行组合(拆包),所以我们标记了好了输入输出就能减少资源)
- Pracelable(对于自定义类实现Pracelable接口,完成里面的两个方法,在writeToParcel方法中将数据写入Parcel中
writeXxx()
方法进行写入,然后再定义如下方法完成数据的解析:
public static final Creator<Person> CREATOR = new Creator<Person>(){
@Override
public Person createFromParcel(Parcel source){
return new Person(source);
}
@Override
public Person[] newArray(int size){
return new Person[size];
}
};
然后我们就编写Person的一个新的构造方法就好了。但是我们要注意到一件事情,我们是按什么顺序加进来的,就要按什么顺序取出来。
public Person(Parcel source){
this.name = source.readString();
this.age = source.readInt();
}
之后我们在aidl文件中需要将Person文件导入进aidl文件中,并且我们需要建立一个aidl文件对于Person进行描述。
package com.gin.xjh.aidl;
pracelable Person;
并且我们需要记住在Person前面加上in或者out或者inout类型
package com.gin.xjh.aidl;
import com.gin.xjh.aidl.Person;
interface Aidl{
List<Person> add(in Person person);
}
然后要将 Person 类连同包一起复制到客户端上,这样才能保持包的路径是一致的,之后就和上面的没有什么区别了。
当然如果我们要提供一个接口列表的话我们就不能单纯的使用 ArrayList 了,因为我们的对象是经过序列化和反序列化得到的新的对象在子进程中 add 或者 remove 进 List 的,所以当我们在 main 进程 add 和 remove 的是同一个对象,但是经过这个过程其实在子进程中 add 和 remove 的是两个不同的对象。所以我们要使用 RemoteCallbackList 并且我们既然是 Listener 所以我们也要监听 Binder 的生命周期,这个 RemoteCallbackList 也帮我们实现好了的。
AIDL总结
我们可以发现AIDL操作中主要是下面3个步骤:
- 定义AIDL文件
- 服务端实现接口
- 客户端调用接口
因此我们必须要保证接口一致。
AIDL实现接口时有以下几个原则:
- 抛出的异常不要返回给调用者,跨进程抛异常处理是不可取的。
- IPC调用的是同步的,如果已知一个IPC服务需要超过几毫秒的时间才难完成,我们就要避免者Activity的主线程中进行调用,否则可能会导致应用程序导致界面失去响应。
- AIDL运行方法有任何类型的参数和返回值
- AIDL只支持方法,不能中AIDL接口中声明静态属性。
还有我们会发现我们使用了代理(proxy)和存根(stub),那这两个是什么东西呢? 我们可以这样进行理解你到自动取款机上去取款;你就是客户,取款机就是你的代理;你不会在乎钱具体放在那里,你只想看到足够或更多的钱从出口出来(这就是com的透明性)。你同银行之间的操作完全是取款机代理实现。 你的取款请求通过取款机,传到另一头,银行的服务器,他也没有必要知道你在哪儿取钱,他所关心的是你的身份,和你取款多少。当他确认你的权限,就进行相应的操作,返回操作结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出卡后,操作完成。 取款机不是直接同服务器连接的,他们之间还有一个“存根”,取款机与存根通信,服务器与存根通信。从某种意义上说存根就是服务器的代理。
这样我们就能很好的理解代理以及存根的概念。