OSChina客戶端源碼學習(3)--輪詢機制的實現

主要以OSChina Android客戶端源碼中Notice的輪詢機制進行解讀。

一、基礎知識

  1. 一般IM(即使通訊)的實現有兩種方式:推送和輪詢,推送就是服務器主動向客戶端發送消息,用特定的協議比如XMPP、MQTT。另一種是輪詢,實時性並不高,而且比較耗電。這種有分爲兩種情況:一段時間發起一次查詢和死循環進行查詢。
    參考: http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0401/1609.html

  2. 遠端Service調用:
    a.服務端:
    0.自定義接口 (AIDL定義接口文件),然後自動生成對應的java類。
    1.繼承Stub類,複寫接口中定義的方法
    2.將1中的類對象作爲Service中onBind方法的返回值,也就是將來信息交流的使者。
    b.客戶端
    3.在Activity或其他工具類中,創建ServiceConnection 對象,在onServiceConnected回調方法中,將第二個參數轉化賦值給本地變量,通過這個變量進行與遠端服務交互(通信)。

以上總結很簡陋,後面我會結合OSChina的Android源碼詳細爲大家解讀。

參考:http://blog.csdn.net/guolin_blog/article/details/9797169

二、源碼解析

1 綁定了服務

首先在MainActivity中綁定了服務(開啓了服務)

MainActivity初始化init()中

NoticeUtils.bindToService(this);

其中bindToService實現

public staticboolean bindToService(Context context,
           ServiceConnection callback) {
//直接開啓本地NoticeService服務,(注:startService方式並不能進行進行通信)
       context.startService(new Intent(context, NoticeService.class));
//綁定遠程NoticeService 服務,
//csp:爲什麼同一個服務用兩種不同的方式開啓?
//answer: 可能是先開啓本地服務,然後把綁定本地服務當做遠程服務來處理,目的可能是爲了創造不同的進程,提高效率?
       ServiceBinder sb = new ServiceBinder(callback);
       sConnectionMap.put(context, sb);

       return context.bindService(
                (newIntent()).setClass(context, NoticeService.class), sb, 0);
    }

//最後,總之開啓了服務NoticeService

2 採用Service+AlarmManager+Thread方式輪詢

在NoticeService中的onCreate方法中用AlarmManager的方式,每隔2分鐘執行一次(輪詢)請求,看是否有新的消息通知(這種方式適合通信實時性不高的情況,比如論壇的回覆,你並不需要立馬知道別人的回覆,晚個1-2分鐘是可以接受的。)

mAlarmMgr = (AlarmManager)getSystemService(ALARM_SERVICE);
       startRequestAlarm();
private voidstartRequestAlarm() {
       cancelRequestAlarm();
       // 從1秒後開始,每隔2分鐘執行getOperationIntent()
       mAlarmMgr.setRepeating(AlarmManager.RTC_WAKEUP,
                System.currentTimeMillis()+ 1000, INTERVAL,
                getOperationIntent());
    }

其中getOperationIntent()實現如下:

  /**
    * OSC採用輪詢方式實現消息推送<br>
    * 每次被調用都去執行一次{@link #AlarmReceiver}onReceive()方法
    *
    * @return
    */
    privatePendingIntent getOperationIntent() {
       Intent intent = new Intent(this, AlarmReceiver.class);
       PendingIntent operation = PendingIntent.getBroadcast(this,0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
       return operation;
}

在AlarmReceiver類中調用NoticeUtils.requestNotice方法如下
該方法首先判斷遠端Service的onBind返回來的sService對象是否爲空,如果連接上了,不爲空,則調用該sService對象的方法requestNotice(),否則發送廣播,請求訪問服務器更新Notice

 publicstatic void requestNotice(Context context) {
       if (sService != null) {
           try {
                TLog.log("requestNotice...");
                //這裏的sService,在下面進行詳細的解讀
                sService.requestNotice();
           } catch (RemoteException e) {
                e.printStackTrace();
           }
       } else {
           context.sendBroadcast(new Intent(
                    NoticeService.INTENT_ACTION_REQUEST));
           TLog.log("requestNotice,service is null");

最後就是requestNotice的實現了:

    publicstatic void getNotices(AsyncHttpResponseHandler handler) {
       RequestParams params = new RequestParams();
       params.put("uid", AppContext.getInstance().getLoginUid());
       ApiHttpClient.get("action/api/user_notice", params, handler);
    }

3 詳細解讀requestNotice中的sService

還是先上源碼,以下是調用遠程Service的步驟:
(0)用AIDL自定義一 個接口文件INoticeService.aidl

 package net.oschina.app.service;
interface INoticeService
{ 
   void scheduleNotice();
   void requestNotice();
   void clearNotice(int uid,int type);
} 

然後點擊保存之後,gen目錄下就會生成一個對應的Java文件:INoticeService.java

這裏寫圖片描述

然後,我們打開看一下里面的代碼:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: E:\\oschina-android-app-v2.2.1\\android-app\\osc-android-app\\src\\net\\oschina\\app\\service\\INoticeService.aidl
 */
package net.oschina.app.service;
public interface INoticeService extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements net.oschina.app.service.INoticeService
{
...//Stub類的內容較長,我在此省略
}
public void scheduleNotice() throws android.os.RemoteException;
public void requestNotice() throws android.os.RemoteException;
public void clearNotice(int uid, int type) throws android.os.RemoteException;
}

ADT自帶工具aidl.exe生成的代碼的看點在於,生成了一個Stub類,該類1.繼承了Binder(Binder是IBinder接口的一個實現類)因此將來可以作爲Service的onBind方法的返回值,2.實現了自定義的接口(INoticeService.aidl),將來可以複寫或調用。

(1)定義一個Stub的子類:ServiceStub,複寫自定義接口中的三個方法。

 private static class ServiceStub extends INoticeService.Stub {
        WeakReference<NoticeService> mService;

        ServiceStub(NoticeService service) {
            mService = new WeakReference<NoticeService>(service);
        }

        @Override
        public void clearNotice(int uid, int type) throws RemoteException {
            mService.get().clearNotice(uid, type);
        }

        @Override
        public void scheduleNotice() throws RemoteException {
            mService.get().startRequestAlarm();
        }

        @Override
        public void requestNotice() throws RemoteException {
            mService.get().requestNotice();
        }
    }

(2)在NoticeService中將(1)的對象作爲onBind方法的返回值返回。


   private final IBinder mBinder = new ServiceStub(this);

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

(3)創建ServiceConnection 對象
源碼在NoticeUtils中定義了一個ServiceBinder 類實現ServiceConnection 接口,複寫了兩個回調函數(當連接遠端服務成功和連接遠端服務失敗)


 private static class ServiceBinder implements ServiceConnection {
        ServiceConnection mCallback;

        ServiceBinder(ServiceConnection callback) {
            mCallback = callback;
        }

        @Override
        public void onServiceConnected(ComponentName className,
                android.os.IBinder service) {
            //第二個參數service:獲取遠程Service的onBind方法返回的對象的代理
            //下面一句是將代理轉換爲對象
            sService = INoticeService.Stub.asInterface(service);
            if (mCallback != null) {
                mCallback.onServiceConnected(className, service);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName className) {
            if (mCallback != null) {
                mCallback.onServiceDisconnected(className);
            }
            sService = null;
        }
    }

上面的代碼告訴我們,如果連接遠端服務(NoticeService)成功,則可以通過遠端onBind方法返回的對象(即onServiceConnected方法的第二個參數)來進行通信,這裏值得一說的是遠端服務返回來的只是對象的代理,這一點區別於綁定本地服務,所以要進行轉換,轉化的方法就是這一句: sService = INoticeService.Stub.asInterface(service)。
至此,我們已經徹底知道了sService的由來。

三、總結

  1. 通過閱讀這一部分的源代碼,我大致理清了客戶端通過輪詢的方式實現IM的同步的過程。
  2. 遠端Service的使用方法。這裏補充幾點:
    a. MainAcitivy中開一個Service,實際上是在同一個線程中,所以不要將耗時操作直接寫在Service的onCreate方法裏,而應該另外開啓一個線程去操作 。
    可以參考:http://blog.csdn.net/guolin_blog/article/details/11952435
    b. 所謂綁定“遠端服務”(有的書上叫遠程服務),本質就是IPC(inter process communication)跨進程通信,Android提供了AIDL Service,底層是又Binder機制實現。注意:這裏說的“遠端”不是C/S中的Server,而是充當提供服務的Service,它可以用來共享,所有訪問遠端Service的,都被統稱爲Client。
    所以,說白了,這裏扮演C/S的,可以是兩個不同的進程,可以是兩個不同的應用程序,其中一個應用程序共享了自己的一個Service組件,充當另一個應用程序的遠端Service,另一個應用程序則充當Client的角色,可以進行訪問遠端Service

最後,歡迎拍磚。。。

作者:項昂之
時間:2015.7.20
轉載註明出處:http://blog.csdn.net/csp277?viewmode=list

發佈了34 篇原創文章 · 獲贊 5 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章