前言
做应用开发的时候关于Android中的多进程问题其实并不多见,不过面试当中还是会经常问到,这里就对常用的通信方式做一些基本的总结。
Bundle
Android四大组件可以通过Intent进行通信,Intent里的Bundle对象就是用来保存各种通信的数据,不过如果传递的是自定义类型的数据就需要支持序列化,可以是Serializable或者Parcelable两种类型。
// sender进程
Intent intent = new Intent("com.example.bundle.receiver");
intent.putExtra("message", "Bundle Hello world");
startActivity(intent);
// receiver进程
public class BundleReceiverActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bundle_receiver);
textView = findViewById(R.id.text);
textView.setText(getIntent().getStringExtra("message"));
}
}
<activity android:name=".BundleReceiverActivity">
<intent-filter>
<action android:name="com.example.bundle.receiver" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
当然发送进程的Activity和接收进程的Activity通过隐式跳转来实现通信,开发中使用最多的就是Activity之间的通信,其实这种方法很常见,其他的组件通信方式不再演示。
File
文件作为操作系统提供的功能,可以被多个进程同时访问,所以为了能够保证数据的一致性还需要在多进程访问的时候做加锁同步处理。Java中的FileLock文件锁对象能够保证多进程访问的同步效果,不过需要使用nio的FileChannel来获取FileLock。
// sender进程
File file = new File(Environment.getExternalStorageDirectory(), "readMe.txt");
FileChannel fileChannel = null;
FileLock fileLock = null;
try {
if (!file.exists()) {
file.createNewFile();
}
RandomAccessFile randomAccessFile = new RandomAccessFile(file.getAbsolutePath(), "rw");
fileChannel = randomAccessFile.getChannel();
fileLock = fileChannel.lock();
ByteBuffer byteBuffer = ByteBuffer.wrap(("File Hello World" + (count += 111)).getBytes());
fileChannel.write(byteBuffer);
Toast.makeText(this, "写入" + new String(byteBuffer.array()), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.close(fileChannel);
if (fileLock != null) {
try {
fileLock.release();
} catch (IOException e) {
e.printStackTrace();
}
}
}
发送数据进程通过FileLock独占当前的文件并写入数据,而接收进程则需要获取独占锁之后才能够读取到文件内容。
File file = new File(Environment.getExternalStorageDirectory(), "readMe.txt");
FileChannel fileChannel = null;
FileLock fileLock = null;
if (file.exists()) {
try {
RandomAccessFile randomAccessFile = new RandomAccessFile(file.getAbsolutePath(), "rw");
fileChannel = randomAccessFile.getChannel();
fileLock = fileChannel.lock();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
fileChannel.read(byteBuffer);
Toast.makeText(this, new String(byteBuffer.array()), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.close(fileChannel);
if (fileLock != null) {
try {
fileLock.release();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
AIDL
AIDL也就是Android Interface Definition Language(Android 接口定义语言),是Android自身提供的一种进程键通信机制,可以使用bindService来获取另外一个进程里提供的Binder对象,通过这个远程Binder实现进程间的通信。
在Android Studio里添加aidl文件,aidl的语法和java很像但又不完全一样,下面的代码就是定义一个发送的接口。
// ICommunicate.aidl
package com.example.process;
interface ICommunicate {
void send(String str);
}
Android Studio会自动为aidl文件生成Java代码,在接收者进程的Service中返回自定义的Stub对象。
public class AIDLService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new Hello();
}
private class Hello extends ICommunicate.Stub {
@Override
public void send(final String str) throws RemoteException {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
}
});
}
}
}
为了能够向接受者进程发送消息,发送者进程调用bindService方法获取这个自定义的Binder对象。
Intent intent = new Intent();
intent.setAction("com.example.aidl.receiver");
intent.setPackage("com.example.receiver");
bindService(intent, new AIDLCallback(), BIND_AUTO_CREATE);
private class AIDLCallback implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
communicate = ICommunicate.Stub.asInterface(service);
sendAIDLMessage();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
private void sendAIDLMessage() {
try {
communicate.send( "Hello world from AIDL" + (count += 111));
} catch (RemoteException e) {
e.printStackTrace();
}
}
Messenger
Messenger是Android系统提供的对AIDL功能的简单封装,同样可以使用bindService来获取远程进程的Messenger内部Binder对象,这样两者就可以通过Messenger内部的Binder实现进程间通信。在接受者的Service里将Messenger内部的Binder返回。
// 接收者所在的服务
public class ReceiverService extends Service {
private class MessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
Bundle bundle = msg.getData();
Toast.makeText(getApplicationContext(), bundle.getString("message"), Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Messenger messenger = new Messenger(messageHandler);
return messenger.getBinder();
}
private MessageHandler messageHandler = new MessageHandler();
}
发送者进程通过bindService获取这个Binder对象。
Intent intent = new Intent();
intent.setAction("com.example.messenger.receiver");
intent.setPackage("com.example.receiver");
bindService(intent, new ServiceCallback(), BIND_AUTO_CREATE);
private class ServiceCallback implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
sender = new Messenger(service);
sendMessage();
}
@Override
public void onServiceDisconnected(ComponentName name) {
sender = null;
}
}
private void sendMessage() {
Message message = Message.obtain();
message.what = 1;
String str = "Hello world from Messenger" + (count += 111);
Bundle bundle = new Bundle();
bundle.putString("message", str);
message.setData(bundle);
try {
sender.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
Socket
Socket是操作系统提供的网络连接对象,它不止能支持本地的进程间通信还支持不同网络主机键的进程通信,不过这种比较通用的通信手段导致它相对其他通信方式速度更慢。首先在接收者进程需要创建ServerSocket对象。
new Thread(new Runnable() {
@Override
public void run() {
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(8888);
socket = serverSocket.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
final StringBuilder stringBuilder = new StringBuilder();
String str;
while ((str = reader.readLine()) != null) {
stringBuilder.append(str);
}
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, stringBuilder.toString(), Toast.LENGTH_SHORT).show();
}
});
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
发送者进程连接ServerSocket会导致accept方法返回并且生成一个服务端的Socket,这样客户端和服务端两个Socket就能相互通信了。
new Thread(new Runnable() {
@Override
public void run() {
Socket sock = null;
try {
sock = new Socket("127.0.0.1", 8888);
PrintWriter printWriter = new PrintWriter(sock.getOutputStream());
printWriter.write("Socket Hello World" + (count += 111));
printWriter.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (sock != null) {
try {
sock.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
socket.post(new Runnable() {
@Override
public void run() {
socket.setEnabled(true);
}
});
}
}).start();