安卓socket一对一聊天小demo

一、最近,学习了安卓socket通信。

刚好手上又需要一个客服聊天的功能,可惜这个demo最终还是不太符合我项目的要求,因此,今天先写下来供以后复习使用。
之所以选择socket是因为网上推送,IM等SDK有很多,不仅要收费,用户信息那些都要存到他们的平台,不太安全。

二、此demo已实现功能

1、多台手机进行连接,可选择对应的id号进行聊天。
2、安卓聊天界面的实现。

三、还想实现的功能但仍未实现的功能

1、离线消息。
2、聊天记录保存到手机数据库。
3、通过socket id绑定用户账号,当某用户发信息过来的时候,可以通过用户来得到对应的socket id,从而把消息转发出去。(这一个我是真的想不到,学业不精啊,求赐教!)。

四、demo效果

在这里插入图片描述
在这里插入图片描述

五、原理

当每有一台手机连接到服务器时,会对该手机生成一个线程和生成一个socket id,手机之间可以通过对应的socket id和目标手机聊天。

六、功能实现

1、服务器的实现(eclipse软件,引用了一个json.jar包)

SocketMessage.java

import com.jimstin.server.MyServer.SocketThread;
public class SocketMessage {

	public int to;
	public int from;
	public String msg;
	public String time;
	public SocketThread thread;
	public String username;
	public String user;
}

MyServer.java

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import org.json.JSONObject;
import com.jimstin.msg.SocketMessage;

public class MyServer {

	private boolean isStartServer;
	private ServerSocket mServer;

	private ArrayList<SocketMessage> mMsgList = new ArrayList<SocketMessage>();
	private ArrayList<SocketThread> mThreadList = new ArrayList<SocketThread>();
	/**
	 * 开启服务器
	 */
	private void startSocket() {
		try {
			isStartServer = true;
			int port = 2000;
			mServer = new ServerSocket(port);
			System.out.println("启动server,端口:"+port);
			Socket socket = null;
			int socketID = 0;
			startMessageThread();
			while(isStartServer) {
				socket = mServer.accept();
				SocketThread thread = new SocketThread(socket, socketID++);
				thread.start();
				mThreadList.add(thread);
			}
    		
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	 * 转发消息
	 */
	public void startMessageThread() {
		new Thread(){
			@Override
			public void run() {
				super.run();
				try {
					while(isStartServer) {
						if(mMsgList.size() > 0) {
							SocketMessage from = mMsgList.get(0);
							for(SocketThread to : mThreadList) {
								if(to.socketID == from.to) {
									BufferedWriter writer = to.writer;
									JSONObject json = new JSONObject();
									json.put("from", from.from);
									json.put("msg", from.msg);
									json.put("time", from.time);
									writer.write(json.toString()+"\n");
									writer.flush();
									System.out.println("转发消息成功:"+from.msg+">> to socketID:"+from.to);
									break;
								}
							}
							mMsgList.remove(0);
						}
						Thread.sleep(200);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}.start();
	}
	/**
	 * 接收来自客户端的消息
	 * json数组包括socketID,msg
	 * @author Administrator
	 *
	 */
	public class SocketThread extends Thread {
		
		public int socketID;
		public Socket socket;
		public BufferedWriter writer;
		public BufferedReader reader;
		
		public SocketThread(Socket socket, int count) {
			socketID = count;
			this.socket = socket;
			System.out.println("新增一台客户机,socketID:"+socketID);
			
		}
		
		@Override
		public void run() {
			super.run();

			try {
				reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
				writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));
				while(isStartServer) {
					if(reader.ready()) {
						String data = reader.readLine();
						JSONObject json = new JSONObject(data);
						SocketMessage msg = new SocketMessage();
						msg.to = json.getInt("to");
						msg.msg = json.getString("msg");
					
						msg.from = socketID;
						
						msg.time = getTime(System.currentTimeMillis());
						mMsgList.add(msg);
						System.out.println("收到一条消息:"+json.getString("msg")+" >>>> to socketID:"+json.getInt("to"));
					}
					Thread.sleep(100);
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			}  
		}
	}
	/**
	 * 得到此刻的时间
	 * @param millTime
	 * @return
	 */
	private String getTime(long millTime) {
		Date d = new Date(millTime);
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println(sdf.format(d));
		return sdf.format(d);
	}
	public static void main(String[] args) {
		MyServer server = new MyServer();
		server.startSocket();
	}

}

2、安卓实现

(1)先来看看manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jw.socketclient"
    android:versionCode="1"
    android:versionName="1.0" >

	<uses-permission android:name="android.permission.INTERNET"/>
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
	<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
	<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
	
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.jw.socketclient.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

(2)用到的依赖:

  implementation 'androidx.legacy:legacy-support-v4:1.0.0'
  implementation 'androidx.recyclerview:recyclerview:1.1.0'

(3)界面:

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical" >
    
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

    </LinearLayout>
	<LinearLayout
		android:layout_width="fill_parent"
		android:layout_height="wrap_content">
		<Button
			android:id="@+id/start_btn"
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			android:layout_weight="1"
			android:text="start"/>
		<Button
			android:id="@+id/stop_btn"
			android:text="stop"
			android:layout_weight="1"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"/>
	</LinearLayout>

    <EditText 
        android:id="@+id/socket_id_edt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="socketID"/>

	<androidx.recyclerview.widget.RecyclerView

		android:id="@+id/rv"
		android:layout_width="match_parent"
		android:layout_height="0dp"
		android:layout_weight="1"/>
	<!--<TextView
		android:id="@+id/console_txt"
		android:layout_width="match_parent"
		android:layout_height="0dp"
		android:layout_weight="1"/>
		-->
	<LinearLayout
		android:layout_width="match_parent"
		android:layout_height="50dp"
		android:background="#E3ECE3"
		>
		<EditText
			android:id="@+id/msg_edt"
			android:layout_marginLeft="30dp"
			android:layout_marginRight="10dp"
			android:layout_width="160dp"
			android:layout_height="40dp"
			android:background="#ffffff"
			android:layout_gravity="center"
			android:layout_weight="9"
			/>
		<Button
			android:id="@+id/send_btn"
			android:layout_width="40dp"
			android:layout_height="35dp"
			android:text="发送"
			android:textColor="#ffffff"
			android:layout_gravity="center"
			android:background="#0B9C10"
			android:layout_weight="1"/>
	</LinearLayout>


</LinearLayout>

item.xml(右边的界面,展示自己发出的消息)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:padding="6dp"
    >
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#ECE8E8"
            android:padding="2dp"
            android:textColor="#ffffff"
            android:textSize="12sp" />
    </LinearLayout>

    <RelativeLayout
        android:layout_marginTop="5dp"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        >
        <ImageView
            android:id="@+id/iv_userhead"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_alignParentRight="true"
            android:background="@drawable/head_img"
            android:focusable="false" />
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/iv_userhead"
            android:textColor="#818181"
            android:textSize="15sp" />
        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="10dp"
            android:layout_toLeftOf="@id/iv_userhead"
            android:clickable="true"
            android:focusable="true"
            android:lineSpacingExtra="2dp"
            android:minHeight="50dp"
            android:gravity="center"
            android:background="@drawable/chat_to"
            android:textColor="#ff000000"
            android:textSize="15sp" />
    </RelativeLayout>

</LinearLayout>

item2.xml(左边界面,展示别人发过来的消息)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="6dp"
    >
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tv_time2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#ECE8E8"
            android:padding="2dp"
            android:textColor="#ffffff"
            android:textSize="12sp" />
    </LinearLayout>
    <RelativeLayout
        android:layout_marginTop="5dp"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        >
        <ImageView
            android:id="@+id/iv_userhead2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_alignParentLeft="true"
            android:background="@drawable/feedback"
            android:focusable="false" />
        <TextView
            android:id="@+id/tv_name2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/iv_userhead2"
            android:textColor="#818181"
            android:textSize="15sp"
            android:layout_alignParentLeft="true"
            />
        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@id/iv_userhead2"
            android:clickable="true"
            android:focusable="true"
            android:gravity="center"
            android:lineSpacingExtra="2dp"
            android:minHeight="50dp"
            android:background="@drawable/chat_from"
            android:textColor="#ff000000"
            android:textSize="15sp"
            />
    </RelativeLayout>
</LinearLayout>

(4)4张图,随便网上下载几张矢量图即可。

在这里插入图片描述

(5)活动:

MainActivity.java

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import org.json.JSONObject;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.app.Activity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class MainActivity extends Activity implements OnClickListener {

	private EditText mSocketIDEdt, mMessageEdt;
	private StringBuffer mConsoleStr = new StringBuffer();
	private Socket mSocket;
	private boolean isStartRecieveMsg;
	private RecyclerView rv;
	private SocketHandler mHandler;
	protected BufferedReader mReader;
	protected BufferedWriter mWriter;
	private static final int COMPLETED = 1;
	private ArrayList<MyBean> list;
	private MyAdapter adapter;
	Thread thread;
	private static boolean flag=false;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();


	}

	private void initView() {

		mSocketIDEdt = (EditText) findViewById(R.id.socket_id_edt);
		mMessageEdt = (EditText) findViewById(R.id.msg_edt);
		findViewById(R.id.start_btn).setOnClickListener(this);
		findViewById(R.id.send_btn).setOnClickListener(this);
		findViewById(R.id.stop_btn).setOnClickListener(this);
		mHandler = new SocketHandler();
		rv = (RecyclerView) findViewById(R.id.rv);
		list = new ArrayList<>();
		adapter = new MyAdapter(this);
		rv.setAdapter(adapter);
		LinearLayoutManager manager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false);
		rv.setLayoutManager(manager);
	}

	private void initSocket() {
		thread = new Thread(new Runnable() {

			@Override
			public void run() {

				try {
					isStartRecieveMsg = true;
					mSocket = new Socket("192.168.2.172", 2000);
					mReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), "utf-8"));
					mWriter = new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream(), "utf-8"));
					while(isStartRecieveMsg) {
						if(mReader.ready()) {
							mHandler.obtainMessage(0, mReader.readLine()).sendToTarget();

						}
						Thread.sleep(200);
					}
					mWriter.close();
					mReader.close();
					mSocket.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		thread.start();

	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
			case R.id.send_btn:
				send();
				break;
			case R.id.start_btn:
				if(!isStartRecieveMsg) {
					initSocket();
				}
				break;
			case R.id.stop_btn:
				flag=true;
				System.err.println("中断");
			default:
				break;
		}
	}

	private void send() {

		new AsyncTask<String, Integer, String>() {

			@Override
			protected String doInBackground(String... params) {
				sendMsg();
				return null;
			}
		}.execute();
	}

	protected void sendMsg() {
		try {
			String socketID = mSocketIDEdt.getText().toString().trim();
			String msg = mMessageEdt.getText().toString().trim();
			JSONObject json = new JSONObject();
			json.put("to", socketID);
			json.put("msg", msg);
			mWriter.write(json.toString()+"\n");
			mWriter.flush();
			mConsoleStr.append("我" +msg+"   "+getTime(System.currentTimeMillis())+"\n");
			MyBean bean = new MyBean("我",2,msg,getTime(System.currentTimeMillis()));
			list.add(bean);
			/**
			 * 在子线程更新UI
			 */
			Message message = new Message();
			message.what = COMPLETED;
			mHandler.sendMessage(message);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	class SocketHandler extends Handler {

		@Override
		public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			super.handleMessage(msg);
			switch (msg.what) {
				case 0:
					try {
						JSONObject json = new JSONObject((String)msg.obj);
						MyBean bean = new MyBean(json.getString("from"),1,json.getString("msg"),getTime(System.currentTimeMillis()));
						list.add(bean);
					} catch (Exception e) {
						e.printStackTrace();
					}
					// 向适配器set数据
					adapter.setData(list);
					rv.setAdapter(adapter);
					LinearLayoutManager manager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false);
					rv.setLayoutManager(manager);
					rv.scrollToPosition(adapter.getItemCount()-1);//recycleview滑到底部

					break;
				case 1:

					// 向适配器set数据
					adapter.setData(list);
					rv.setAdapter(adapter);
					 manager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false);
					rv.setLayoutManager(manager);
					rv.scrollToPosition(adapter.getItemCount()-1);//recycleview滑到底部

					break;
				default:
					break;
			}
		}
	}

	@Override
	public void onBackPressed() {
		super.onBackPressed();
		isStartRecieveMsg = false;
	}

	private String getTime(long millTime) {
		Date d = new Date(millTime);
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println(sdf.format(d));
		return sdf.format(d);
	}

}

(6)适配器:

MyAdapter.java

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter {

    private Context context;
    private ArrayList<MyBean> data;
    private static final int TYPEONE = 1;
    private static final int TYPETWO = 2;

    public MyAdapter(Context context) {
        this.context = context;
    }

    public void setData(ArrayList<MyBean> data) {
        this.data = data;
        notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        return data.get(position).getNumber();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder holder = null;
        switch (viewType){
            case TYPEONE:
                View view = LayoutInflater.from(context).inflate(R.layout.item2,parent,false);
                holder = new OneViewHolder(view);
                break;
            case TYPETWO:
                View view1 = LayoutInflater.from(context).inflate(R.layout.item,parent,false);
                holder = new TwoViewHolder(view1);
                break;
        }
        return holder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int itemViewType = getItemViewType(position);
        switch (itemViewType){
            case TYPEONE:
                OneViewHolder oneViewHolder = (OneViewHolder) holder;
                oneViewHolder.tv2.setText(data.get(position).getData());
                oneViewHolder.name2.setText(data.get(position).getName());
                oneViewHolder.time2.setText(data.get(position).getTime());
                break;
            case TYPETWO:
                TwoViewHolder twoViewHolder = (TwoViewHolder) holder;
                twoViewHolder.tv1.setText(data.get(position).getData());
                twoViewHolder.name1.setText(data.get(position).getName());
                twoViewHolder.time1.setText(data.get(position).getTime());
                break;
        }
    }

    @Override
    public int getItemCount() {
        return  data != null && data.size() > 0 ? data.size() : 0;
    }

    class OneViewHolder extends RecyclerView.ViewHolder{
        private TextView tv2;
        private TextView name2,time2;
        public OneViewHolder(View itemView) {
            super(itemView);
            tv2 = (TextView) itemView.findViewById(R.id.tv2);
            name2 = (TextView) itemView.findViewById(R.id.tv_name2);
            time2 = (TextView) itemView.findViewById(R.id.tv_time2);

        }
    }

    class TwoViewHolder extends RecyclerView.ViewHolder{
        private TextView tv1;
        private TextView name1,time1;
        public TwoViewHolder(View itemView) {
            super(itemView);
            tv1 = (TextView) itemView.findViewById(R.id.tv);
            name1 = (TextView) itemView.findViewById(R.id.tv_name);
            time1 = (TextView) itemView.findViewById(R.id.tv_time);
        }
    }
}

MyBean.java

public class MyBean {
    private String data;
    private String time,name;
    private int number;

    public MyBean() {
    }

    public MyBean(String name, int number,String data,String time) {
        this.data = data;
        this.number = number;
        this.name = name;
        this.time = time;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

(7)测试效果:

首先在eclipse运行MyServer.java,然后运行安卓模拟器,点击start,往第一个编辑框输入0,往第二个编辑器输入hello,点击发送,模拟器出现两个hello即成功。可以多开几个模拟器试试。

七、源码

源码
提取码:6x59

八、不足之处:

该demo还有很多需要改善的地方,例如没有离线消息功能,没有聊天记录的存储。希望各位大神可以多多指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一粒程序米

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值