2014年1月14日

Android Service之IntentService

前言:

對於Android的Service,一直以來都是一知半解的狀況,因此前陣子有小段空擋時,花點時間在Android Develop Guide看看Service的說明,意外看到了一個玩意,IntentService,對它有點好奇,有Service就好了為何還會有它?因此對這個類別研究並測試了一下。

淺談Service

Android Service其實有點複雜,要談的話可能需要多點篇幅,因此這邊稍微講一下它的大概就好。

在我的認知裡,其實就把它想成一個沒有畫面的Activity就好了,因為它沒有畫面,所以它也不會因為User切換Activity而被中斷掉,因此是一個很適合做背景工作的App Component,如放音樂、下載檔案等工作,畢竟你不可能讓User只能在某個特定頁面才能正常下載,而且還跟User說,不准動手機,等我抓完你才可以動這類的話吧。

而Android提供了方便的框架,讓開發者去使用自己寫的Service,也就是透過context.startService(Intent service),就可以對Service送出request,讓它做某些工作。

只是因為要讓Service能完成更全面的工作,因此Android官方在設計此Components時將此它弄的很彈性,相對的複雜度也提高。在看官方API時可能會對於context.bindService()和context.startService()差別在哪為何要用有所困惑,又或者對為什麼service.onStartCommand()的回傳值要傳特定的參數回去,每個參數有何意義,在什麼狀況下我的參數會發揮什麼作用諸如此類的。

所以在看IntentService時,只需知道幾件和Service有關的事情:

  1. Service並不會另外開一條process出來執行你的code,除非你另外指定,否則它會和application執行在同一條process內,所以你如果在這邊執行大量運算的工作,還是會出現ANR的訊息。
  2. Service並不是另開一條新的thread來執行你的工作,它還是在main thread底下,所以不能直接透過他執行網路存取的工作。

Why IntentService

由於上面提到的Service特性以及它的複雜度的關係,因此官方提供了一個簡單使用的Service來給開發者使用,也就是今天要提到的IntentService,IntentService跟Service的差別在於,系統會給IntentService獨立的一條worker thread,讓它不會和activity共用thread,然後它預設會有個queue的機制,會保證一次只有一個request會被執行而已,也就是說不管你呼叫幾次context.startService(intent),在同一時間內只有單一個request會在service.onHandleIntent(intent)執行,而當沒有request在queue內時,該IntentService會自動銷毀,讓你不需要管理它的命週期。這邊整理了IntentService的一些特點:

  1. Easy to use,一樣透過context.startService(Intent service)就可以送出request。
  2. 不需要管Service的生命週期,只要把主要工作的code寫在onHandleIntent(Intent intent)就好了,code執行完畢如果沒有其他request則它會自己銷毀。
  3. 保證一次只有一個request會被處理,其餘的request會被block住。
  4. 由於他是另外開一條worker thread的關係,因此request被block住也不會影響main thread,簡單來說就是不會出現ANR的訊息。
  5. 也由於是另一條thread的關係,因此你可以直接在onHandleIntent()內寫網路相關的程式。

測試範例:

先在resource裡面定一個Button:

<Button 
    android:id="@+id/btn_is"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="intent service go"/>

接著在Activity內寫他的事件,只寫了這段code,發送request出去:

btn_is = (Button) findViewById(R.id.btn_is);
btn_is.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        Intent intent = new Intent();
        intent.setClass(MainActivity.this, 
                    IntentServiceImpl.class);
        startService(intent);
    }
});

而主要IntentService code,在onHandleIntent()裡面會故意將Thread sleep 2秒,用以觀察request bolcking的狀況,這邊需要注意的是,要覆寫建構子回傳一個IntentServiceImpl Service的名稱給worker thread,如下::

public class IntentServiceImpl extends IntentService {
    public static final String TAG = "mayer";
    private int i = 0;

    public IntentServiceImpl() {
        super("IntentServiceImpl");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate()");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d(TAG, "onHandleIntent(), start work.");
        Log.d(TAG, "print start, init i = " + i);
        for(; i< 5; i++) {
            Log.d(TAG, "i = " + i);
        }
        try {
            TimeUnit.SECONDS.sleep(2l);
        } catch (InterruptedException e) {
            Log.e(TAG, "Error:", e);
        }
        Log.d(TAG, "onHandleIntent(), end work.");
    }

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

最後,跟一般Service一樣,需要在AndroidManifest.xml加上:

<service android:name="com.test.servicetest.IntentServiceImpl" />

範例結果:

測試時對按鈕連點三下,會輸出以下結果:

01-14 11:02:43.746: onCreate()
01-14 11:02:43.761: onHandleIntent(), start work.
01-14 11:02:43.761: print start, init i = 0
01-14 11:02:43.761: i = 0
01-14 11:02:43.761: i = 1
01-14 11:02:43.761: i = 2
01-14 11:02:43.761: i = 3
01-14 11:02:43.765: i = 4
01-14 11:02:45.765: onHandleIntent(), end work.
01-14 11:02:45.769: onHandleIntent(), start work.
01-14 11:02:45.773: print start, init i = 5
01-14 11:02:47.773: onHandleIntent(), end work.
01-14 11:02:47.777: onHandleIntent(), start work.
01-14 11:02:47.781: print start, init i = 5
01-14 11:02:49.785: onHandleIntent(), end work.
01-14 11:02:49.793: onDestroy()

由於點了按鈕三次,因此是送出了三個context.startService()的request出去。可以看到測試的Intent Service只會onCreate()一次,也就代表只會有一個Intent Service的實例被建立,接著他執行onHandleIntent()裡面的程式,由於會自己幫你block住其他的request,因此可以確保一次只有一個request會在這邊被執行,第一個request執行完之後第二個在進來執行,以此類推。當第三個request執行完時,由於沒有request了,因此這個Intent Service會自己呼叫onDestroy(),將自己銷毀。

結論:

所以這能幹麻呢?舉個例子,譬如你的Client要做圖片下載,而你又想限制一次只從Server上抓取一張圖片,一張抓完之後在抓下一張,這時你就可以用IntentService來做,當要抓圖片時把intent包一包,就直接呼叫startService(intent),然後就可以達成你的需求了!