socket工具android,Android通过socket长连接实现推送
工具:Android studio
軟件方法及協議:socket、protobuf
實現原理:
通過本地建立一個socket,綁定服務器IP和port,然后connect,再開啟另外線程定時心跳(注意這里的心跳不是自定義發送數據,而是采用socket本身的心跳功能sendUrgentData,否則有坑),心跳失敗則自動重連,另一方面,啟動循環從緩存獲取socket數據。
大致框架
推送實現流程圖
實現代碼:
public class QpushClient implements Runnable {
protected static volatile QpushClient mInstance;//volatile可保證可見性和有序性
protected Handler mHandler;
protected InetSocketAddress mAddress;
String mIMEI;
protected String TAG = "QpushClient";
//socket連接的超時時間
private final int CONNECT_TIME_OUT = 5 * 1000;
//巡檢周期
private final int CHECK_PERIOD = 2 * 1000;
//連接嘗試間隔時間
private final int CONNECT_PERIOD = 30 * 1000;
private final int HEARTBEART_PERIOD = 30 * 1000;
//若連接失敗或響應失敗,則嘗試次數為9,若仍無效,則不再嘗試
private final int CONNECT_TRY_TIMES = 9;
private final int SEND_MSG_TYPE_HEARTBEAT = 1; //心跳包
private final int SEND_MSG_TYPE_SOCKET_LOGIN = 2; //發送socket登錄包
//連接嘗試次數
private int mConnectCount;
Socket mClientSocket;
String mHost;
int mPort;
//設置是否去讀取數據
boolean isStartRecieveMsg = false;
//開啟心跳檢測
boolean isKeepHeartBeat = false;
BufferedReader mReader;
ScheduledExecutorService executor;//定位定時器
HeartBeatTask mHeartBeatTask;
private QpushClient(Handler handler) {
mHandler = handler;
}
public static QpushClient getInstance(Handler handler) {
if (mInstance == null) {
synchronized(QpushClient.class){ //線程安全,所以加鎖
if(mInstance == null){
mInstance = new QpushClient(handler);
}}
}
return mInstance;
}
public void init(String host, int port,String imei) {
mHost = host;
mPort = port;
mIMEI = imei;
new Thread(this).start();
isStartRecieveMsg = true;
isKeepHeartBeat = true;
}
@Override
public void run() {
mAddress = new InetSocketAddress(getIP(mHost), mPort);
//嘗試連接,若未連接,則設置嘗試次數
while (mConnectCount < CONNECT_TRY_TIMES) {
connect();
if (!mClientSocket.isConnected()) {
mConnectCount++;
sleep(CONNECT_PERIOD);
} else {
mConnectCount = 0;//連接上,則恢復置0
break;
}
}
if (mClientSocket.isConnected()) {
keepHeartBeat();
recvProtobufMsg();
// recvStringMsg();
}
}
private void connect() {
try {
if (mClientSocket == null) {
mClientSocket = new Socket();
}
mClientSocket.connect(mAddress, CONNECT_TIME_OUT);
Driver.HeartBeat heartbeat = Driver.HeartBeat.newBuilder().setImei(mIMEI).build();
Driver.ClientMessage socketLogin = Driver.ClientMessage.newBuilder().setType(1).setHeartBeat(heartbeat).build();
sendMsg(socketLogin,SEND_MSG_TYPE_SOCKET_LOGIN);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "連接失敗 mClientSocket.connect fail ,ip=" + mAddress.getHostName() + ";port=" + mAddress.getPort() + ";detail:" + e.getMessage());
}
}
/**
* 心跳維護
*/
private void keepHeartBeat() {
//設置心跳頻率,啟動心跳
if (isKeepHeartBeat) {
if (mHeartBeatTask == null) {
mHeartBeatTask = new HeartBeatTask();
}
try {
if (executor != null) {
executor.shutdownNow();
executor = null;
}
executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(
mHeartBeatTask,
1000, //initDelay
HEARTBEART_PERIOD, //period
TimeUnit.MILLISECONDS);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
/**
* @param message
* @param type 1=login;2=心跳;
*/
public void sendMsg(String message, int type) {
PrintWriter writer;
try {
writer = new PrintWriter(new OutputStreamWriter(mClientSocket.getOutputStream(), "UTF-8"), true);
writer.println(message);
Log.e(TAG, "sendMsg Socket.isClosed()=" + mClientSocket.isClosed() + ";connect=" + mClientSocket.isConnected());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
switch (type) {
case SEND_MSG_TYPE_HEARTBEAT:
mHandler.obtainMessage(QpushService.PUSH_TYPE_PROTO_DATA, "發送心跳異常").sendToTarget();
break;
}
}
}
/**
* @param message
* @param type 1=login;2=心跳;
*/
public void sendMsg(Driver.ClientMessage message, int type) {
try {
message.writeTo(mClientSocket.getOutputStream());
Log.e(TAG, "sendMsg success");
} catch (IOException e) {
// TODO Auto-generated catch block
if (type == SEND_MSG_TYPE_HEARTBEAT) {
//心跳失敗
Log.e(TAG, "心跳失敗");
if (mClientSocket.isClosed()) {
connect();
}
} else {
Log.e(TAG, "發送數據失敗");
}
e.printStackTrace();
}
}
/**
* 不斷的檢測是否有服務器推送的數據過來
*/
public void recvStringMsg() {
while (mClientSocket != null && mClientSocket.isConnected() && !mClientSocket.isClosed()) {
try {
mReader = new BufferedReader(new InputStreamReader(mClientSocket.getInputStream(), "UTF-8"));
String data = mReader.readLine();
Log.e(TAG, "recvStringMsg data=" + data);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception ex) {
ex.printStackTrace();
}
sleep(2000);
}
sleep(CHECK_PERIOD);
}
/**
* 不斷的檢測是否有服務器推送的數據過來
*/
public void recvProtobufMsg() {
while (isStartRecieveMsg) {
try {
byte[] resultByte = recvByteMsg(mClientSocket.getInputStream());
if (resultByte != null) {
Driver.ClientMessage retMsg = Driver.ClientMessage.parseFrom(resultByte);
mHandler.obtainMessage(QpushService.PUSH_TYPE_PROTO_DATA, retMsg).sendToTarget();
} else {
Log.e(TAG, "resultByte is null");
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception ex) {
ex.printStackTrace();
}
sleep(5 * 1000);
}
}
/**
* 接收server的信息
*
* @return
*/
public byte[] recvByteMsg(InputStream inpustream) {
try {
byte len[] = new byte[1024];
int count = inpustream.read(len);
byte[] temp = new byte[count];
for (int i = 0; i < count; i++) {
temp[i] = len[i];
}
return temp;
} catch (Exception localException) {
localException.printStackTrace();
}
return null;
}
class HeartBeatTask implements Runnable {
@Override
public void run() {
//執行發送心跳
try {
mClientSocket.sendUrgentData(65);
} catch (IOException e) {
e.printStackTrace();
try {
Log.e(TAG, "socket心跳異常,嘗試斷開,重連");
mClientSocket.close();
mClientSocket = null;
//然后嘗試重連
connect();
} catch (IOException e1) {
e1.printStackTrace();
}
}
Log.e(TAG, "發送心跳,Socket.isClosed()=" + mClientSocket.isClosed() + ";connect" + mClientSocket.isConnected());
}
}
/**
* 通過域名獲取IP
*
* @param domain
* @return
*/
public String getIP(String domain) {
String IPAddress = "";
InetAddress ReturnStr1 = null;
try {
ReturnStr1 = java.net.InetAddress.getByName(domain);
IPAddress = ReturnStr1.getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
Log.e(TAG, "獲取IP失敗" + e.getMessage());
}
return IPAddress;
// return "192.168.3.121";
}
private void sleep(long sleepTime) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 銷毀socket
*/
public void onDestory() {
if (mClientSocket != null) {
try {
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
mClientSocket = null;
}
}
/*
* Ready for use.
*/
public void close() {
try {
if (mClientSocket != null && !mClientSocket.isClosed())
mClientSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
QpushService類:主要負責啟動socket客戶端及管理消息
public class QpushService extends Service {
//數據推送,不顯示到通知欄
final static int PUSH_TYPE_DATA = 1;
final static int PUSH_TYPE_PROTO_DATA = 2;
Handler mHandler;
String TAG = "QpushService";
QpushClient mClient;
@Override
public void onCreate() {
Log.e(TAG, "onCreate");
initHandler();
super.onCreate();
}
/**
* 初始化handler,用于接收推送的消息
*/
private void initHandler() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case PUSH_TYPE_DATA:
Log.e(TAG, "PUSH_TYPE_DATA");
String data = (String) msg.obj;
ToastUtil.showShort(QpushService.this.getApplicationContext(), data);
case PUSH_TYPE_PROTO_DATA:
Driver.ClientMessage clientMessage = (Driver.ClientMessage) msg.obj;
Log.e(TAG, "PUSH_TYPE_PROTO_DATA");
Intent intentCancelRighr = new Intent();
intentCancelRighr.setAction(PushReceiverAction.PUSH_ACTION);
intentCancelRighr.putExtra("data", clientMessage);
getApplicationContext().sendBroadcast(intentCancelRighr);
break;
}
}
};
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind");
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mClient = QpushClient.getInstance(mHandler);
mClient.init(HttpUrls.SOCKET_HOST, HttpUrls.SOCKET_PORT, AppUtils.getPesudoUniqueID());
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy");
super.onDestroy();
mClient.onDestory();
}
}
PushReceiver類:接收service消息的廣播,運行的主進程,防止接收不到數據
public class PushReceiver extends BroadcastReceiver {
Context mContext;
String TAG = "PushReceiver";
@Override
public void onReceive(Context context, Intent intent) {
mContext = context;
if (intent.getAction().equals(PushReceiverAction.PUSH_ACTION)) {
//推送的通知消息
Driver.ClientMessage clientMessage = (Driver.ClientMessage) intent.getSerializableExtra("data");
if (clientMessage != null) {
switch (clientMessage.getType()) {
case 1: //socket登錄,不做處理
Log.e(TAG, "socket登錄消息,sid=" + clientMessage.getHeartBeat().getSid());
MyApplication.sid = clientMessage.getHeartBeat().getSid();
// showNotification("socket","登錄成功",1);
break;
case 2://通知告知被迫取消領取紅包資格
Log.e(TAG, "取消搶紅包資格,title=" + clientMessage.getLogOut().getTitle() + ";message=" + clientMessage.getLogOut().getContent());
ToastUtil.showShort(mContext, "被迫取消搶紅包資格");
Intent intentCancelRighr = new Intent();
intentCancelRighr.setAction(PushReceiverAction.CANCEL_REDPACKET_RIGHT);
intentCancelRighr.putExtra("data", clientMessage);
context.sendBroadcast(intentCancelRighr);
MyApplication.mLoginStatus = 1;
showNotification(clientMessage.getLogOut().getTitle(), clientMessage.getLogOut().getContent(), 1);
break;
case 3: //領取紅包/未領到紅包消息
Intent intentGo = new Intent();
intentGo.setAction(clientMessage.getRedPacket().getResult() == 1 ? PushReceiverAction.GET_REDPACKET_ACTION : PushReceiverAction.DONT_GET_REDPACKET_ACTION);
intentGo.putExtra("data", clientMessage);
context.sendBroadcast(intentGo);
Log.e(TAG, "紅包消息,content=" + clientMessage.getRedPacket().getContent());
showNotification(clientMessage.getRedPacket().getTitle(), clientMessage.getRedPacket().getContent(), 2);
break;
}
} else {
Log.e(TAG, "clientMessage is null");
}
}
}
/**
* 在狀態欄顯示通知
*/
@SuppressWarnings("deprecation")
private void showNotification(String title, String content, int type) {
// 創建一個NotificationManager的引用
NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(android.content.Context
.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
builder.setContentTitle(title)
.setContentText(content)
.setTicker(title)
// .setContentIntent(getDefalutIntent(Notification.FLAG_AUTO_CANCEL))//點擊通知欄設置意圖
.setWhen(System.currentTimeMillis())
.setPriority(Notification.PRIORITY_HIGH)
// .setAutoCancel(true)
.setOngoing(false)//ture,設置他為一個正在進行的通知。他們通常是用來表示一個后臺任務,用戶積極參與(如播放音樂)或以某種方式正在等待,因此占用設備(如一個文件下載,同步操作,主動網絡連接)
.setSmallIcon(R.drawable.logo);
Notification notification = builder.build();
Log.e(TAG, "isSoundOn:" + SharePreferUtils.isSoundOn(mContext));
if (SharePreferUtils.isSoundOn(mContext) && type == 2) { //type==紅包消息
notification.sound = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.diaoluo_da);
}
Intent notificationIntent;
if (type == 2) {
//進入我的資產
notificationIntent = HomeActivity.toWitchFragmentOfHome(mContext, Constants.TOWITCHOFHOME_MYASSETS);
} else {
//進入主頁流量球
notificationIntent = HomeActivity.toWitchFragmentOfHome(mContext, Constants.TOWITCHOFHOME_BALL);
}
Log.e(TAG, "type=" + type);
// 點擊該通知后要跳轉的Activity
PendingIntent contentItent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
notification.contentIntent = contentItent;
notification.flags = Notification.FLAG_AUTO_CANCEL;
// 把Notification傳遞給NotificationManager
notificationManager.notify(type, notification);
}
}
Driver類:該類是通過proto編譯出來的,代碼量比較大,Driver.proto文件貼下來,Driver.java文件我附上下載鏈接,編譯教程見:protobuf在Android推送中的使用方法(比json、xml都要好的數據結構)
Driver.proto文件:
syntax = "proto3";
//編譯教程:http://www.jianshu.com/p/8036003cb849
message ClientMessage
{
int32 type = 1; //類型1=socket登錄;2=取消搶紅包資格;3=領取紅包消息
HeartBeat heartBeat = 2; //socket登錄
LogOut logOut = 3; //取消搶紅包資格消息
RedPacket redPacket = 4; //領取&未搶到紅包消息
}
message HeartBeat
{
string sid = 1; //服務ID
string imei = 2; //手機唯一編號imei
}
message LogOut
{
string sid = 1; //服務ID
int32 result = 2; //1成功,2失敗
string title = 3; //消息標題
string content = 4; //消息內容
}
message RedPacket
{
string sid = 1; //服務ID
int32 result = 2; //1成功,2失敗
int32 price = 3; //紅包金額單位分
string title = 4; //消息標題
string content = 5; //消息內容
}
總結
以上是生活随笔為你收集整理的socket工具android,Android通过socket长连接实现推送的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java值参_JAVA赋值和传参理解
- 下一篇: android sina oauth2.