第二章 IPC机制 源
##1、多进程模式的运行机制
Android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配了一个独立的虚拟机,不同的虚拟机在不同的内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败。
一般来说,使用多进程会造成如下几个方面的问题:
(1)静态成员和单例模式完全失效
(2)线程同步机制完全失效 ,不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象
(3)SharedPreference的可靠性下降
SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,这时因为SharedPreferences底层是通过读写XML文件来实现的,并发写显然是可能出问题的,甚至并发读写都有可能发生问题
(4)Application会多次创建
运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的。同理,运行在不同进程中的组件是属于两个不同的虚拟机和Application的。
##2、Binder
(1)继承了IBinder接口
(2) Binder是一种跨进程通信方式
(3)是ServiceManager连接各种Manager(ActivityManager,WindowManager等)和相应ManagerService的桥梁
(4)从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务器会返回一个包含了服务器端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者是数据,这里的服务包含了普通服务和基于AIDL的服务aidl工具根据aidl文件自动生成的java接口的解析:首先,它声明了几个接口方法,同时还声明了几个整型的id用于标识这些方法,id用于标识在transact过程中客户端所请求的到底是哪个方法;接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub内部的代理类Proxy来完成。 所以,这个接口的核心就是它的内部类Stub和Stub内部的代理类Proxy。
下面分析其中的方法:
& asInterface(android.os.IBinder obj):用于将服务器端的Binder对象转化成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端是在同一进程中,那么这个方法返回的是服务端的Stub对象本身,否则返回的是系统封装的Stub.Proxy对象。
& asBinder:返回当前Binder对象
& onTransact:这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。 这个方法的原型是public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags) 服务端通过code可以知道客户端请求的目标方法,接着从data中取出所需的参数,然后执行目标方法,执行完毕之后,将结果写入到reply中。如果此方法返回false,说明客户端的请求失败,利用这个特性可以做权限验证(即验证是否有权限调用该服务)。
& Proxy#[Method]:代理类中的接口方法,这些方法运行在客户端,当客户端远程调用此方法时,它的内部实现是:首先创建该方法所需要的参数,然后把方法的参数信息写入到_data中,接着调用transact方法来发起RPC请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。
##3、Android的IPC方式
1、Bundle
2、文件共享
3、使用Messenger
4、使用AIDL
5、使用ConntentProvider
6、使用Socket
1、使用Bundle
Activity,Service,Receiver都是支持在Intent中传递Bundle数据,由于Bundle实现了Parcelable接口,所以方便的在不同的进程间传输。
2、使用文件共享
两个进程通过读写同一个文件交换数据,Android基于Linux系统。使其并发读写文件可以没有限制的进行,甚至两个线程同时对同一个文件进行读写操作都是允许的。通过共享的文件也是有局限性的,比如并发读写的问题,如果并发读,那么我们读出的内容具有可能不是最新的,因此文件共享方式是个对数据同步要求不高的进程之间通信。
本质上来说SharedPreferences属于文件的一种,由于系统对他的读写有一定的缓存策略,即在内存中会有一份SharedPreference文件缓存,因此在多进程模式下,系统对他的读写变得不可靠,当面对高并发访问,SharedPreferences很大几率丢失数据,因此不建议在进程中通信使用SharedPreferences.
//写
private void persistToFile() {
new Thread(new Runnable() {
public void run() {
User user = new User(1, "hello world", false);
File dir = new File(MyConstants.CHAPTER_2_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
} finally {
MyUtils.close(objectOutputStream);
}
}
}).start();
}
//读
private void recoverFromFile() {
new Thread(new Runnable() {
public void run() {
User user = null;
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
if (cachedFile.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));
user = (User) objectInputStream.readObject();
Log.d(TAG, "recover user:" + user);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyUtils.close(objectInputStream);
}
}
}
}).start();
}
3、使用Messenger
Messenger对AIDL做了封装,底层实现就是AIDL,方便进程间通信,由于一次处理一个请求,因此在服务端不用考虑线程同步问题,不存在并发执行的情形。下面是Messenger的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是Stub.asInterface,这种使用方法都表明它的底层是AIDL。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target){
mTarget = IMessenger.Stud.asInterface(target);
}
接下里看下使用步骤
客户端:
1.在activity中绑定服务
2.创建ServiceConnection并在其中使用 IBinder 将 Messenger实例化
3.使用Messenger向服务端发送消息
4.解绑服务
5.服务端中在 hadleMessage() 方法中接收每个 Message
服务端:
1、建一个handler对象,并实现hanlemessage方法,用于接收来自客户端的消息,并作处理
2、messenger(送信人),封装handler
3、messenger的getBinder()方法获取一个IBinder对象,通过onBind返回给客户端
上面实现的仅仅是单向通信,即客户端给服务端发送消息,如果我需要服务端给客户端发送消息又该怎样做呢?
下面就让我们接着上面的步骤来实现双向通信吧
1.在客户端中创建一个Handler对象,用于处理服务端发过来的消息
2.创建一个客户端自己的messenger对象,并封装handler。
3.将客户端的Messenger对象赋给待发送的Message对象的replyTo字段
4.在服务端的Handler处理Message时将客户端的Messenger解析出来,并使用客户端的Messenger对象给客户端发送消息
接下来看一下代码:
//客户端
在Messenger中进行数据传递必须将数据放入Messsage中,而Messenger和Message都实现了Parcelable接口,实际上,通过 Messenger来传递Message,Message中能使用的载体就只有what、arg1、arg2、Bundle以及replyTo,我们还有Bundle,Bundle中可以支持大量的数据类型。
public class MainActivity extends AppCompatActivity {
private Messenger mService;
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
//接收从服务端传递来的数据
Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "hello, this is client.");
msg.setData(data);
//当客户端发送消息时需要把接收服务端返回的Messenger通过Message的replyTo参数传递给服务端
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent("com.ryg.MessengerService.launch");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
}
//服务端service
public class MessengerService extends Service {
private static class MessengerHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.i(TAG, "receive msg from Client:" + msg.getData().getString("msg"));
//收到消息后立即回复一条消息给客户端
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。");
relpyMessage.setData(bundle);
try {
client.send(relpyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
4、使用AIDL
使用AIDL进行进程间通信分为服务端和客户端两方面:
(1)、服务端
服务端首先创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个接口。
(2)、客户端
绑定服务端的Service,绑定成功后将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。
(3)、AIDL接口的创建
新建一个后缀为AIDL的文件,声明一个接口和方法在gen目录下自动生成对应的.java文件
interface IService {
void callALiPayService();
}
AIDL文件中并不是所有的数据类型都支持的,那么支持数据的类型有:
基本数据类型(int,long,char,boolean等)
String和CharSequence
List:只支持ArrayList
Map:只支持HashMap
Parcelable:所有实现Parcelable接口的对象
AIDL:所有AIDL接口本身也可以在AIDL文件中使用
(4)远程服务端Service的实现
public class ALiPayService extends Service {
public void onCreate() {
super.onCreate();
}
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(),"绑定成功,准备付费",Toast.LENGTH_LONG).show();
return new MyBinder();
}
public class MyBinder extends IService.Stub{
public void callALiPayService() throws RemoteException {
Toast.makeText(getApplicationContext(),"开始付费,购买装备",Toast.LENGTH_LONG).show();
}
}
public boolean onUnbind(Intent intent) {
Toast.makeText(getApplicationContext(),"解除绑定,取消付费",Toast.LENGTH_LONG).show();
return super.onUnbind(intent);
}
public void onDestroy() {
super.onDestroy();
}
(5)客户端的实现
绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后可以通过这个接口去调用服务端的远程方法了。
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private MyConn myConn;
private Intent intent;
private IService iService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent = new Intent();
intent.setAction("cn.alipay");//清单文件中指定的action动作
intent.setPackage("com.alipayservice");
findViewById(R.id.bind).setOnClickListener(this);
findViewById(R.id.call).setOnClickListener(this);
findViewById(R.id.unbind).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bind: //点击执行绑定的动作
myConn = new MyConn();
bindService(intent,myConn,BIND_AUTO_CREATE);
break;
case R.id.call://点击执行支付的动作
try {
iService.callALiPayService();
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.unbind://点击执行解绑的动作
unbindService(myConn);
break;
}
}
public class MyConn implements ServiceConnection{
public void onServiceConnected(ComponentName name, IBinder service) {
iService = IService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName name) {}
}
}
5、使用ContentProvider
继承ContentProvider,实现6个抽象方法:onCreate,query,update,insert,delete,getType
(1)ContentProvider主要以表格的形式来组织数据,并且可以包含多个表;
(2)ContentProvider还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider;
(3)ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行;
(4)要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者;
6、使用Socket套接字,分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中TCP和UDP协议。
TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过"三次握手"才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传功能,因此具有很高的稳定性
UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能,在性能上,UDP具有更好的效率,其缺点是不保证数据能够正确传输,尤其是在网络拥塞的情况下。
TCP下服务端设计:当serve启动在线程中建立TCP服务,等待客户端连接请求。当有客户端连接时生成一个新的socket,每次创建的socket分别和不同的客户端通信,服务端收到消息回复。当客户端断开连接时,服务端通过判断输入流返回值为null,关闭对应的socket并结束通话线程。
主要代码如下:
public class TCPServerService extends Service {
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
private class TcpServer implements Runnable {
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8688);
} catch (Exception e) {
return;
}
while (!mIsServiceDestoryed) {
try {
// 接受客户端请求
final Socket client = serverSocket.accept();
System.out.println("accept");
new Thread() {
@Override
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
// 用于接收客户端消息
BufferedReader in = new BufferedReader(new InputStreamReader(
client.getInputStream()));
// 用于向客户端发送消息
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())), true);
out.println("欢迎来到聊天室!");
while (!mIsServiceDestoryed) {
String str = in.readLine();
if (str == null) {
break;
}
int i = new Random().nextInt(mDefinedMessages.length);
String msg = mDefinedMessages[i];
out.println(msg);
}
out.close();
in.close();
client.close();
}
}
TCP下客户端设计:客户端Activity启动时在onCreate中开启一个线程连接服务端socket,采用超时重连的策略,间隔为1000毫秒,服务端连接成功后就可以通信了,当Activity退出时就退出循环并终止线程,关闭socket。
主要代码如下:
public class TCPClientActivity extends Activity implements OnClickListener {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_RECEIVE_NEW_MSG: {
mMessageTextView.setText(mMessageTextView.getText()
+ (String) msg.obj);
break;
}
case MESSAGE_SOCKET_CONNECTED: {
mSendButton.setEnabled(true);
break;
}
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
Intent service = new Intent(this, TCPServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPServer();
}
}.start();
}
private void connectTCPServer() {
Socket socket = null;
while (socket == null) {
try {
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())), true);
mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connect server success");
} catch (IOException e) {
SystemClock.sleep(1000);
System.out.println("connect tcp server failed, retry...");
}
}
try {
// 接收服务器端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
while (!TCPClientActivity.this.isFinishing()) {
String msg = br.readLine();
System.out.println("receive :" + msg);
if (msg != null) {
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "server " + time + ":" + msg
+ "\n";
mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg)
.sendToTarget();
}
}
System.out.println("quit...");
mPrintWriter.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结:IPC方式对比
Bundle: 优点:简单易用 缺点:只传输Bundle支持数据类型 适用场景:四大组件间进程通信
文件共享: 优点:简单易用 缺点:不适合高并发,无法做到进程间即时通信 适用场景:无并发,实时性不高的场景
AIDL: 优点:一对多并发实时通信 缺点:使用复杂,处理好线程同步 适用场景:一对多通信且有RPC需求
Messenger: 优点:一对多并发实时通信 缺点:不适合高并发,只传输Bundle支持数据类型 适用场景:低并发一对多通信
ContentProvider: 优点:一对多并发数据共享 缺点:提供数据源CRUD操作 适用场景:一对多进程间数据共享
Socket: 优点:网络传输字节流,一对多并发实时通信 缺点:细节繁琐 适用场景:网络数据交换