4.使用ContentProvider
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 | 网络数据交换 |
简单易用 |