Android中使用socket通信實(shí)現(xiàn)消息推送的方法詳解
原理
最近用socket寫了一個消息推送的demo,在這里和大家分享一下。
主要實(shí)現(xiàn)了:一臺手機(jī)向另外一臺手機(jī)發(fā)送消息,這兩臺手機(jī)可以隨時自由發(fā)送文本消息進(jìn)行通信,類似我們常用的QQ。
效果圖:
原理:手機(jī)通過socket發(fā)送消息到服務(wù)器,服務(wù)器每接收到一條消息之后,都會把這條消息放進(jìn)一個messageList里面,服務(wù)器會不停地檢測messageList是否含有消息,如果有的話就會根據(jù)messageList里面item的數(shù)據(jù),推送到相應(yīng)的另一端手機(jī)上面。
下面簡單畫了一個圖來說明這個原理:
演示:手機(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ù)器時就新建里一個檢測messageList的線程,線程里面有一個死循環(huán),用于不停檢測messageList是否含有消息);
4.服務(wù)器讀取消息數(shù)據(jù),如讀取到來自client1發(fā)給client2的消息“msg1”,那么服務(wù)器就把“msg1”推送到client2上;
5.client2檢測到服務(wù)器推送的消息,做出相應(yī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)建了兩個工程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;//接收時間 public SocketThread thread;//socketThread下面有介紹 }
該類是一個消息類,用于表示消息是由誰發(fā)給誰的、消息內(nèi)容是什么、接收時間是多少,只有幾個屬性,比較簡單。
而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; /** * 消息隊列,用于保存SocketServer接收來自于客戶機(jī)(手機(jī)端)的消息 */ private ArrayList<SocketMessage> mMsgList = new ArrayList<SocketMessage>(); /** * 線程隊列,用于接收消息。每個客戶機(jī)擁有一個線程,每個線程只接收發(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)建一個ServerSocket System.out.println("啟動server,端口:"+prot); Socket socket = null; int socketID = 0;//Android(SocketClient)客戶機(jī)的唯一標(biāo)志,每個socketID表示一個Android客戶機(jī) //開啟發(fā)送消息線程 startSendMessageThread(); //用一個循環(huán)來檢測是否有新的客戶機(jī)加入 while(isStartServer) { //accept()方法是一個阻塞的方法,調(diào)用該方法后, //該線程會一直阻塞,直到有新的客戶機(jī)加入,代碼才會繼續(xù)往下走 socket = mServer.accept(); //有新的客戶機(jī)加入后,則創(chuàng)建一個新的SocketThread線程對象 SocketThread thread = new SocketThread(socket, socketID++); thread.start(); //將該線程添加到線程隊列 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已啟動, 用一個循環(huán)來檢測消息隊列中是否有消息,如果有,則推送消息到相應(yīng)的客戶機(jī)*/ while(isStartServer) { //判斷消息隊列中的長度是否大于0,大于0則說明消息隊列不為空 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寫進(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; } } //每推送一條消息之后,就要在消息隊列中移除該消息 mMsgList.remove(0); } Thread.sleep(200); } } catch (Exception e) { e.printStackTrace(); } } }.start(); } /** * 定義一個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)啟動, //現(xiàn)在需要用一個循環(huán)來不斷接收來自客戶機(jī)的消息,并作其他處理 while(isStartServer) { //先判斷reader是否已經(jīng)準(zhǔn)備好 if(reader.ready()) { /*讀取一行字符串,讀取的內(nèi)容來自于客戶機(jī) reader.readLine()方法是一個阻塞方法, 從調(diào)用這個方法開始,該線程會一直處于阻塞狀態(tài), 直到接收到新的消息,代碼才會往下走*/ String data = reader.readLine(); //講data作為json對象的內(nèi)容,創(chuàng)建一個json對象 JSONObject json = new JSONObject(data); //創(chuàng)建一個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()); //接收到一條消息后,將該消息添加到消息隊列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(); } } } /** * 獲取指定格式的時間字符串,通過毫秒轉(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工程
該工程是一個Android的工程,只有一個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() { //新建一個線程,用于初始化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()方法是一個阻塞方法, 從調(diào)用這個方法開始,該線程會一直處于阻塞狀態(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事件分發(fā)機(jī)制(上) ViewGroup的事件分發(fā)
這篇文章主要為大家詳細(xì)介紹了Android ViewGroup的事件分發(fā)機(jī)制上篇,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01Android中使用listview實(shí)現(xiàn)qq/微信好友列表
本文主要介紹了android中使用listview實(shí)現(xiàn)qq/微信好友列表(頭像,昵稱,個性簽名)的相關(guān)知識。具有很好的參考價值。下面跟著小編一起來看下吧2017-04-04關(guān)于Android短信驗(yàn)證碼的獲取的示例
本篇文章主要介紹了關(guān)于Android短信驗(yàn)證碼的獲取的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08Android序列化接口Parcelable與Serializable接口對比
我們使用 Intent 傳遞數(shù)據(jù)的時候,putExtra() 所支持的數(shù)據(jù)類型事有限的,當(dāng)需要傳遞自定義對象的時候就需要序列化。Serializable更簡單但是會把整個對象進(jìn)行序列化因此效率比Parcelable低一些2023-02-02Flutter模仿實(shí)現(xiàn)微信底部導(dǎo)航欄流程詳解
這篇文章主要介紹了Flutter模仿實(shí)現(xiàn)微信底部導(dǎo)航欄流程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-05-05利用Flutter實(shí)現(xiàn)“孔雀開屏”的動畫效果
這篇文章主要給大家介紹了關(guān)于如何利用Flutter實(shí)現(xiàn)“孔雀開屏”的動畫效果,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05Android實(shí)現(xiàn)檢測手機(jī)多點(diǎn)觸摸點(diǎn)數(shù)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)檢測手機(jī)多點(diǎn)觸摸點(diǎn)數(shù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05android中打開相機(jī)、打開相冊進(jìn)行圖片的獲取示例
本篇文章主要介紹了android中打開相機(jī)、打開相冊進(jìn)行圖片的獲取示例,非常具有實(shí)用價值,需要的朋友可以參考下。2017-01-01View觸發(fā)機(jī)制API實(shí)現(xiàn)GestureDetector OverScroller詳解
這篇文章主要為大家介紹了View觸發(fā)機(jī)制API實(shí)現(xiàn)GestureDetector OverScroller詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11關(guān)于AndroidStudio R文件莫名其妙缺失的快速解決方法
下面小編就為大家?guī)硪黄P(guān)于AndroidStudio R文件莫名其妙缺失的快速解決方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03