0.Thanks
性能優(yōu)化十六之Wake_Lock喚醒鎖以及JobScheduler使用
安卓 java 判斷socket斷開
android保持服務(wù)不休眠(持續(xù)運(yùn)行)以及喚醒屏幕的方法
Android API 19 及以上版本AlarmManager setRepeating 不準(zhǔn)或只執(zhí)行一次的解決方案
1.概述
前陣子接到一個(gè)用戶反饋說,公司的一款機(jī)器人,很容易掉線。這款產(chǎn)品上,跑的是Android5.1系統(tǒng),用的是Socket與自己服務(wù)器保持著長(zhǎng)鏈接。于是開始排查問題。。。
我們知道,一般我們?cè)诳蛻舳松鲜褂眯奶鴧f(xié)議,跟服務(wù)器保持著長(zhǎng)連接。所以,先從這里下手,懷疑是服務(wù)器心跳協(xié)議那段代碼有問題。然后實(shí)際上不是。
后面繼續(xù)找,發(fā)現(xiàn)了幾個(gè)問題:
問題1,心跳包是客戶端發(fā)送的,而服務(wù)器收到心跳包是沒有回包的,這樣會(huì)存在一個(gè)情況,有時(shí)候因網(wǎng)絡(luò)原因,心跳包一直有發(fā)送,但客戶端并不知道是否成功,而使得socket鏈接被服務(wù)器判定為超時(shí),就被服務(wù)器斷開了。
問題2,接著上面的問題,socket被服務(wù)器判斷為超時(shí),服務(wù)器主動(dòng)斷開socket,但是,客戶端并沒有收到fin包,也就是socket斷開的信號(hào)。為啥沒有收到4次揮手,嗯,有機(jī)會(huì)要深究。
問題3,發(fā)送心跳包是有間隔的,并不是一支持續(xù)地發(fā)送,但從客戶端本地的日志來看,發(fā)現(xiàn)在鎖屏的時(shí)候,心跳包的發(fā)送并不是按照程序那樣子去跑。程序?qū)懙氖敲?分鐘跑一次心跳,但是,鎖屏后,這個(gè)時(shí)間就不是5分鐘,有可能是10分鐘
2.解決問題1:修改心跳協(xié)議
心跳協(xié)議是讓雙方知道,雙方都在線,既然是雙方,心跳也應(yīng)該是有回包的。所以,后面把心跳協(xié)議改成,服務(wù)器收到心跳包,也應(yīng)該回一個(gè)收到心跳包的通知。考慮到有時(shí)候,會(huì)因?yàn)榫W(wǎng)絡(luò)的緣故,會(huì)丟包,所以,在客戶端上增加重試的邏輯。所以現(xiàn)在變成,先發(fā)送心跳包,在N秒內(nèi)若沒有收到回復(fù),則再發(fā)送一次。重試M次。
3.解決問題2:判斷socket是否已經(jīng)斷開了
1)isClosed()、isConnected()、isInputStreamShutdown()、isOutputStreamShutdown()方法來判斷
- 這種方式只是本地判斷,只是本地操作connect()或close()方法后保存的一個(gè)狀態(tài),
對(duì)于遠(yuǎn)程服務(wù)器主動(dòng)斷開就沒有用了
2)sendUrgentData()判斷遠(yuǎn)程服務(wù)器是否斷開Socket
往輸出流發(fā)送一個(gè)字節(jié)的數(shù)據(jù),只要對(duì)方Socket的SO_OOBINLINE屬性沒有打開,就會(huì)自動(dòng)舍棄這個(gè)字節(jié),SO_OOBINLINE屬性默認(rèn)情況下就是關(guān)閉的!
但是,經(jīng)測(cè)試,這個(gè)方法行不通。但網(wǎng)上說這個(gè)方法有用,唉,可能我的是假socket。
3)通過OutputStream.write
- 有人說,發(fā)送心跳協(xié)議的時(shí)候,try catch一下,如果有報(bào)異常,就說明是斷開了。實(shí)際并不然,實(shí)際測(cè)試中,
服務(wù)器斷開socket,還是可以發(fā)包,并且不會(huì)報(bào)異常
既然以上的辦法都無(wú)法判斷,只能依靠心跳協(xié)議了,只要服務(wù)器沒有按心跳的邏輯回包,就判斷已經(jīng)斷開并主動(dòng)去鏈接。實(shí)際測(cè)試中,修該后的心跳協(xié)議,是可以很好地保持長(zhǎng)鏈接。
4.解決問題3:系統(tǒng)休眠定時(shí)器不準(zhǔn)
系統(tǒng)鎖屏后一段時(shí)間,系統(tǒng)會(huì)進(jìn)入一個(gè)休眠狀態(tài),此狀態(tài)下,系統(tǒng)會(huì)把線程掛起,所以Timer定時(shí)器會(huì)不準(zhǔn)確。
清楚問題了,就好解決??梢酝ㄟ^以下解決:
1)準(zhǔn)確定時(shí)
- 系統(tǒng)休眠導(dǎo)致定時(shí)器不準(zhǔn)備,那有沒有辦法可以讓定時(shí)器正常工作呢?有!
在API19,可以這樣做,親測(cè)起效,還有,你還需監(jiān)聽此Action的廣播:
/**
* 設(shè)置定時(shí)器
* @param timeMs 毫秒,既是,多少毫秒后,觸發(fā)
* @param action 觸發(fā),以廣播形式,傳入廣播的action
*/
private void setAlarm(int timeMs,String action) {
try {
AlarmManager am = (AlarmManager) XHoneyBotApplicationLike.getInstance().getApplication().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(action);
PendingIntent sender = PendingIntent.getBroadcast(XHoneyBotApplicationLike.getInstance().getApplication(), 0, intent,PendingIntent.FLAG_CANCEL_CURRENT);
if (am!=null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//參數(shù)2是開始時(shí)間、參數(shù)3是允許系統(tǒng)延遲的時(shí)間
am.setWindow(AlarmManager.RTC, System.currentTimeMillis() + timeMs, 500, sender);
} else {
am.setRepeating(AlarmManager.RTC, System.currentTimeMillis() + timeMs, 500, sender);
}
}
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 取消對(duì)應(yīng)action的定時(shí)器
* @param action action
*/
private void canalAlarm(String action,Context context) {
try {
Intent intent = new Intent(action);
PendingIntent pi = PendingIntent.getBroadcast(context.getApplicationContext(), 0, intent,PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
if (am!=null)
am.cancel(pi);
} catch (Exception e){
e.printStackTrace();
}
}
2)添加喚醒鎖
- 在Android中還有一種喚醒鎖的機(jī)制,詳情可以百度學(xué)習(xí)一下。其實(shí)大概就是,你可以使用喚醒鎖
強(qiáng)制讓系統(tǒng)不進(jìn)行休眠!
/**
* Thanks: http://blog.csdn.net/wzj0808/article/details/52608940
* 保持系統(tǒng)CUP Active
*/
private static PowerManager.WakeLock wl;
public static void setCPUAliveLock(Context context) {
try {
if (wl!=null)
releaseKeepAliveOnScreenOff();
PowerManager pm = (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE);
if (pm!=null) {
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ScreenOff");
wl.acquire();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void releaseCPUAliveLock() {
try {
if (wl!=null)
wl.release();
wl = null;
} catch (Exception e) {
e.printStackTrace();
}
}
喚醒后,CPU處于ACTIVE狀態(tài),然后執(zhí)行任務(wù),執(zhí)行完成后,便釋放鎖。
PS,如果在鎖屏的時(shí)候,一直喚醒CPU,在實(shí)際應(yīng)用中,會(huì)出現(xiàn)一個(gè)非常嚴(yán)重的問題:耗電!
所以,推介是,使用定時(shí)器,在定時(shí)器定觸發(fā)的時(shí)候,喚醒CPU,進(jìn)行心跳,心跳邏輯跑完后,就釋放鎖。