andriod搭建自己的輪詢框架

Hanking發表於2019-01-03

前言

很多時候Android應用需要每間隔一段時間向伺服器請求資料,如果伺服器資料有更新則通知介面變化。Android中最常用的紅點一般採用的就是輪詢,紅點是為了在資料有更新時及時的提醒使用者,比如朋友圈更新,當使用者的朋友圈更新時就會顯示紅點,就是通過移動端不斷的向伺服器查詢朋友圈的更新狀態。

相關知識點

在實現輪詢框架時會主要會要到下面兩個類,會結合輪詢框架對這三個類進行講解,在應用中分析會理解更加深刻。

1、IntentService IntentService是一種特殊的Service,繼承了Service並且是一個抽象類,必須建立它的子類才能用。IntentService可以用於執行後臺耗時的任務,當任務執行後會自動停止,IntentService的優先順序比一般的執行緒高,比較適合執行一些優先順序高的後臺任務。

2、PendingIntent PendingIntent是延遲的intent,主要用來在某個事件完成後執行特定的Action。PendingIntent包含了Intent及Context,所以就算Intent所屬程式結束,PendingIntent依然有效,可以在其他程式中使用。PendingIntent一般作為引數傳給某個例項,在該例項完成某個操作後自動執行PendingIntent上的Action,也可以通過PendingIntent的send函式手動執行,並可以在send函式中設定OnFinished表示send成功後執行的動作。

輪詢框架實現

要實現輪詢,可以借鑑Handler中的looper機制,如下圖,維護一個訊息佇列,迴圈的從訊息佇列中取出訊息來執行,輪詢框架可以定時的向訊息佇列中加入訊息,然後迴圈中訊息佇列中取出訊息執行。

在這裡插入圖片描述
可以自己實現一個Looper,但是IntentService中已經包含了一個Looper和一個HandlerThread。因此輪詢框架中使用IntentService作為迴圈框架。繼承IntentService介面來實現處理訊息訪問伺服器。

PollingService 用於每次輪詢時向請求伺服器介面資料

public class PollingService extends IntentService {
	public static final String ACTION_CHECK_CIRCLE_UPDATE="ACTION_CHECK_CIRCLE_UPDATE";	
	public static final long DEFAULT_MIN_POLLING_INTERVAL = 60000;//最短輪詢間隔1分鐘
    public PollingService() {
        super("PollingService");
    }
	
    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null)
            return;
        final String action = intent.getAction();
        if (ACTION_CHECK_Circle_UPDATE.equals(action)) {
            CheckCircleOfFriendsUpdate();//這個是訪問伺服器獲取朋友圈是否更新
        }
    }
}
複製程式碼

PollingService 用來處理接到輪詢的訊息之後在onHandleIntent(Intent intent)中根據Intent所帶有的action不同來進行訪問伺服器不同的介面獲取資料。

PollingUtil 用於控制輪詢服務的開始和結束 使用PollingUtil中的startPollingService來根據action和context生成一個PendingIntent,並將PendingIntent交給PollingScheduler來處理。PollingScheduler是一個執行緒池控制類。

public class PollingUtil {
    /**
     * 開始輪詢服務
     */
    public static void startPollingService(final Context context, String action) {
            //包裝需要執行Service的Intent
            Intent intent = new Intent(context, PollingService.class);
            intent.setAction(action);
            PendingIntent pendingIntent = PendingIntent.getService(context, 0,
                    intent, PendingIntent.FLAG_UPDATE_CURRENT);
            PollingScheduler.getInstance().addScheduleTask(pendingIntent, 0, PollingService.DEFAULT_MIN_POLLING_INTERVAL);
        }
    }
    /**
     * 停止輪詢服務
     *
     * @param context
     */
    public static void stopPollingServices(Context context, String action) {
            PollingScheduler.getInstance().clearScheduleTasks();
        }
    }
複製程式碼

PollingScheduler實現定時向IntentService的Looper中加入訊息 PollingScheduler中生成一個單執行緒池,addScheduleTask中定時的執行pendingIntent.send(),其中PendingIntent是由PendingIntent pendingIntent = PendingIntent.getService(context, 0,intent, PendingIntent.FLAG_UPDATE_CURRENT);生成的,pendingIntent.send()函式會呼叫Service.startService()來開啟一個服務。

public class PollingScheduler {
    private static PollingScheduler sInstance;
    private ScheduledExecutorService mScheduler;

    private PollingScheduler() {
        mScheduler = Executors.newSingleThreadScheduledExecutor();
    }

    public static synchronized PollingScheduler getInstance() {
        if (sInstance == null) {
            sInstance = new PollingScheduler();
        }
        if (sInstance.mScheduler.isShutdown()) {
            sInstance.mScheduler = Executors.newSingleThreadScheduledExecutor();
        }
        return sInstance;
    }
	
    public void addScheduleTask(final PendingIntent pendingIntent, long initialDelay, long period) {
        Runnable command = new Runnable() {
            @Override
            public void run() {
                try {
                    pendingIntent.send();
                } catch (PendingIntent.CanceledException e) {
                    e.printStackTrace();
                }
            }
        };
        mScheduler.scheduleAtFixedRate(command, initialDelay, period, TimeUnit.MILLISECONDS);
    }

    public void clearScheduleTasks() {
        mScheduler.shutdownNow();
    }
}
複製程式碼

程式碼分析

先給出類圖之間的關係如下:

在這裡插入圖片描述
PollingService繼承了IntentService,並且在PollingUtil的startPollingService方法中通過Intent intent = new Intent(context, PollingService.class);和將PendingIntent 與PollingService關聯起來,並將PendingIntent加入到定時執行的執行緒池中,在PollingScheduler 中使用pendingIntent.send();由於PendingIntent與PollingService關聯,所以執行pendingIntent.send()的時候會呼叫PollingIntentServide中的onStart()方法。onStart()方法是IntentService中的方法,程式碼如下:

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
複製程式碼

在onstart()中有一個mServiceHandler.sendMessage(msg);,找到mServiceHandler的生成位置:

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
複製程式碼

在IntentService的onCreate方法中生成了一個HandlerThread,一個mServiceLooper,一個mServiceHandler,其中mServiceHandler.sendMessage(msg)中的msg都會放到mServiceLooper,執行時從mServiceLooper中取出執行,其中ServiceHandler 的程式碼如下

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
複製程式碼

handleMessage(Message msg)中會呼叫onHandleIntent((Intent)msg.obj);方法,也就是在PollingService中重寫的onHandleIntent方法。因此我們在addScheduleTask中不斷的執行pending.send()方法,會不斷的呼叫IntentService中的onStart方法中的mServiceHandler.sendMessage(msg);不斷的向訊息佇列中發訊息,然後在onHandleIntent處理訊息。 這樣一個輪詢框架就完成了。

總結

本文的輪詢框架利用了IntentService中的handler和Looper機制來實現迴圈的處理訊息,由於IntentService具有服務的特性因此特別適合後臺輪詢訪問伺服器資料。

更改

經過評論區的提醒,又測試了幾遍發現每次輪詢確實都會新建和銷燬IntentService,這樣就沒有利用到訊息佇列,所以重寫了一個PollingIntentService類繼承Service,使得每次使用時不會重寫建立Service,達到複用的效果。同時增加了enterPollingQueue()方法,可以直接往PollingIntentService的佇列中增加輪詢的Intent訊息。 PollingIntentService程式碼

 * Created time 11:40.
 *
 * @author huhanjun
 * @since 2019/1/7
 */
public abstract class PollingIntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent) msg.obj);
        }
    }

    public PollingIntentService(String name) {
        super();
        mName = name;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
	//進入輪詢佇列
    public void enterPollingQueue(@Nullable Intent intent, int startId) {
        Log.d(TAG, "enterPollingQueue");
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        enterPollingQueue(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
	//停止服務
    public void onStopService() {
        stopSelf();
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
        Log.d(TAG, "onDestroy");
    }

    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);

}
複製程式碼

PollingIntentService 繼承Service,在ServiceHandler 的handleMessage方法執行後並不執行stopSelf方法,而是專門提供了onStopService方法來停止整個Service。另外暴露出enterPollingQueue方法,可以直接通過這個方法往輪詢佇列中加入輪詢訊息。 使用: 只需將

PollingService extends IntentService{
}
複製程式碼

改為

PollingService extends PollingIntentService{
}
複製程式碼

原始碼地址:github.com/hankinghu/P…

輪詢框架重構

根據評論區的反饋,我將會使用WorkManager對輪詢框架進行重構,重構文章連結:juejin.im/post/5c4472…

我的scdn

blog.csdn.net/u013309870

相關文章