編輯:關於Android編程
上一篇文章中我們講解了android app實現長連接的幾種方式,各自的優缺點以及具體的實現,一般而言使用第三方的推送服務已經可以滿足了基本的業務需求,當然了若是對技術有追求的可以通過NIO或者是MINA實現自身的長連接服務,但是自己實現的長連接服務一來比較復雜耗時比較多,而且可能過程中有許多坑要填,一般而言推薦使用第三方的推送服務,穩定簡單。
而本文將講解app端的輪訓請求服務,一般而言我們經常將輪訓操作用於請求服務器。比如某一個頁面我們有定時任務需要時時的從服務器獲取更新信息並顯示,比如當長連接斷掉之後我們可能需要啟動輪訓請求作為長連接的補充等,所以這時候就用到了輪訓服務。
什麼是輪訓請求
在說明我們輪訓請求之前,這裡先說明一下什麼叫輪訓請求,我的理解就是App端每隔一定的時間重復請求的操作就叫做輪訓請求,比如:App端每隔一段時間上報一次定位信息,App端每隔一段時間拉去一次用戶狀態等,這些應該都是輪訓請求,那麼前一篇我們講了App端的長連接,為什麼我們有了長連接之後還需要輪訓操作呢?
這是因為我們的長連接並不是穩定的可靠的,而我們執行輪訓操作的時候一般都是要穩定的網絡請求,而且輪訓操作一般都是有生命周期的,即在一定的生命周期內執行輪訓操作,而我們的長連接一般都是整個進程生命周期的,所以從這方面講也不太適合。
輪訓請求實踐
與長連接相關的輪訓請求
上一篇我們在講解長連接的時候說過長連接有可能會斷,而這時候在長連接斷的時候我們就需要啟動一個輪訓服務,它作為長連接的補充。
/**
* 啟動輪詢服務
*/
public void startLoopService() {
// 啟動輪詢服務
// 暫時不考慮加入網絡情況的判斷...
if (!LoopService.isServiceRuning) {
// 用戶是登錄態,啟動輪詢服務
if (UserConfig.isPassLogined()) {
// 判斷當前長連接的狀態,若長連接已連接,則不再開啟輪詢服務
if (MinaLongConnectManager.session != null && MinaLongConnectManager.session.isConnected()) {
LoopService.quitLoopService(context);
return;
}
LoopService.startLoopService(context);
} else {
LoopService.quitLoopService(context);
}
}
}
這裡就是我們執行輪訓服務的操作代碼,其作用就是啟動了一個輪訓service(即輪訓服務),然後在輪訓服務中執行具體的輪訓請求,既然這樣我們就具體看一下這個service的代碼邏輯。
與長連接相關的輪訓服務請求
/**
* 長連接異常時啟動服務,長連接恢復時關閉服務
*/
public class LoopService extends Service {
public static final String ACTION = "com.youyou.uuelectric.renter.Service.LoopService";
/**
* 客戶端執行輪詢的時間間隔,該值由StartQueryInterface接口返回,默認設置為30s
*/
public static int LOOP_INTERVAL_SECS = 30;
/**
* 輪詢時間間隔(MLOOP_INTERVAL_SECS 這個時間間隔變量有服務器下發,此時輪詢服務的場景與邏輯與定義時發生變化,涉及到IOS端,因此采用自己定義的常量在客戶端寫死時間間隔)
*/
public static int MLOOP_INTERVAL_SECS = 30;
/**
* 當前服務是否正在執行
*/
public static boolean isServiceRuning = false;
/**
* 定時任務工具類
*/
public static Timer timer = new Timer();
private static Context context;
public LoopService() {
isServiceRuning = false;
}
//-------------------------------使用鬧鐘執行輪詢服務------------------------------------
/**
* 啟動輪詢服務
*/
public static void startLoopService(Context context) {
if (context == null)
return;
quitLoopService(context);
L.i("開啟輪詢服務,輪詢間隔:" + MLOOP_INTERVAL_SECS + "s");
AlarmManager manager = (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context.getApplicationContext(), LoopService.class);
intent.setAction(LoopService.ACTION);
PendingIntent pendingIntent = PendingIntent.getService(context.getApplicationContext(), 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// long triggerAtTime = SystemClock.elapsedRealtime() + 1000;
/**
* 鬧鐘的第一次執行時間,以毫秒為單位,可以自定義時間,不過一般使用當前時間。需要注意的是,本屬性與第一個屬性(type)密切相關,
* 如果第一個參數對應的鬧鐘使用的是相對時間(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那麼本屬性就得使用相對時間(相對於系統啟動時間來說),
* 比如當前時間就表示為:SystemClock.elapsedRealtime();
* 如果第一個參數對應的鬧鐘使用的是絕對時間(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那麼本屬性就得使用絕對時間,
* 比如當前時間就表示為:System.currentTimeMillis()。
*/
manager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), MLOOP_INTERVAL_SECS * 1000, pendingIntent);
}
/**
* 停止輪詢服務
*/
public static void quitLoopService(Context context) {
if (context == null)
return;
L.i("關閉輪詢鬧鐘服務...");
AlarmManager manager = (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context.getApplicationContext(), LoopService.class);
intent.setAction(LoopService.ACTION);
PendingIntent pendingIntent = PendingIntent.getService(context.getApplicationContext(), 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
manager.cancel(pendingIntent);
// 關閉輪詢服務
L.i("關閉輪詢服務...");
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
L.i("開始執行輪詢服務... \n 判斷當前用戶是否已登錄...");
// 若當前網絡不正常或者是用戶未登陸,則不再跳轉
if (UserConfig.isPassLogined()) {
// 判斷當前長連接狀態,若長連接正常,則關閉輪詢服務
L.i("當前用戶已登錄... \n 判斷長連接是否已經連接...");
if (MinaLongConnectManager.session != null && MinaLongConnectManager.session.isConnected()) {
L.i("長連接已恢復連接,退出輪詢服務...");
quitLoopService(context);
} else {
if (isServiceRuning) {
return START_STICKY;
}
// 啟動輪詢拉取消息
startLoop();
}
} else {
L.i("用戶已退出登錄,關閉輪詢服務...");
quitLoopService(context);
}
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
L.i("輪詢服務退出,執行onDestory()方法,inServiceRuning賦值false");
isServiceRuning = false;
timer.cancel();
timer = new Timer();
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* 啟動輪詢拉去消息
*/
private void startLoop() {
if (timer == null) {
timer = new Timer();
}
timer.schedule(new TimerTask() {
@Override
public void run() {
isServiceRuning = true;
L.i("長連接未恢復連接,執行輪詢操作... \n 輪詢服務中請求getInstance接口...");
LoopRequest.getInstance(context).sendLoopRequest();
}
}, 0, MLOOP_INTERVAL_SECS * 1000);
}
}
可以發現這裡的service輪訓服務的代碼量還是比較多的,但是輪訓服務請求代碼注釋已經很詳細了,所以就不做過多的說明,需要說明的是其核心就是通過Timer對象每個一段時間執行一次網絡請求。具體的網絡請求代碼:
L.i("長連接未恢復連接,執行輪詢操作... \n 輪詢服務中請求getInstance接口...");
LoopRequest.getInstance(context).sendLoopRequest();
這裡的輪訓服務請求核心邏輯:當長連接出現異常時,啟動輪訓服務,並通過Timer對象每隔一定時間拉取服務器狀態,當長連接恢復時,關閉輪訓服務。這就是我們與長連接有關的輪訓服務的代碼執行邏輯,看完這部分之後我們再看一下與頁面相關的輪訓請求的執行邏輯。
與頁面相關的輪訓請求
與頁面相關的輪訓請求這裡我們看一下我們產品當前行程頁面的輪訓操作,用於輪訓請求當前用戶的車輛裡程,費用,用時等信息,具體可參考下圖:
其實在當前Fragment頁面有一個定時的拉去訂單信息的輪訓請求,下面我們具體看一下這個定時請求的執行邏輯:
/**
* TimerTask對象,主要用於定時拉去服務器信息
*/
public class Task extends TimerTask {
@Override
public void run() {
L.i("開始執行執行timer定時任務...");
handler.post(new Runnable() {
@Override
public void run() {
isFirstGetData = false;
getData(true);
}
});
}
}
而這裡的getData方法就是拉去服務器狀態的方法,這裡不做過多的解釋,當用戶退出這個頁面的時候需要清除這裡的輪訓操作。所以在Fragment的onDesctoryView方法中執行了清除timerTask的操作。
@Override
public void onDestroyView() {
super.onDestroyView();
...
if (timer != null) {
timer.cancel();
timer = null;
}
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
...
}
這樣當用戶打開這個頁面的時候初始化TimerTask對象,每個一分鐘請求一次服務器拉取訂單信息並更新UI,當用戶離開頁面的時候清除TimerTask對象,即取消輪訓請求操作。可以發現上面我們看到的與長連接和頁面相關的輪訓請求服務都是通過timer對象的定時任務實現的輪訓請求服務,下面我們看一下如何通過Handler對象實現輪訓請求服務。
通過Handler對象實現輪訓請求
下面我們來看一個通過Handler異步消息實現的輪訓請求服務。
/**
* 默認的時間間隔:1分鐘
*/
private static int DEFAULT_INTERVAL = 60 * 1000;
/**
* 異常情況下的輪詢時間間隔:5秒
*/
private static int ERROR_INTERVAL = 5 * 1000;
/**
* 當前輪詢執行的時間間隔
*/
private static int interval = DEFAULT_INTERVAL;
/**
* 輪詢Handler的消息類型
*/
private static int LOOP_WHAT = 10;
/**
* 是否是第一次拉取數據
*/
private boolean isFirstRequest = false;
/**
* 第一次請求數據是否成功
*/
private boolean isFirstRequestSuccess = false;
/**
* 開始執行輪詢,正常情況下,每隔1分鐘輪詢拉取一次最新數據
* 在onStart時開啟輪詢
*/
private void startLoop() {
L.i("頁面onStart,需要開啟輪詢");
loopRequestHandler.sendEmptyMessageDelayed(LOOP_WHAT, interval);
}
/**
* 關閉輪詢,在界面onStop時,停止輪詢操作
*/
private void stopLoop() {
L.i("頁面已onStop,需要停止輪詢");
loopRequestHandler.removeMessages(LOOP_WHAT);
}
/**
* 處理輪詢的Handler
*/
private Handler loopRequestHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 如果首次請求失敗,
if (!isFirstRequestSuccess) {
L.i("首次請求失敗,需要將輪詢時間設置為:" + ERROR_INTERVAL);
interval = ERROR_INTERVAL;
} else {
interval = DEFAULT_INTERVAL;
}
L.i("輪詢中-----當前輪詢間隔:" + interval);
loopRequestHandler.removeMessages(LOOP_WHAT);
// 首次請求為成功、或者定位未成功時執行重定位,並加載網點數據
if (!isFirstRequestSuccess || !Config.locationIsSuccess()) {
isClickLocationButton = false;
doLocationOption();
} else {
loadData();
}
System.gc();
loopRequestHandler.sendEmptyMessageDelayed(LOOP_WHAT, interval);
}
};
這裡是通過Handler實現的輪訓操作,其核心原理就是在handler的handlerMessage方法中,接收到消息之後再次發送延遲消息,這裡的延遲時間就是我們定義的輪訓間隔時間,這樣當我們下次接收到消息的時候又一次發送延遲消息,從而造成我們時時發送輪訓消息的情景。
以上就是我們實現輪訓操作的兩種方式:
Timer對象實現輪訓操作
Handler對象實現輪訓操作
上面我們分析了輪訓請求的不同使用場景,作用以及實現方式,當我們在具體的開發過程中需要定時的向服務器拉取消息的時候就可以考慮使用輪訓請求了。
總結:
輪訓操作一般都是通過定時請求服務器拉取信息並更新UI;
輪訓操作一般都有一定的生命周期,比如在某個頁面打開時啟動輪訓操作,在某個頁面關閉時取消輪訓操作;
輪訓操作的請求間隔需要根據具體的需求確定,間隔時間不宜果斷,否則可能造成並發性問題;
產品開發過程中,某些需要試試更新服務器拉取信息並更新UI時,可以考慮使用輪訓操作實現;
可以通過Timer對象和Handler對象兩種方式實現輪訓請求操作;
本文以同步至github中:https://github.com/yipianfengye/androidProject,歡迎star和follow
Android Studio 1.0正式版發布啦今天是個大日子,Android Studio 1.0 終於發布了正式版, 這對於Android開發者來說簡直是喜大普奔的大
為了幫助一些剛接觸AndroidStudio的童鞋,在這裡我把自己琢磨出來的一點經驗分享給大家!Ecplise項目變為AS項目有兩種方式,一種只不改變原有的項目結構,只是
ScaleType表示ImageView的縮放類型,決定了一張圖片在ImageView控件內如何縮放和顯示。ScaleType的官方文檔:https://develope
本篇隨筆將講解一下Android當中比較常用的兩個布局容器--ScrollView和HorizontalScrollView,從字面意義上來看也是非常的簡單的,Scrol