轉(zhuǎn)自:泡在網(wǎng)上的日子 輪詢,android,service,alarmmanager
android中涉及到將服務器中數(shù)據(jù)變化信息通知用戶一般有兩種辦法蝙昙,推送和輪詢誉己。
消息推送是服務端主動發(fā)消息給客戶端囤锉,因為第一時間知道數(shù)據(jù)發(fā)生變化的是服務器自己芝囤,所以推送的優(yōu)勢是實時性高秕脓。但服務器主動推送需要單獨開發(fā)一套能讓客戶端持久連接的服務端程序灌灾,不過現(xiàn)在已經(jīng)有很多開源的代碼實現(xiàn)了基于xmmp協(xié)議的推送方案雕旨,而且還可以使用谷歌的推送方案扮匠。但有些情況下并不需要服務端主動推送捧请,而是在一定的時間間隔內(nèi)客戶端主動發(fā)起查詢。
譬如有這樣一個app棒搜,實時性要求不高疹蛉,每天只要能獲取10次最新數(shù)據(jù)就能滿足要求了,這種情況顯然輪詢更適合一些力麸,推送顯得太浪費可款,而且更耗電。
但是不管是輪詢還是推送都需要無論應用程序是否正在運行或者關閉的情況下能給用戶發(fā)送通知克蚂,因此都需要用到service筑舅。
我們有兩種方案來使用service達到此目的:
方案一:service +****Thread
在service中開啟一個帶有while循環(huán)的線程,使其不斷的從服務器查詢數(shù)據(jù)(一定時間間隔內(nèi))陨舱,當發(fā)現(xiàn)有需要通知用戶的情況下發(fā)送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;
/**
*
* 短信推送服務類,在后臺長期運行游盲,每個一段時間就向服務器發(fā)送一次請求
*
* @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) {
// 獲取系統(tǒng)的通知管理器
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
+ "發(fā)來短信", content, pi);
// 將消息推送到狀態(tài)欄
manager.notify(0, notification);
}
private class MyThread extends Thread {
@Override
public void run() {
String url = "你請求的網(wǎng)絡地址";
while (flag) {
System.out.println("發(fā)送請求");
try {
// 每個10秒向服務器發(fā)送一次請求
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 采用get方式向服務器發(fā)送請求
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");
// 假設偶數(shù)為未讀消息
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(), "數(shù)據(jù)請求失敗", 0)
.show();
}
});
}
}
}
}
其中AsyncHttpClient
為網(wǎng)絡異步請求的開源庫莺奔,可以很方便的實現(xiàn)異步網(wǎng)絡請求欣范。
這種方案存在的不足有很多,
一 是應用長期有一個后臺程序運行令哟,如果是一個喜歡用手機安全的用戶恼琼,這個service很可能被他殺死;
二 是雖然service可以運行在后臺屏富,但在手機休眠的情況下線程好像是被掛起的晴竞,這里涉及一個Android系統(tǒng)鎖的機制,即系統(tǒng)在檢測到一段時間沒有活躍以后狠半,會關閉一些不必要的服務來減少資源和電量消耗噩死,這跟很多應用表現(xiàn)出來的都不一樣,不符合用戶習慣神年。
因此我們還是選擇第二種方案已维。
方案二:service+AlarmManager+Thread
雖然alarm的意思是鬧鐘,而且在原生android自帶的鬧鐘應用中AlarmManager也確實非常重要已日,但并不代表AlarmManager只是用來做鬧鐘應用的垛耳,作為一個一種系統(tǒng)級別的提示服務,肯定應該有著非常重要的地位,實際上android中很多東西都可以利用AlarmManager來實現(xiàn)艾扮。
AlarmManager在特定的時刻為我們廣播一個指定的Intent既琴。簡單的說就是我們設定一個時間,然后在該時間到來時泡嘴,AlarmManager為我們廣播一個我們設定的Intent甫恩。這個intent可以指向一個activity,也可以指向一個service酌予。
下面就是使用alarm定時調(diào)用service實現(xiàn)輪詢的實現(xiàn)方法:
一磺箕、新建輪詢工具類PollingUtils.java
public class PollingUtils {
//開啟輪詢服務
public static void startPollingService(Context context, int seconds, Class<?> cls,String action) {
//獲取AlarmManager系統(tǒng)服務
AlarmManager manager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
//包裝需要執(zhí)行Service的Intent
Intent intent = new Intent(context, cls);
intent.setAction(action);
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
//觸發(fā)服務的起始時間
long triggerAtTime = SystemClock.elapsedRealtime();
//使用AlarmManger的setRepeating方法設置定期執(zhí)行的時間間隔(seconds秒)和需要執(zhí)行的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);
//取消正在執(zhí)行的服務
manager.cancel(pendingIntent);
}
}
二、構建輪詢?nèi)蝿請?zhí)行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 ++;
//當計數(shù)能被5整除時彈出通知
if (count % 5 == 0) {
showNotification();
System.out.println("New message!");
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
System.out.println("Service:onDestroy");
}
}
三抛虫、在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);
}
}
可以看出第二種方案和第一種方案的本質(zhì)區(qū)別是實現(xiàn)定時查詢的方式不同松靡,一種是利用系統(tǒng)服務,一種是自己通過while循環(huán)建椰。顯然使用系統(tǒng)服務具有更高的穩(wěn)定性雕欺,而且恰好解決了休眠狀態(tài)下輪詢中斷的問題,因為AlarmManager是始終運行者的棉姐。