Android应用程序如何与app结合 安卓app之间如何通信_android tcp socket框架


最近去了一趟上海,外滩的景色还是很nice的,非常喜欢这种具有历史感的建筑。

很久没写文章,小姐姐最近打代码比较少,搞项管去。。。。。。程序媛才是小姐姐的最爱,记录文档写起来,哈哈哈。


在一些工具类开发中,经常涉及不同设备之间的通信,今天我就针对安卓手机和PC端的通信,做一些文档说明。

在安卓手机和PC端的TCP通信中,PC做为接收端(服务器Server端),以Android设备做为发送端(客户Client端)。

关于PC端开发工具,我用的是eclipse,大家也可以用IntelliJ IDEA。


Android应用程序如何与app结合 安卓app之间如何通信_完整mes代码(含客户端和server端_02


IntelliJ IDEA:业界公认最好的Java开发工具,平时用的最多。可以安装大量插件丰富功能,开发前端应用也不在话下!

demo再文章最后附上。

1、什么是TCP/IP协议?

百度百科的解释:Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。

TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。

通俗而言:

TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。

而IP是给因特网的每一台电脑规定一个地址。

2、什么是Socket?

Socket是应用层与TCP/IP协议簇通讯的中间抽象层,Socket是一组接口,在设计模式中,Socket的设计就是门面模式,它把复杂的TCP/IP协议簇的内容隐藏在套接字接口后面,用户无需关心协议的实现,只需使用Socket提供的接口即可。

Socket的类型有两种,一种是面向连接的TCP应用服务,一种是面向无连接的UDP(User Data Package)应用服务。通俗的理解就是,TCP方式是打电话(连接性),UDP方式是发短信(无连接)。


Ok,以上是一些概念的介绍,那么接下来就看下如何在Android上利用TCP/IP协议使用Socket与Server进行通讯吧!今天我们要使用的是面向连接的TCP方式。首先,在本机建立一个Java项目作为Server Client(这一部分也可以是用C或其他语言写的PC端TCP工具),JAVA代码如下:


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class Server implements Runnable {
   public static final int SERVERPORT = 3333;
   public void run() {
       try {
           System.out.println("Server: Connecting...");
           ServerSocket serverSocket = new ServerSocket(SERVERPORT);
           while (true) {
               System.out.println("Server:Connected.");
               Socket client = serverSocket.accept();
               System.out.println("Server: Receiving...");
               try {
                   BufferedReader in = new BufferedReader(
                   new InputStreamReader(client.getInputStream()));
                   String str = in.readLine();
                   System.out.println("Server: Received: '" + str + "'");
               } catch (Exception e) {
                   System.out.println("Server: Error");
                   e.printStackTrace();
               } finally {
                   client.close();
                   System.out.println("Server: Done.");
               }
           }
       } catch (Exception e) {
           System.out.println("Server: Error");
           e.printStackTrace();
       }
   }
   public static void main(String a[]) {
       Thread desktopServerThread = new Thread(new Server());
       desktopServerThread.start();

   }
}


以上设置PC端的端口为3333,IP地址可以通过计算机——网络查询,也可以ifconfig你的电脑

如我的是MAC电脑,在系统偏好—网络:


Android应用程序如何与app结合 安卓app之间如何通信_完整mes代码(含客户端和server端_03


然后是Android客户端的实现:


import android.app.Activity;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.xiaoniu.wifihotspotdemo.R;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class SocketAndrodActivity extends Activity {

    TextView text;
    EditText input,address,port;
    Socket socket;
    @Override
    public void onCreate(Bundle savedInstanceState) {

        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectDiskReads().detectDiskWrites().detectNetwork()
                .penaltyLog().build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .detectLeakedSqlLiteObjects().detectLeakedClosableObjects()
                .penaltyLog().penaltyDeath().build());

        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button = (Button) this.findViewById(R.id.btn_send);
        text=(TextView)findViewById(R.id.receive);
        input=(EditText)findViewById(R.id.input);
        address=(EditText)findViewById(R.id.address);
        port=(EditText)findViewById(R.id.port);
        address.setText("162.30.200.26");
        port.setText("205");
        button.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                try {
                   text.setText("Client:Connecting");
                    //IP地址和端口号(对应服务端),我这的IP是本地路由器的IP地址
                    String addStr=address.getText().toString().trim();
                    int portStr=Integer.parseInt(port.getText().toString().trim());
                    socket = new Socket(addStr, portStr);
                    if(socket==null){
                        Log.e("error","socket  null");
                        return;
                    }
                    //发送给服务端的消息
                    String message = input.getText().toString();
                    try {
                        text.setText("Client Sending: '" + message + "'");
                        //第二个参数为True则为自动flush
                        PrintWriter out = new PrintWriter(
                                new BufferedWriter(new OutputStreamWriter(
                                        socket.getOutputStream())), true);
                        out.println(message);
//			out.flush();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        //关闭Socket
                        if(socket!=null){
                            socket.close();
                        }
                        text.append("  Client:Socket closed");
                    }
                } catch (UnknownHostException e1) {
                    e1.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        });
    }
}


以上的码块是我在出现android.os.NetworkOnMainThreadException:问题后,看到网上的一个分享,该分析认为:


一般主线程(跑UI的线程)不推荐使用网络相关的服务,因为使用到网络可能会导致运行UI的线程很卡,最终很可能会出现ARN(Application Not Responding)的提示框。
     * 所以如果在主线程中运用到了网络相关的服务(例如本例的TCP连接),会在LogCat中出现NetworkOnMainThreadException的异常。
     * <p>
     * StrictMode则是Android专门为开发者提供的工具类,你可以决定当出现诸如NetworkOnMainThreadException异常时,将异常存至LogCat中,
     * 而不是直接终止你的安卓应用的运行。由于本程序为示例代码,故不在安卓虚拟机中加入线程之类的东西去开启TCP客户端,
     * 而用了StrictMode忽略NetworkOnMainThreadException异常。


因此在 super.onCreate(savedInstanceState);前加了


StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectDiskReads().detectDiskWrites().detectNetwork()
                .penaltyLog().build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .detectLeakedSqlLiteObjects().detectLeakedClosableObjects()
                .penaltyLog().penaltyDeath().build());


感谢他的分享。

我最终采用的方式是,多线程处理方式,handle更新UI状态,完整代码如下:


import com.xiaoniu.wifihotspotdemo.R;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
/**
 * author Created by michelle on 2019/5/23.
 * email: 1031983332@qq.com
 */

public class SocketAndrodActivity extends Activity {

    private static final int CONNECTING = 0;
    private static final int SENDING = 1;
    private static final int CLOSE = 2;
    private static final int RECEIVE = 3;

    TextView text;
    EditText input, address, port;
    Socket socket;

    String addStr,sendMsg,receiveMsg;
    int portStr;


    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == CONNECTING) {
                text.setText("正在连接中......");
            }else if(msg.what==SENDING){
                text.setText("Client Sending: '" + sendMsg + "'");
            }else if(msg.what==CLOSE){
                text.append("socket close");
            }else if(msg.what==RECEIVE){
                text.setText("Client Receiveing: '" + receiveMsg + "'");
            }


        }
    };


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //方法一、用StrictMode模式,调用以下码块,删除Thread和handle方式

        // setInternet();

        setContentView(R.layout.main);
        Button button = (Button) this.findViewById(R.id.btn_send);
        text = (TextView) findViewById(R.id.receive);
        input = (EditText) findViewById(R.id.input);
        address = (EditText) findViewById(R.id.address);
        port = (EditText) findViewById(R.id.port);
//        address.setText("162.30.200.26");
//        port.setText("205");
        address.setText("162.30.202.220");
        port.setText("3333");
        button.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {

                addStr = address.getText().toString().trim();
                portStr = Integer.parseInt(port.getText().toString().trim());
                //方法二、用多线程的方式
                new WorkThread().start();
            }
        });
    }



    //工作线程
    private class WorkThread extends Thread {
        @Override
        public void run() {
            //处理比较耗时的操作
            //数据处理完成后,关于UI的更新要通过handler发送消息
            Message msg = new Message();
            Message msg1 = new Message();
            Message msg2 = new Message();
            msg.what = CONNECTING;
            handler.sendMessage(msg);
            try {
                socket = new Socket(addStr, portStr);
                if (socket == null) {
                    Log.e("error", "socket  null");
                    return;
                }
                //发送给服务端的消息
                sendMsg = input.getText().toString();
                msg1.what = SENDING;
                handler.sendMessage(msg1);

                PrintWriter out = new PrintWriter(
                        new BufferedWriter(new OutputStreamWriter(
                                socket.getOutputStream())), true);
                out.println(sendMsg);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                //关闭Socket
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                msg2.what = CLOSE;
                handler.sendMessage(msg2);
            }



        }
    }


 
}


布局为:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入ip地址"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:hint="请输入端口号"
        android:id="@+id/port"
        />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:hint="请输入发送的信息"
        android:id="@+id/input"
        />


    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:id="@+id/btn_send"
        android:layout_marginTop="10dp"
        android:text="send"/>


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/receive"
        android:text="收到信息为: "
        />
</LinearLayout>



在这个过程,需要注意的点有:

1、权限

在AndroidManifest.xml文件中,必须加入

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

2、IP地址和端口必须正确

查PC端IP的方式,我这边就不再复述了。

值得说明的是:有的window上的PC端工具装在mac的虚拟机上面,虚拟机识别的ip不是mac的ip地址,安卓没办法链接。

3、线程问题

处理数据用子线程,更新UI用主线程。在子线程中可以通过handle进行传数据给主线程。