Android实现IPC进程间通信的6种方式 (七)Socket篇

接前六篇
基础篇
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(先后顺序无关),然后我们就可以在客户端的界面上输入文字开始聊天了
Android实现IPC进程间通信的6种方式 (七)Socket篇_第1张图片
到现在我们就将进程间通信的6中方式学完了,下面我们用一个表格来总结一下各个方式的特点和使适用场景

名称 优 点 缺 点 适用场景
Bundle 简单易用 只支持Bundle支持的数据类型 四大组件间的进程通信
文件共享 简单易用 不适合高并发场景,并且无法做到进程间的即时通信 无并发访问,交换简单数据,实时性要求不高
AIDL 功能强大,支持一对多、并发、实时通信 使用复杂,需要处理好线程同步 一对多且有RPC要求
Messenger 功能一般,支持一对多串行通信,实时通信 不能很好的处理高并发的情形,不支持RPC,数据通过Messenger进行传输,因此只能传输Bundle类型数据 低并发的一对多计时通信,无RPC要求,或者无需要返回结果的RPC要求
ContentProvider 在数据访问方面功能强大,支持一对多并发数据共享,可通过call方法扩展其他操作 可理解为受约束的AIDL,主要提供数据源的CRUD操作 一对多的进程间数据共享
Socket 功能强大,可以通过网络传输字节,支持一对多并发实时通信 实现稍微复杂,不支持RPC 网络数据交换

特别感谢《Android 开发艺术探索》

你可能感兴趣的:(Android进阶)