編輯:關於Android編程
因為運營商有一個NAT超時:因為IP v4的IP量有限,運營商分配給手機終端的IP是運營商內網的IP,手機要連接Internet,就需要通過運營商的網關做一個網絡地址轉換(Network Address Translation,NAT)。簡單的說運營商的網關需要維護一個外網IP、端口到內網IP、端口的對應關系,以確保內網的手機可以跟Internet的服務器通訊,大部分移動無線網絡運營商都在鏈路一段時間沒有數據通訊時,會淘汰NAT表中的對應項,造成鏈路中斷。
所以我們需要間隔一定的時間發送一個數據包來保證當前的TCP連接保持有效,這就是所謂的心跳包
智能心跳實際上就是動態的探測到最大的NAT超時時間,然後選定合適的心跳間隔區間去發送心跳包,同時在網絡狀況發生變化的時候能夠動態的調整心跳間隔時間;如果心跳間隔不合適,例如心跳間隔過短,那麼可能導致頻繁的喚醒手機發送心跳包,增加耗電,心跳間隔過長,可能導致這條TCP連接已經無效但是無法及時的檢測到,只能等待下一個心跳包發送的時候才能感知到,所以會導致消息接收延遲,所以探測到一個合適的心跳間隔是非常重要的,把耗電和消息接收及時性綜合折中來取得一個最佳的體驗

public abstract class HeartbeatScheduler {
protected int timeout = 20000;
protected int minHeart = 60;
protected int maxHeart = 300;
protected int step = 30;
protected volatile boolean started = false;
protected volatile long heartbeatSuccessTime;
protected volatile int currentHeartType;
public static final String HEART_TYPE_TAG = "heart_type";
public static final int UNKNOWN_HEART = 0, SHORT_HEART = 1, PROBE_HEART = 2, STABLE_HEART = 3, REDUNDANCY_HEART = 4;
protected PendingIntent createPendingIntent(Context context, int requestCode, int heartType) {
Intent intent = new Intent();
intent.setPackage(context.getPackageName());
intent.setAction(SyncAction.HEARTBEAT_REQUEST);
intent.putExtra(HEART_TYPE_TAG, heartType);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pendingIntent;
}
protected void set(int minHeart, int maxHeart, int step) {
this.minHeart = minHeart;
this.maxHeart = maxHeart;
this.step = step;
SyncLogUtil.i("set minMax:" + minHeart + ",maxHeart:" + maxHeart + ",step:" + step);
}
protected boolean isStarted() {
return started;
}
protected abstract boolean isStabled();
protected void setCurrentHeartType(int currentHeartType) {
this.currentHeartType = currentHeartType;
SyncLogUtil.i("set current heart type:" + currentHeartType);
}
protected int getTimeout() {
return timeout;
}
protected void setTimeout(int timeout) {
this.timeout = timeout;
}
protected long getHeartbeatSuccessTime() {
return heartbeatSuccessTime;
}
protected void setHeartbeatSuccessTime(long heartbeatSuccessTime) {
this.heartbeatSuccessTime = heartbeatSuccessTime;
}
protected abstract void start(Context context);
protected abstract void stop(Context context);
protected abstract void clear(Context context);
protected abstract void adjustHeart(Context context, boolean success);
protected abstract void startNextHeartbeat(Context context, int heartType);
protected abstract void resetScheduledHeart(Context context);
protected abstract void receiveHeartbeatFailed(Context context);
protected abstract void receiveHeartbeatSuccess(Context context);
protected abstract int getCurHeart();
}
public class WatchHearbeatScheduler extends HeartbeatScheduler {
private class Heartbeat {
AtomicInteger heartbeatStabledSuccessCount = new AtomicInteger(0); // 心跳連續成功次數
AtomicInteger heartbeatFailedCount = new AtomicInteger(0); // 心跳連續失敗次數
int successHeart;
int failedHeart;
int curHeart = 270;
AtomicBoolean stabled = new AtomicBoolean(false);
}
private int curMaxHeart = maxHeart;
private int curMinHeart = minHeart;
private int maxFailedCount = 5;
private int maxSuccessCount = 20;
private volatile String networkTag;
private int requestCode = 700;
private Map heartbeatMap = new HashMap<>();
private List successHeartList = new ArrayList<>();
protected WatchHearbeatScheduler() {
}
@Override
protected void start(Context context) {
started = true;
networkTag = NetUtil.getNetworkTag(context);
alarm(context);
SyncLogUtil.i("start heartbeat,networkTag:" + networkTag);
}
@Override
protected void stop(Context context) {
heartbeatSuccessTime = 0;
started = false;
networkTag = null;
currentHeartType = UNKNOWN_HEART;
for (Map.Entry entry : heartbeatMap.entrySet()) {
Heartbeat heartbeat = entry.getValue();
heartbeat.heartbeatStabledSuccessCount.set(0);
heartbeat.heartbeatFailedCount.set(0);
}
cancel(context);
SyncLogUtil.d("stop heartbeat...");
}
@Override
protected void setCurrentHeartType(int currentHeartType) {
this.currentHeartType = currentHeartType;
}
@Override
protected void set(int minHeart, int maxHeart, int step) {
super.set(minHeart, maxHeart, step);
curMaxHeart = maxHeart;
curMinHeart = minHeart;
}
@Override
protected boolean isStabled() {
Heartbeat heartbeat = getHeartbeat();
return heartbeat.stabled.get();
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public void alarm(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Heartbeat heartbeat = getHeartbeat();
boolean stabled = heartbeat.stabled.get();
int heart;
if (stabled) {
heart = heartbeat.curHeart - 10;
if (heart < minHeart) {
heart = minHeart;
}
heart = heart * 1000;
} else {
heart = heartbeat.curHeart * 1000;
}
int heartType = stabled ? STABLE_HEART : PROBE_HEART;
PendingIntent pendingIntent = createPendingIntent(context, requestCode, heartType);
int sdk = Build.VERSION.SDK_INT;
if (sdk >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + heart, pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + heart, pendingIntent);
}
SyncLogUtil.i("start heartbeat,curHeart [" + heartbeat.curHeart + "],heart [" + heart + "],requestCode:" + requestCode + ",stabled:" + stabled);
}
private void cancel(Context context) {
Heartbeat heartbeat = getHeartbeat();
int heartType = heartbeat.stabled.get() ? STABLE_HEART : PROBE_HEART;
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = createPendingIntent(context, requestCode, heartType);
alarmManager.cancel(pendingIntent);
SyncLogUtil.d("cancel heartbeat,requestCode:" + requestCode);
}
@Override
public void startNextHeartbeat(Context context, int heartType) {
alarm(context);
}
@Override
public void resetScheduledHeart(Context context) {
alarm(context);
}
private void addSuccessHeart(Integer successHeart) {
if (!successHeartList.contains(successHeart)) {
if (successHeartList.size() > 10) {
successHeartList.remove(0);
}
successHeartList.add(successHeart);
SyncLogUtil.i("add successHeart:" + successHeart);
}
SyncLogUtil.i("successHeartList:" + successHeartList);
}
private void removeSuccessHeart(Integer successHeart) {
successHeartList.remove(Integer.valueOf(successHeart));
SyncLogUtil.i("successHeartList:" + successHeartList);
}
@Override
protected void adjustHeart(Context context, boolean success) {
if (currentHeartType == REDUNDANCY_HEART) {
SyncLogUtil.d("redundancy heart,do not adjustHeart...");
return;
}
Heartbeat heartbeat = getHeartbeat();
if (success) {
onSuccess(heartbeat);
} else {
onFailed(heartbeat);
}
SyncLogUtil.i("after success is [" + success + "] adjusted,heartbeat.curHeart:" + heartbeat.curHeart + ",networkTag:" + networkTag);
}
private void onSuccess(Heartbeat heartbeat) {
heartbeat.successHeart = heartbeat.curHeart;
curMinHeart = heartbeat.curHeart;
addSuccessHeart(heartbeat.successHeart);
heartbeat.heartbeatFailedCount.set(0);
if (heartbeat.stabled.get()) {
int count = heartbeat.heartbeatStabledSuccessCount.incrementAndGet();
SyncLogUtil.i("heartbeatStabledSuccessCount:" + heartbeat.heartbeatStabledSuccessCount.get());
if (count >= maxSuccessCount) {
maxSuccessCount += 20;
SyncLogUtil.i("maxSuccessCount:" + maxSuccessCount);
Integer successHeart = selectMinSuccessHeart(heartbeat.curHeart);
if (successHeart != null) {
heartbeat.curHeart = successHeart;
} else {
heartbeat.stabled.set(false);
curMaxHeart = maxHeart;
heartbeat.curHeart = (curMinHeart + curMaxHeart) / 2;
SyncLogUtil.i("curHeart = (" + curMinHeart + " + " + curMaxHeart + ") / 2 = " + heartbeat.curHeart);
}
}
} else {
heartbeat.curHeart = (curMinHeart + curMaxHeart) / 2;
SyncLogUtil.i("curHeart = (" + curMinHeart + " + " + curMaxHeart + ") / 2 = " + heartbeat.curHeart);
}
if (heartbeat.curHeart >= maxHeart) {
heartbeat.curHeart = maxHeart;
heartbeat.stabled.set(true);
SyncLogUtil.i("探測達到最大心跳adjust stabled:" + heartbeat.stabled.get());
} else if (curMaxHeart - curMinHeart < 10) {
if (!heartbeat.stabled.get()) {
heartbeat.curHeart = curMinHeart;
}
heartbeat.stabled.set(true);
SyncLogUtil.i("二分法探測盡頭adjust stabled:" + heartbeat.stabled.get());
}
SyncLogUtil.i("curHeart:" + heartbeat.curHeart + ",curMinHeart:" + curMinHeart + ",curMaxHeart:" + curMaxHeart);
}
private void onFailed(Heartbeat heartbeat) {
removeSuccessHeart(heartbeat.curHeart);
heartbeat.failedHeart = heartbeat.curHeart;
heartbeat.heartbeatStabledSuccessCount.set(0);
curMaxHeart = heartbeat.curHeart;
int count = heartbeat.heartbeatFailedCount.incrementAndGet();
SyncLogUtil.i("heartbeatFailedCount:" + count);
if (maxSuccessCount > 20) {
maxSuccessCount -= 20;
}
if (heartbeat.stabled.get()) {
if (count > maxFailedCount) {
Integer successHeart = selectMaxSuccessHeart(heartbeat.curHeart);
if (successHeart != null) {
heartbeat.curHeart = successHeart;
} else {
heartbeat.stabled.set(false);
curMinHeart = minHeart;
heartbeat.curHeart = (curMinHeart + curMaxHeart) / 2;
SyncLogUtil.i("curHeart = (" + curMaxHeart + " + " + curMinHeart + ") / 2 = " + heartbeat.curHeart);
}
} else {
SyncLogUtil.i("continue retry heartbeat.curHeart:" + heartbeat.curHeart + ",stabled:" + heartbeat.stabled.get());
}
} else {
if (count > maxFailedCount) {
heartbeat.curHeart = (curMinHeart + curMaxHeart) / 2;
SyncLogUtil.i("curHeart = (" + curMaxHeart + " + " + curMinHeart + ") / 2 = " + heartbeat.curHeart);
} else {
SyncLogUtil.i("continue retry heartbeat.curHeart:" + heartbeat.curHeart + ",stabled:" + heartbeat.stabled.get());
}
}
if (curMaxHeart - curMinHeart < 10) {
if (!heartbeat.stabled.get()) {
curMinHeart = minHeart;
}
SyncLogUtil.i("二分法探測達到瓶頸" + ",curHeart:" + heartbeat.curHeart);
SyncLogUtil.i("curMinHeart:" + curMinHeart + ",curMaxHeart:" + curMaxHeart);
}
SyncLogUtil.i("curHeart:" + heartbeat.curHeart + ",curMinHeart:" + curMinHeart + ",curMaxHeart:" + curMaxHeart);
}
private Integer selectMaxSuccessHeart(int curHeart) {
Collections.sort(successHeartList, new Comparator() {
@Override
public int compare(Integer lhs, Integer rhs) {
return rhs.compareTo(lhs);
}
});
SyncLogUtil.i("successHeartList:" + successHeartList);
for (Integer heart : successHeartList) {
if (curHeart >= heart) {
continue;
} else {
return heart;
}
}
return null;
}
private Integer selectMinSuccessHeart(int curHeart) {
Collections.sort(successHeartList, new Comparator() {
@Override
public int compare(Integer lhs, Integer rhs) {
return lhs.compareTo(rhs);
}
});
SyncLogUtil.i("successHeartList:" + successHeartList);
for (Integer heart : successHeartList) {
if (curHeart >= heart) {
continue;
} else {
return heart;
}
}
return null;
}
private Heartbeat getHeartbeat() {
Heartbeat heartbeat = heartbeatMap.get(networkTag);
if (heartbeat == null) {
heartbeat = new Heartbeat();
heartbeatMap.put(networkTag, heartbeat);
}
return heartbeat;
}
@Override
protected void receiveHeartbeatFailed(Context context) {
adjustHeart(context, false);
}
@Override
protected void receiveHeartbeatSuccess(Context context) {
adjustHeart(context, true);
alarm(context);
}
@Override
protected void clear(Context context) {
stop(context);
heartbeatMap.clear();
successHeartList.clear();
curMinHeart = minHeart;
curMaxHeart = maxHeart;
SyncLogUtil.d("clear heartbeat...");
}
@Override
protected int getCurHeart() {
Heartbeat heartbeat = getHeartbeat();
return heartbeat.curHeart;
}
}
Android自定義控件系列:詳解onMeasure-利用onMeasure測量來實現圖片拉伸永不變形,解決屏幕適配問題
使用ImageView會遇到的問題 在Android應用中,都少不了圖片的顯示,ImageView,輪播圖,ViewPager等等,很多
Android安全開發之Provider組件安全
1 Content Provider組件簡介Content Provider組件是Android應用的重要組件之一,管理對數據的訪問,主要用於不同的應用程序之間實現數據共
簡易圖片教程 Android手機就能做
不少玩家會想到將圖片拷貝到電腦中,然後用PS等工具去標注編輯加工,然在再發送到朋友圈或社交平台。那麼,轉來轉去,是不是也不太方便呢?其實Android手機也
Android特效專輯(十二)——如何仿支付寶咻一咻功能實現波紋擴散特效
Android特效專輯(十二)——仿支付寶咻一咻功能實現波紋擴散特效,精細小巧的View 先來看看這個效果 這是我的在Only上添加的效果,說實話