android輪詢最佳實踐service+AlarmManager+Thread
android中涉及到將服務器中數據變化信息通知用戶一般有兩種辦法,推送和輪詢。
消息推送是服務端主動發消息給客戶端,因為第一時間知道數據發生變化的是服務器自己,所以推送的優勢是實時性高。但服務器主動推送需要單獨開發一套能讓客戶端持久連接的服務端程序,不過現在已經有很多開源的代碼實現了基于xmmp協議的推送方案,而且還可以使用谷歌的推送方案。但有些情況下并不需要服務端主動推送,而是在一定的時間間隔內客戶端主動發起查詢。
譬如有這樣一個app,實時性要求不高,每天只要能獲取10次最新數據就能滿足要求了,這種情況顯然輪詢更適合一些,推送顯得太浪費,而且更耗電。
但是不管是輪詢還是推送都需要無論應用程序是否正在運行或者關閉的情況下能給用戶發送通知,因此都需要用到service。我們有兩種方案來使用service達到此目的:
方案一:service +Thread
在service中開啟一個帶有while循環的線程,使其不斷的從服務器查詢數據(一定時間間隔內),當發現有需要通知用戶的情況下發送notification。這種方案的代碼大致是:
import org.apache.http.Header; import org.json.JSONObject; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.widget.Toast; import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.AsyncHttpResponseHandler; /*
- 短信推送服務類,在后臺長期運行,每個一段時間就向服務器發送一次請求 *
@author jerry / public class PushSmsService extends Service { private MyThread myThread; private NotificationManager manager; private Notification notification; private PendingIntent pi; private AsyncHttpClient client; private boolean flag = true; @Override public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub return null;
} @Override public void onCreate() {
System.out.println("oncreate()"); this.client = new AsyncHttpClient(); this.myThread = new MyThread(); this.myThread.start(); super.onCreate();
} @Override public void onDestroy() {
this.flag = false; super.onDestroy();
} private void notification(String content, String number, String date) {
// 獲取系統的通知管理器 manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notification = new Notification(R.drawable.ic_menu_compose, content, System.currentTimeMillis()); notification.defaults = Notification.DEFAULT_ALL; // 使用默認設置,比如鈴聲、震動、閃燈 notification.flags = Notification.FLAG_AUTO_CANCEL; // 但用戶點擊消息后,消息自動在通知欄自動消失 notification.flags |= Notification.FLAG_NO_CLEAR;// 點擊通知欄的刪除,消息不會依然不會被刪除 Intent intent = new Intent(getApplicationContext(), ContentActivity.class); intent.putExtra("content", content); intent.putExtra("number", number); intent.putExtra("date", date); pi = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0); notification.setLatestEventInfo(getApplicationContext(), number + "發來短信", content, pi); // 將消息推送到狀態欄 manager.notify(0, notification);
} private class MyThread extends Thread {
@Override public void run() { String url = "你請求的網絡地址"; while (flag) { System.out.println("發送請求"); try { // 每個10秒向服務器發送一次請求 Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } // 采用get方式向服務器發送請求 client.get(url, new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { try { JSONObject result = new JSONObject(new String( responseBody, "utf-8")); int state = result.getInt("state"); // 假設偶數為未讀消息 if (state % 2 == 0) { String content = result.getString("content"); String date = result.getString("date"); String number = result.getString("number"); notification(content, number, date); } } catch (Exception e) { e.printStackTrace(); } } @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { Toast.makeText(getApplicationContext(), "數據請求失敗", 0) .show(); } }); } }
} }</pre>
其中
AsyncHttpClient
為網絡異步請求的開源庫,可以很方便的實現異步網絡請求。這種方案存在的不足有很多,一是應用長期有一個后臺程序運行,如果是一個喜歡用手機安全的用戶,這個service很可能被他殺死;二是雖然service可以運行在后臺,但在手機休眠的情況下線程好像是被掛起的,這里涉及一個Android系統鎖的機制,即系統在檢測到一段時間沒有活躍以后,會關閉一些不必要的服務來減少資源和電量消耗,這跟很多應用表現出來的都不一樣,不符合用戶習慣。因此我們還是選擇第二種方案。
方案二:service+AlarmManager+Thread
雖然alarm的意思是鬧鐘,而且在原生android自帶的鬧鐘應用中AlarmManager也確實非常重要,但并不代表AlarmManager只是用來做鬧鐘應用的,作為一個一種系統級別的提示服務,肯定應該有著非常重要的地位,實際上android中很多東西都可以利用AlarmManager來實現。
AlarmManager在特定的時刻為我們廣播一個指定的Intent。簡單的說就是我們設定一個時間,然后在該時間到來時,AlarmManager為我們廣播一個我們設定的Intent。這個intent可以指向一個activity,也可以指向一個service。
下面就是使用alarm定時調用service實現輪詢的實現方法:
一、新建輪詢工具類PollingUtils.java
public class PollingUtils { //開啟輪詢服務 public static void startPollingService(Context context, int seconds, Class<?> cls,String action) {
//獲取AlarmManager系統服務 AlarmManager manager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); //包裝需要執行Service的Intent Intent intent = new Intent(context, cls); intent.setAction(action); PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); //觸發服務的起始時間 long triggerAtTime = SystemClock.elapsedRealtime(); //使用AlarmManger的setRepeating方法設置定期執行的時間間隔(seconds秒)和需要執行的Service manager.setRepeating(AlarmManager.ELAPSED_REALTIME, triggerAtTime, seconds * 1000, pendingIntent);
} //停止輪詢服務 public static void stopPollingService(Context context, Class<?> cls,String action) {
AlarmManager manager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, cls); intent.setAction(action); PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); //取消正在執行的服務 manager.cancel(pendingIntent);
} }</pre>
二、構建輪詢任務執行PollingService.java
public class PollingService extends Service { public static final String ACTION = "com.ryantang.service.PollingService";
private Notification mNotification; private NotificationManager mManager; @Override public IBinder onBind(Intent intent) {
return null;
} @Override public void onCreate() {
initNotifiManager();
}
@Override public void onStart(Intent intent, int startId) {
new PollingThread().start();
} //初始化通知欄配置 private void initNotifiManager() {
mManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); int icon = R.drawable.ic_launcher; mNotification = new Notification(); mNotification.icon = icon; mNotification.tickerText = "New Message"; mNotification.defaults |= Notification.DEFAULT_SOUND; mNotification.flags = Notification.FLAG_AUTO_CANCEL;
} //彈出Notification private void showNotification() {
mNotification.when = System.currentTimeMillis(); //Navigator to the new activity when click the notification title Intent i = new Intent(this, MessageActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, i, Intent.FLAG_ACTIVITY_NEW_TASK); mNotification.setLatestEventInfo(this, getResources().getString(R.string.app_name), "You have new message!", pendingIntent); mManager.notify(0, mNotification);
} /**
- Polling thread
- 模擬向Server輪詢的異步線程
- @Author Ryan
@Create 2013-7-13 上午10:18:34 */ int count = 0; class PollingThread extends Thread { @Override public void run() {
System.out.println("Polling..."); count ++; //當計數能被5整除時彈出通知 if (count % 5 == 0) { showNotification(); System.out.println("New message!"); }
} }
@Override public void onDestroy() { super.onDestroy(); System.out.println("Service:onDestroy"); } }</pre>
三、在MainActivity.java中開啟和停止PollingService
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Start polling service System.out.println("Start polling service..."); PollingUtils.startPollingService(this, 5, PollingService.class, PollingService.ACTION); }
@Override protected void onDestroy() { super.onDestroy(); //Stop polling service System.out.println("Stop polling service..."); PollingUtils.stopPollingService(this, PollingService.class, PollingService.ACTION); } }</pre>
可以看出第二種方案和第一種方案的本質區別是實現定時查詢的方式不同,一種是利用系統服務,一種是自己通過while循環。顯然使用系統服務具有更高的穩定性,而且恰好解決了休眠狀態下輪詢中斷的問題,因為AlarmManager是始終運行者的。