IPC 机制(三)
2016-03-05 18:42
267 查看
4.使用ContentProvider
CotentProvider是Android中提供专门用于不同应用间进行数据共享的方式,从这一点来看,它天生就适合进程间通信。
5.使用Socket
我们也可以通过Socket来实现进程间的通信,Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP链接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供超时重传机制,因此具有很高的稳定性;而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能。在性能上,UDP具有很好的效率,确定是不能保证数据一定能够正确传输,尤其是在网络拥堵的情况下。下面是一个跨进程连天的程序,两个进程可以通过Socket来实现信息的传输,Socket本身可以支持传输任意直接流,这里为了简单起见,仅仅传输文本信息,很显然,这事一种IPC方式。
使用Socket来进行通信,需要声明权限:
先看一下服务端的设计,当Service启动时,会在线程中建立TCP服务,这里监听的是8688端口,然后就可以等待客户端的链接请求了。当有客户端链接时,会生成一个Socket,通过每次新建Socket就可以分别和不同的客户端通信了。服务端每收到一个客户端的消息就会随机回复一句话给客户端。当客户端断开连接时,服务端这边也会响应的关闭对应Socket并结束通话线程,这里通过判断服务端输入流的返回值来确定的,当客户端断开链接后,服务端这边的输入流会返回null,这个时候我们就知道客户端退出了。
服务端代码如下:
下面是客户端的代码:
Binder连接池
当项目中使用很多A
IDL的时候,我第一个想到的是一个个实现AIDL,创建多个Service,但是Service是很占资源,而且会是的应用看起来很重量级。针对上面的问题,需要减少Service的数量,将所有的AIDL放在同一个Service中去管理。
在这种模式下,整个工作机制是这样的:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间不能有耦合,所有实现细节我们单独开来,然后向服务端提供自己的唯一标识和其对应的Binder现象;对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象就可以进行远程方法调用了。由此可见,Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。
下面是实例:
新建两个业务模块AIDL接口:
ISecurityCenter.aidl
和
ICompute.aidl
下面是AIDL的实现
SecurityCenterImpl.java
和
ComputeImpl.java
现在业务模块的AIDL接口和实现都已经完成了,注意这里并没有为每个模块的AIDL单独创建Service,接下来就是服务端和Binder连接池的工作了。
首先,为Binder连接池创建AIDL接口IBnderPool.aidl,代码如下:
接着,为Binder连接池创建远程Service并实现IBinderPool
IBinderPool的实现:
Service的实现
下面是客户端的实现:
执行后打印的Log:
03-05 11:52:20.182 4051-4530/? D/ipctest: content:helloworld-安卓
03-05 11:52:20.183 4051-4530/? D/ipctest: encrypt:6;221)1,2:s寗匍
03-05 11:52:20.183 4051-4530/? D/ipctest: decrypt:helloworld-安卓
03-05 11:52:20.189 4051-4530/? D/ipctest: 3+5=8
CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
主要方法
public CountDownLatch(int count);
public void countDown();
public void await() throws InterruptedException
构造方法参数指定了计数的次数
countDown方法,当前线程调用此方法,则计数减一
awaint方法,调用此方法会一直阻塞当前线程,直到计时器的值为0
在线程中执行线程池是因为,通过CountDownLatch将bindService这一异步操作转换成可同步操作,这就意味着他有可能是耗时的,然后就是Binder方法调用过程也是可能耗时的,因此不建议放在主线程去执行。注意到BinderPool是一个单例实现,因此在同一个进程中只会初始化一次,所以我们就提前初始化BinderPool,那么可能优化程序的体验,比如可以放在Application中提前初始化BinderPool,虽然不能保证当我们调用BinderPool时它一定是初始化好的,但是在大多数情况下,这种初始化工作(绑定远程服务)的时间开销(比如BinderPool没有提前初始化完成的话)是可以接受的。另外,BinderPool中有断线重连的机制,当远程服务意外终止时,BinderPool重新建立连接,这个时候如果业务模块中Binder调用出现异常,也需要手动去重新获取最新的Binder对象,这个是需要注意的。
使用BinderPool可以大大方便日常的开发工作,比如如果有一个新的业务模块需要添加新的AIDL,那么在它实现了自己的AIDL接口后,只需要修改BinderPoolImpl中的queryBinder方法,给自己添加一个新的BinderCode并返回对应的Binder对象即可,不需要做其他改动,也不需要创建新的Service。由此可见,BinderPool能过极大地提高AIDL的工作效率,并且可以避免大量的Service创建,因此,建议在AIDL开发工作中引入BinderPool机制。
选用合适的IPC
IPC方式的优缺点
CotentProvider是Android中提供专门用于不同应用间进行数据共享的方式,从这一点来看,它天生就适合进程间通信。
5.使用Socket
我们也可以通过Socket来实现进程间的通信,Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP链接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供超时重传机制,因此具有很高的稳定性;而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能。在性能上,UDP具有很好的效率,确定是不能保证数据一定能够正确传输,尤其是在网络拥堵的情况下。下面是一个跨进程连天的程序,两个进程可以通过Socket来实现信息的传输,Socket本身可以支持传输任意直接流,这里为了简单起见,仅仅传输文本信息,很显然,这事一种IPC方式。
使用Socket来进行通信,需要声明权限:
<uses-permission android:name="android.permission.INTERNET " /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />需要注意的是不能在主线程中访问网络,因为会引起报错,并且访问网络是耗时的,如果放在主线程会影响效率。
先看一下服务端的设计,当Service启动时,会在线程中建立TCP服务,这里监听的是8688端口,然后就可以等待客户端的链接请求了。当有客户端链接时,会生成一个Socket,通过每次新建Socket就可以分别和不同的客户端通信了。服务端每收到一个客户端的消息就会随机回复一句话给客户端。当客户端断开连接时,服务端这边也会响应的关闭对应Socket并结束通话线程,这里通过判断服务端输入流的返回值来确定的,当客户端断开链接后,服务端这边的输入流会返回null,这个时候我们就知道客户端退出了。
服务端代码如下:
public class TCPServerService extends Service { private boolean mIsServiceDestoryed = false; private String[] mDefineMessages = new String[] { "你好啊,哈哈","请问你叫什么名字呀?","今天北京天气不错啊,shy","你知道吗?我可是可以和很多人同时聊天的哦" ,"给你讲个笑话吧:据说爱笑的人运气不会太差,不知道真假。" }; public TCPServerService() { } @Override public void onCreate() { new Thread(new TcpServer()).start(); super.onCreate(); } @Override public void onDestroy() { mIsServiceDestoryed = true; super.onDestroy(); } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } private class TcpServer implements Runnable { @Override public void run() { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(8688); } catch (IOException e) { System.err.println("establish tcp setver failed, port:8688"); e.printStackTrace(); 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(); System.out.println("msg from client:" + str); if(str == null) { break; } int i = new Random().nextInt(mDefineMessages.length); String msg = mDefineMessages[i]; out.println(msg); System.out.println("send :" + msg); } System.out.println("client quit."); MyUtils.close(out); MyUtils.close(in); client.close(); } }
下面是客户端的代码:
public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener{ private static final int MESSAGE_RECEIVE_NEW_MSG = 1; private static final int MESSAGE_SOCKET_CONNECTED = 2; private Button mSendButton; private TextView mMessageTextView; private EditText mMessageEditText; private PrintWriter mPrintWriter; private Socket mClientSocket; 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; } } super.handleMessage(msg); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tcpclient); mMessageTextView = (TextView) findViewById(R.id.msg_container); mSendButton = (Button) findViewById(R.id.send); mSendButton.setOnClickListener(this); mMessageEditText = (EditText) findViewById(R.id.msg); Intent service = new Intent(this,TCPServerService.class); startService(service); new Thread(new Runnable() { @Override public void run() { connectTCPServer(); } }).start(); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); } @Override protected void onDestroy() { if(mClientSocket != null) { try { mClientSocket.shutdownInput(); mClientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } super.onDestroy(); } @Override public void onClick(View v) { if(v == mSendButton) { final String msg = mMessageEditText.getText().toString(); if(!TextUtils.isEmpty(msg) && mPrintWriter != null) { mPrintWriter.println(msg); mMessageEditText.setText(""); String time = formatDateTime(System.currentTimeMillis()); final String showMsg = "self"+time +":" + msg + "\n"; mMessageTextView.setText(mMessageTextView.getText() + showMsg); } } } private String formatDateTime(long time) { return new SimpleDateFormat("HH:mm:ss").format(new Date(time)); } 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(SystemClock.currentThreadTimeMillis()); final String showedMsg = "server " + time + ":" + msg + "\n"; mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showedMsg).sendToTarget(); } } System.out.println("quit...."); MyUtils.close(mPrintWriter); MyUtils.close(br); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
Binder连接池
当项目中使用很多A
IDL的时候,我第一个想到的是一个个实现AIDL,创建多个Service,但是Service是很占资源,而且会是的应用看起来很重量级。针对上面的问题,需要减少Service的数量,将所有的AIDL放在同一个Service中去管理。
在这种模式下,整个工作机制是这样的:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间不能有耦合,所有实现细节我们单独开来,然后向服务端提供自己的唯一标识和其对应的Binder现象;对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象就可以进行远程方法调用了。由此可见,Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。
下面是实例:
新建两个业务模块AIDL接口:
ISecurityCenter.aidl
// ISecurityCenter.aidl package com.app.song.ipc1; // Declare any non-default types here with import statements interface ISecurityCenter { String encrypt(String content); String decrypt(String password); }
和
ICompute.aidl
// ICompute.aidl package com.app.song.ipc1; // Declare any non-default types here with import statements interface ICompute { int add(int a, int b); }
下面是AIDL的实现
SecurityCenterImpl.java
package com.app.song.ipc1.binderpool; import android.os.RemoteException; import com.app.song.ipc1.ISecurityCenter; /** * Created by song on 2016/3/5. */ public class SecurityCenterImpl extends ISecurityCenter.Stub { private static final char SECRET_CODE = '^'; @Override public String encrypt(String content) throws RemoteException { char[] chars = content.toCharArray(); for(int i = 0; i < chars.length; i++) { chars[i] ^= SECRET_CODE; } return new String(chars); } @Override public String decrypt(String password) throws RemoteException { return encrypt(password); } }
和
ComputeImpl.java
package com.app.song.ipc1.binderpool; import android.os.RemoteException; import com.app.song.ipc1.ICompute; /** * Created by song on 2016/3/5. */ public class ComputeImpl extends ICompute.Stub { @Override public int add(int a, int b) throws RemoteException { return a + b; } }
现在业务模块的AIDL接口和实现都已经完成了,注意这里并没有为每个模块的AIDL单独创建Service,接下来就是服务端和Binder连接池的工作了。
首先,为Binder连接池创建AIDL接口IBnderPool.aidl,代码如下:
// IBinderPool.aidl package com.app.song.ipc1; // Declare any non-default types here with import statements interface IBinderPool { IBinder queryBinder(int binderCode); }
接着,为Binder连接池创建远程Service并实现IBinderPool
IBinderPool的实现:
public class BinderPool { public static final int BINDER_NONE = -1; public static final int BINDER_COMPUTE = 0; public static final int BINDER_SECURITY_CENTER = 1; private Context mContext; private IBinderPool mBinderPool; private static volatile BinderPool sInstance; private CountDownLatch mConnectBinderPoolCountDownLatch; private BinderPool(Context context) { mContext = context.getApplicationContext(); connectBinderPoolService(); } public static BinderPool getInstance(Context cotext) { if(sInstance == null) { synchronized (BinderPool.class) { sInstance = new BinderPool(cotext); } } return sInstance; } private void connectBinderPoolService() { mConnectBinderPoolCountDownLatch = new CountDownLatch(1); Intent setvice = new Intent(mContext, BinderPoolService.class); mContext.bindService(setvice,mBinderPoolConnection,Context.BIND_AUTO_CREATE); try { mConnectBinderPoolCountDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } public IBinder queryBinder(int binderCode)//通过调用IBinderPool接口来实现AIDL的调用 { IBinder binder = null; if(mBinderPool != null) { try { binder = mBinderPool.queryBinder(binderCode); } catch (RemoteException e) { e.printStackTrace(); } } return binder; } private ServiceConnection mBinderPoolConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBinderPool = IBinderPool.Stub.asInterface(service);//通过服务端返回的IBinder实现IBinderPool try { mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient,0);//死亡监听 } catch (RemoteException e) { e.printStackTrace(); } mConnectBinderPoolCountDownLatch.countDown(); } @Override public void onServiceDisconnected(ComponentName name) { } }; private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient,0); mBinderPool = null; connectBinderPoolService(); } }; public static class BinderPoolImpl extends IBinderPool.Stub {//BinderPoolImpl的实现,Service会调用 @Override public IBinder queryBinder(int binderCode) throws RemoteException {//queryBinder的具体实现 IBinder binder = null; switch (binderCode) { case BINDER_SECURITY_CENTER: { binder = new SecurityCenterImpl(); break; } case BINDER_COMPUTE: { binder = new ComputeImpl(); break; } default: break; } return binder; } } }
Service的实现
public class BinderPoolService extends Service { private Binder mBinderPool = new BinderPool.BinderPoolImpl();//其实实现的是IBidnerPool public BinderPoolService() { } @Override public IBinder onBind(Intent intent) { return mBinderPool; } }
下面是客户端的实现:
public class BinderPoolActivity extends AppCompatActivity { private ISecurityCenter mSecurityCenter; private ICompute mCompute; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_binder_pool); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); new Thread(new Runnable() { @Override public void run() { doWork(); } }).start(); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); } private void doWork() { BinderPool binderPool = BinderPool.getInstance(BinderPoolActivity.this); IBinder securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURITY_CENTER); mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder); String msg = "helloworld-安卓"; L.d("content:" + msg); try { String password = mSecurityCenter.encrypt(msg); L.d("encrypt:" + password); L.d("decrypt:" + mSecurityCenter.decrypt(password)); } catch (RemoteException e) { e.printStackTrace(); } IBinder computeBinder = binderPool.queryBinder(BinderPool.BINDER_COMPUTE); mCompute = ComputeImpl.asInterface(computeBinder); try { L.d("3+5=" +mCompute.add(3,5)); } catch (RemoteException e) { e.printStackTrace(); } } }
执行后打印的Log:
03-05 11:52:20.182 4051-4530/? D/ipctest: content:helloworld-安卓
03-05 11:52:20.183 4051-4530/? D/ipctest: encrypt:6;221)1,2:s寗匍
03-05 11:52:20.183 4051-4530/? D/ipctest: decrypt:helloworld-安卓
03-05 11:52:20.189 4051-4530/? D/ipctest: 3+5=8
CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
主要方法
public CountDownLatch(int count);
public void countDown();
public void await() throws InterruptedException
构造方法参数指定了计数的次数
countDown方法,当前线程调用此方法,则计数减一
awaint方法,调用此方法会一直阻塞当前线程,直到计时器的值为0
在线程中执行线程池是因为,通过CountDownLatch将bindService这一异步操作转换成可同步操作,这就意味着他有可能是耗时的,然后就是Binder方法调用过程也是可能耗时的,因此不建议放在主线程去执行。注意到BinderPool是一个单例实现,因此在同一个进程中只会初始化一次,所以我们就提前初始化BinderPool,那么可能优化程序的体验,比如可以放在Application中提前初始化BinderPool,虽然不能保证当我们调用BinderPool时它一定是初始化好的,但是在大多数情况下,这种初始化工作(绑定远程服务)的时间开销(比如BinderPool没有提前初始化完成的话)是可以接受的。另外,BinderPool中有断线重连的机制,当远程服务意外终止时,BinderPool重新建立连接,这个时候如果业务模块中Binder调用出现异常,也需要手动去重新获取最新的Binder对象,这个是需要注意的。
使用BinderPool可以大大方便日常的开发工作,比如如果有一个新的业务模块需要添加新的AIDL,那么在它实现了自己的AIDL接口后,只需要修改BinderPoolImpl中的queryBinder方法,给自己添加一个新的BinderCode并返回对应的Binder对象即可,不需要做其他改动,也不需要创建新的Service。由此可见,BinderPool能过极大地提高AIDL的工作效率,并且可以避免大量的Service创建,因此,建议在AIDL开发工作中引入BinderPool机制。
选用合适的IPC
IPC方式的优缺点
名称 | 优点 | 缺点 | 适用场景 |
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到 进程间的即使通信 | 无并发访问情形,交换简单的 数据实时性不高的场景 |
ADIL | 功能强大,支持一对多并发通信, 支持实时通信 | 使用稍微复杂需要处理好线程同步 | 一对多通信且有RPC需求 |
Messenger | 功能一般,支持一对多串行通信, 支持实时通信 | 不能很好处理高并发情形,不支持RPC, 数据通过Message进行传输,因此只能 传输Bundle支持的数据类型 | 低并发的一对多即时通信, 无RPC需求,或者无须要返回 结果的RPC需要 |
ContentProvider | 在数据源访问方面功能强大支持 一对多并发数据共享通过Call方法 扩展其他操作 | 可以理解为受约束的AIDL,主要提供 数据的 CRUD | 一对多的进程间的数据共享 |
Socket | 功能强大,可以通过网络传输直接流 ,支持一对多并发实时通信 | 实现细节稍微有点繁琐,不支持直接RPC | 网络数据交换 |
简单易用 |
相关文章推荐
- 进程ID与进程组ID
- Nginx
- 一文看懂IC芯片生产流程:从设计到制造与封装
- JVM内存回收机制简述
- 素数专题
- CF 535D next数组
- c语言中getchar函数的用法,涉及EOF文件结束符的问题
- 用Modelsim SE 10.1a进行仿真的大致步骤
- 用Modelsim SE 10.1a进行仿真的大致步骤
- python面试题
- 本地存储
- Activity的启动模式
- [Unity游戏开发]向量在游戏开发中的应用(一)
- Win10正式版微软原版ISO系统镜像下载汇总(2016年2月版)
- POJ 1840 Eqs
- 1256 乘法逆元
- 关于java访问权限修饰词
- fibonacci
- React 入门实例教程 12个demo
- codeblocks单步调试简单操作