Android中使用socket通信實(shí)現(xiàn)消息推送的方法詳解
原理
最近用socket寫了一個(gè)消息推送的demo,在這里和大家分享一下。
主要實(shí)現(xiàn)了:一臺手機(jī)向另外一臺手機(jī)發(fā)送消息,這兩臺手機(jī)可以隨時(shí)自由發(fā)送文本消息進(jìn)行通信,類似我們常用的QQ。
效果圖:



原理:手機(jī)通過socket發(fā)送消息到服務(wù)器,服務(wù)器每接收到一條消息之后,都會把這條消息放進(jìn)一個(gè)messageList里面,服務(wù)器會不停地檢測messageList是否含有消息,如果有的話就會根據(jù)messageList里面item的數(shù)據(jù),推送到相應(yīng)的另一端手機(jī)上面。
下面簡單畫了一個(gè)圖來說明這個(gè)原理:
演示:手機(jī)客戶端client1發(fā)送消息msg1到手機(jī)客戶端client2,client2收到消息后回復(fù)msg2給client1

1.手機(jī)客戶端client1發(fā)送一條“msg1”的文本消息到服務(wù)器;
2.服務(wù)器收到來自client1的“msg1”消息后,把它add進(jìn)messageList里面;
3.服務(wù)器檢測到messageList里面含有消息(開啟服務(wù)器時(shí)就新建里一個(gè)檢測messageList的線程,線程里面有一個(gè)死循環(huán),用于不停檢測messageList是否含有消息);
4.服務(wù)器讀取消息數(shù)據(jù),如讀取到來自client1發(fā)給client2的消息“msg1”,那么服務(wù)器就把“msg1”推送到client2上;
5.client2檢測到服務(wù)器推送的消息,做出相應(yīng)的操作(如:震動(dòng)、鈴聲、顯示消息等);
6.client2接收到來自服務(wù)器推送的“msg1”消息后,client2也回復(fù)一條文本消息“msg2”給client1,此過程和client1發(fā)送消息給client2一樣。
7.最后,client2就可以顯示來自client1發(fā)送的消息“msg1”,而client1則可以顯示來自client2的回復(fù)消息“msg2”。
實(shí)現(xiàn)過程
根據(jù)消息推送的原理圖,我們的實(shí)現(xiàn)過程主要分為Server端和Client端,Server端采用Java的編程,而Client端則用Android編程。
所以在這里也分別創(chuàng)建了兩個(gè)工程SocketServer和SocketClient

我們先來看一下SocketMessage.java類:
public class SocketMessage {
public int to;//socketID,指發(fā)送給誰
public int from;//socketID,指誰發(fā)送過來的
public String msg;//消息內(nèi)容
public String time;//接收時(shí)間
public SocketThread thread;//socketThread下面有介紹
}
該類是一個(gè)消息類,用于表示消息是由誰發(fā)給誰的、消息內(nèi)容是什么、接收時(shí)間是多少,只有幾個(gè)屬性,比較簡單。
而MyServer.java類就相對比較多一些代碼:
package com.jimstin.server;
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;
/**
* 消息隊(duì)列,用于保存SocketServer接收來自于客戶機(jī)(手機(jī)端)的消息
*/
private ArrayList<SocketMessage> mMsgList = new ArrayList<SocketMessage>();
/**
* 線程隊(duì)列,用于接收消息。每個(gè)客戶機(jī)擁有一個(gè)線程,每個(gè)線程只接收發(fā)送給自己的消息
*/
private ArrayList<SocketThread> mThreadList = new ArrayList<SocketThread>();
/**
* 開啟SocketServer
*/
private void startSocket() {
try {
isStartServer = true;
int prot = 2000;//端口可以自己設(shè)置,但要和Client端的端口保持一致
mServer = new ServerSocket(prot);//創(chuàng)建一個(gè)ServerSocket
System.out.println("啟動(dòng)server,端口:"+prot);
Socket socket = null;
int socketID = 0;//Android(SocketClient)客戶機(jī)的唯一標(biāo)志,每個(gè)socketID表示一個(gè)Android客戶機(jī)
//開啟發(fā)送消息線程
startSendMessageThread();
//用一個(gè)循環(huán)來檢測是否有新的客戶機(jī)加入
while(isStartServer) {
//accept()方法是一個(gè)阻塞的方法,調(diào)用該方法后,
//該線程會一直阻塞,直到有新的客戶機(jī)加入,代碼才會繼續(xù)往下走
socket = mServer.accept();
//有新的客戶機(jī)加入后,則創(chuàng)建一個(gè)新的SocketThread線程對象
SocketThread thread = new SocketThread(socket, socketID++);
thread.start();
//將該線程添加到線程隊(duì)列
mThreadList.add(thread);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 開啟推送消息線程,如果mMsgList中有SocketMessage,則把該消息推送到Android客戶機(jī)
*/
public void startSendMessageThread() {
new Thread(){
@Override
public void run() {
super.run();
try {
/*如果isStartServer=true,則說明SocketServer已啟動(dòng),
用一個(gè)循環(huán)來檢測消息隊(duì)列中是否有消息,如果有,則推送消息到相應(yīng)的客戶機(jī)*/
while(isStartServer) {
//判斷消息隊(duì)列中的長度是否大于0,大于0則說明消息隊(duì)列不為空
if(mMsgList.size() > 0) {
//讀取消息隊(duì)列中的第一個(gè)消息
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寫進(jìn)json中的字符串?dāng)?shù)據(jù),末尾記得加換行符:"\n",否則在客戶機(jī)端無法識別
//因?yàn)锽ufferedReader.readLine()方法是根據(jù)換行符來讀取一行的
writer.write(json.toString()+"\n");
//調(diào)用flush()方法,刷新流緩沖,把消息推送到手機(jī)端
writer.flush();
System.out.println("推送消息成功:"+from.msg+">> to socketID:"+from.to);
break;
}
}
//每推送一條消息之后,就要在消息隊(duì)列中移除該消息
mMsgList.remove(0);
}
Thread.sleep(200);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
/**
* 定義一個(gè)SocketThread類,用于接收消息
*
*/
public class SocketThread extends Thread {
public int socketID;
public Socket socket;//Socket用于獲取輸入流、輸出流
public BufferedWriter writer;//BufferedWriter 用于推送消息
public BufferedReader reader;//BufferedReader 用于接收消息
public SocketThread(Socket socket, int count) {
socketID = count;
this.socket = socket;
System.out.println("新增一臺客戶機(jī),socketID:"+socketID);
}
@Override
public void run() {
super.run();
try {
//初始化BufferedReader
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
//初始化BufferedWriter
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));
//如果isStartServer=true,則說明SocketServer已經(jīng)啟動(dòng),
//現(xiàn)在需要用一個(gè)循環(huán)來不斷接收來自客戶機(jī)的消息,并作其他處理
while(isStartServer) {
//先判斷reader是否已經(jīng)準(zhǔn)備好
if(reader.ready()) {
/*讀取一行字符串,讀取的內(nèi)容來自于客戶機(jī)
reader.readLine()方法是一個(gè)阻塞方法,
從調(diào)用這個(gè)方法開始,該線程會一直處于阻塞狀態(tài),
直到接收到新的消息,代碼才會往下走*/
String data = reader.readLine();
//講data作為json對象的內(nèi)容,創(chuàng)建一個(gè)json對象
JSONObject json = new JSONObject(data);
//創(chuàng)建一個(gè)SocketMessage對象,用于接收json中的數(shù)據(jù)
SocketMessage msg = new SocketMessage();
msg.to = json.getInt("to");
msg.msg = json.getString("msg");
msg.from = socketID;
msg.time = getTime(System.currentTimeMillis());
//接收到一條消息后,將該消息添加到消息隊(duì)列mMsgList
mMsgList.add(msg);
System.out.println("收到一條消息:"+json.getString("msg")+" >>>> to socketID:"+json.getInt("to"));
}
//睡眠100ms,每100ms檢測一次是否有接收到消息
Thread.sleep(100);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 獲取指定格式的時(shí)間字符串,通過毫秒轉(zhuǎn)換日期
* @param millTime
*/
private String getTime(long millTime) {
Date d = new Date(millTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(d);
}
public static void main(String[] args) {
MyServer server = new MyServer();
server.startSocket();
}
}
2.SocketClient工程

該工程是一個(gè)Android的工程,只有一個(gè)MainActivity.java和activity_main.xml文件,
先看一下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">
<EditText
android:id="@+id/ip_edt"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="ip"
android:text="172.16.1.200"/>
<EditText
android:id="@+id/port_edt"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="port"
android:text="2000"/>
</LinearLayout>
<Button
android:id="@+id/start_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="start"/>
<EditText
android:id="@+id/socket_id_edt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="socketID"/>
<EditText
android:id="@+id/msg_edt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="5"
android:hint="content"
android:gravity="top"
/>
<Button
android:id="@+id/send_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="send"/>
<TextView
android:id="@+id/console_txt"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
android:id="@+id/clear_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="clear"/>
</LinearLayout>
效果圖:

MainActivity.java類:
package com.jimstin.socketclient;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.json.JSONObject;
import com.tencent.stat.MtaSDkException;
import com.tencent.stat.StatConfig;
import com.tencent.stat.StatService;
import android.R.integer;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;
public class MainActivity extends Activity implements OnClickListener {
private EditText mIPEdt, mPortEdt, mSocketIDEdt, mMessageEdt;
private static TextView mConsoleTxt;
private static StringBuffer mConsoleStr = new StringBuffer();
private Socket mSocket;
private boolean isStartRecieveMsg;
private SocketHandler mHandler;
protected BufferedReader mReader;//BufferedWriter 用于推送消息
protected BufferedWriter mWriter;//BufferedReader 用于接收消息
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mIPEdt = (EditText) findViewById(R.id.ip_edt);
mPortEdt = (EditText) findViewById(R.id.port_edt);
mSocketIDEdt = (EditText) findViewById(R.id.socket_id_edt);
mMessageEdt = (EditText) findViewById(R.id.msg_edt);
mConsoleTxt = (TextView) findViewById(R.id.console_txt);
findViewById(R.id.start_btn).setOnClickListener(this);
findViewById(R.id.send_btn).setOnClickListener(this);
findViewById(R.id.clear_btn).setOnClickListener(this);
mHandler = new SocketHandler();
}
/**
* 初始化socket
*/
private void initSocket() {
//新建一個(gè)線程,用于初始化socket和檢測是否有接收到新的消息
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String ip = mIPEdt.getText().toString();//IP
int port = Integer.parseInt(mPortEdt.getText().toString());//Socket
try {
isStartRecieveMsg = true;
mSocket = new Socket(ip, port);
mReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), "utf-8"));
mWriter = new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream(), "utf-8"));
while(isStartRecieveMsg) {
if(mReader.ready()) {
/*讀取一行字符串,讀取的內(nèi)容來自于客戶機(jī)
reader.readLine()方法是一個(gè)阻塞方法,
從調(diào)用這個(gè)方法開始,該線程會一直處于阻塞狀態(tài),
直到接收到新的消息,代碼才會往下走*/
String data = mReader.readLine();
//handler發(fā)送消息,在handleMessage()方法中接收
mHandler.obtainMessage(0, data).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.clear_btn:
mConsoleStr.delete(0, mConsoleStr.length());
mConsoleTxt.setText(mConsoleStr.toString());
break;
case R.id.start_btn:
if(!isStartRecieveMsg) {
initSocket();
}
break;
default:
break;
}
}
/**
* 發(fā)送
*/
private void send() {
new AsyncTask<String, Integer, String>() {
@Override
protected String doInBackground(String... params) {
sendMsg();
return null;
}
}.execute();
}
/**
* 發(fā)送消息
*/
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");
mConsoleTxt.setText(mConsoleStr);
} catch (Exception e) {
e.printStackTrace();
}
}
static class SocketHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch (msg.what) {
case 0:
try {
//將handler中發(fā)送過來的消息創(chuàng)建json對象
JSONObject json = new JSONObject((String)msg.obj);
mConsoleStr.append(json.getString("from")+":" +json.getString("msg")+" "+getTime(System.currentTimeMillis())+"\n");
//將json數(shù)據(jù)顯示在TextView中
mConsoleTxt.setText(mConsoleStr);
} catch (Exception e) {
e.printStackTrace();
}
break;
default:
break;
}
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
isStartRecieveMsg = false;
}
private static String getTime(long millTime) {
Date d = new Date(millTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(d);
}
}
以上代碼的注釋都比較詳細(xì),就不再多說了。
- Android Socket通信詳解
- Android編程之客戶端通過socket與服務(wù)器通信的方法
- Android中Socket通信的實(shí)現(xiàn)方法概述
- python服務(wù)器與android客戶端socket通信實(shí)例
- android利用websocket協(xié)議與服務(wù)器通信
- 詳解Android 基于TCP和UDP協(xié)議的Socket通信
- Android Socket通信實(shí)現(xiàn)簡單聊天室
- Android中socket通信的簡單實(shí)現(xiàn)
- Android開發(fā)中Socket通信的基本實(shí)現(xiàn)方法講解
- Android Socket通信的簡單實(shí)現(xiàn)
相關(guān)文章
Android IPC機(jī)制綁定Service實(shí)現(xiàn)本地通信
本文主要介紹Android IPC機(jī)制綁定Service 實(shí)現(xiàn)本地通信,通過圖解,代碼等方式給大家解釋Android IPC機(jī)制,需要參考的同學(xué)可以看一下2016-07-07
Activity與Service之間交互并播放歌曲的實(shí)現(xiàn)代碼
以下是對Activity與Service之間交互并播放歌曲的實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下2013-07-07
Android Doze模式下Alarm定時(shí)任務(wù)實(shí)現(xiàn)流程詳解
這篇文章主要介紹了Android Doze模式下Alarm定時(shí)任務(wù)實(shí)現(xiàn),Android的定時(shí)任務(wù)有兩種,一種是JavaAPI提供的Timer類,另一種就是使用Android的Alarm機(jī)制2022-11-11
Android編程實(shí)現(xiàn)將時(shí)間轉(zhuǎn)化成幾分鐘前、幾天前等形式的工具類
這篇文章主要介紹了Android編程實(shí)現(xiàn)將時(shí)間轉(zhuǎn)化成幾分鐘前、幾天前等形式的工具類,涉及Android針對日期時(shí)間的相關(guān)運(yùn)算與判斷簡單操作技巧,需要的朋友可以參考下2018-02-02
Android getActivity()為空的問題解決辦法
這篇文章主要介紹了Android getActivity()為空的問題解決辦法的相關(guān)資料,導(dǎo)致apk空指針崩潰問題,很嚴(yán)重的問題,為了解決這問題,上網(wǎng)搜索了很多資料,需要的朋友可以參考下2017-07-07
Android獲取手機(jī)本機(jī)號碼的實(shí)現(xiàn)方法
這篇文章主要介紹了Android獲取手機(jī)本機(jī)號碼的實(shí)現(xiàn)方法的相關(guān)資料,希望通過本文大家能夠?qū)崿F(xiàn)這樣的方法,需要的朋友可以參考下2017-10-10

