天天看點

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();
                    }
                });
            }
        }
    }
}                

其中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);
    }
}                

二、建構輪詢任務執行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");
    }
}                

三、在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);
    }
}                

可以看出第二種方案和第一種方案的本質差別是實作定時查詢的方式不同,一種是利用系統服務,一種是自己通過while循環。顯然使用系統服務具有更高的穩定性,而且恰好解決了休眠狀态下輪詢中斷的問題,因為 AlarmManager 是始終運作者的。

版權聲明:本文為CSDN部落客「weixin_34010949」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。

原文連結:https://blog.csdn.net/weixin_34010949/article/details/91628232