接前六篇
基础篇
bundle篇
文件共享篇
Messenger篇
AIDL篇
ContentProvide篇
Socket也成为“套接字”,分为流式套接字和用户数据报套接字,分别对应网络通信中的TCP和UDP协议。TCP提供稳定可靠的双向通信连接,但是需要建立三次握手才能完成;UDP提供不稳定的单向通信,不可靠,但是效率高。Socket支持任意字节流的传输,不仅可以实现进程间通信,还可实现跨设备通信
本篇我们来用Socket实现进程间通信
1、我们需要新建两个项目,一个是Server,一个是Client。创建好之后,需要在两个项目的Manifest文件声明网络权限。除此以外还必须要注意不能再主线程中进行socket通信,否则会抛出android.os.NetworkOnMainThreadException异常
2、Server端
Server不需要界面,因此我只是在MainActivity中启动Server服务,在Server服务中,用一个子线程去获取客户端的请求,并在连接成功之后发送Welcome to chat room!到客户端,然后不断地读取和回复客户端的消息
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SocketServerService socketServerService = new SocketServerService();
socketServerService.onCreate();
}
}
public class SocketServerService extends Service {
private static final String TAG = "myServer";
private boolean mIsServiceOndestoryed = false;
@Override
public void onCreate(){
new Thread(new ServerRunnable()).start();
super.onCreate();
}
@Override
public void onDestroy(){
mIsServiceOndestoryed = true;
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private class ServerRunnable implements Runnable {
@Override
public void run() {
ServerSocket server = null;
try{
server = new ServerSocket(12345);//1-65535
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG,"establish socket server failed, port 12345, e = " + e);
return;
}
while (!mIsServiceOndestoryed){
try {
final Socket client = server.accept();//block
Log.d(TAG,"accept client");
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 sb = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
sb.println("Welcome to chat room!");
while (!mIsServiceOndestoryed){
String str = in.readLine();
Log.d(TAG,"msg from client: " + str);
if(null == str){
break;
}
Log.d(TAG,"send str = \'receive your message " + str + " \' to client");
sb.println( "I have receive your message \'" + str + "\'");
}
Log.d(TAG,"client, quite");
in.close();
sb.close();
client.close();
}
}
3、client 端
在client端我们定义一个聊天室的界面,用一个TextView 来显示聊天记录,用一个EditText来输入文字,还有一个 Button 来发送消息到服务端
public class MainActivity extends Activity {
private static final String TAG = "myClient";
private PrintWriter pw;
private Socket socket;
private LinearLayout layout;
private TextView textView;
private EditText editText;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
layout = new LinearLayout(this);
layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
layout.setOrientation(LinearLayout.VERTICAL);
layout.setBackgroundColor(Color.GRAY);
setContentView(layout);
textView = new TextView(this);
textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 600));
layout.addView(textView);
editText = new EditText(this);
editText.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
editText.setHint("在此输入消息");
layout.addView(editText);
button = new Button(this);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
button.setText("发送消息");
layout.addView(button,layoutParams);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final String str = editText.getText().toString();
if(!TextUtils.isEmpty(str) && null != pw){
new Thread() {
@Override
public void run() {
Log.d(TAG,"send str = " + str + " to server");
pw.println(str);
}
}.start();
String time = formatDateTime(System.currentTimeMillis());
textView.setText(textView.getText() + "self " + time + ":" + str + "\n");
editText.setText("");
}
}
});
new Thread(new Runnable() {
@Override
public void run() {
connectServer();
}
}).start();
}
public String formatDateTime(long time){
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
private void connectServer() {
Socket socket = null;
while (socket == null){
try {
//genymotion模拟器连电脑需要用电脑的ip(win系统可在命令提示符中输入ipconfig看到)
//将server和client运行在同一设备上用localhost
socket = new Socket("localhost",12345);
this.socket = socket;
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
Log.d(TAG,"connect server success");
} catch (IOException e) {
SystemClock.sleep(1000);
Log.d(TAG,"connect server failed, exception = " + e + " retry...");
}
}
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!this.isFinishing()){
String str = br.readLine();
Log.d(TAG,"receive server message: " + str);
if(str != null){
handler.obtainMessage(0,str).sendToTarget();
}
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
String time = formatDateTime(System.currentTimeMillis());
textView.setText(textView.getText() + "server " + time + ":" + (String) msg.obj + "\n");
}
}
};
@Override
protected void onDestroy() {
if(socket != null) {
try {
socket.shutdownInput();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(pw != null){
pw.close();
}
super.onDestroy();
}
}
4、运行结果
我们在同一个虚拟机上,运行Server和Client(先后顺序无关),然后我们就可以在客户端的界面上输入文字开始聊天了
到现在我们就将进程间通信的6中方式学完了,下面我们用一个表格来总结一下各个方式的特点和使适用场景
名称 | 优 点 | 缺 点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只支持Bundle支持的数据类型 | 四大组件间的进程通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间的即时通信 | 无并发访问,交换简单数据,实时性要求不高 |
AIDL | 功能强大,支持一对多、并发、实时通信 | 使用复杂,需要处理好线程同步 | 一对多且有RPC要求 |
Messenger | 功能一般,支持一对多串行通信,实时通信 | 不能很好的处理高并发的情形,不支持RPC,数据通过Messenger进行传输,因此只能传输Bundle类型数据 | 低并发的一对多计时通信,无RPC要求,或者无需要返回结果的RPC要求 |
ContentProvider | 在数据访问方面功能强大,支持一对多并发数据共享,可通过call方法扩展其他操作 | 可理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间数据共享 |
Socket | 功能强大,可以通过网络传输字节,支持一对多并发实时通信 | 实现稍微复杂,不支持RPC | 网络数据交换 |
特别感谢《Android 开发艺术探索》