Android IntentService源碼分析

序言

最近有用到IntentService,對IntentService的源碼做了一定的學習,所以今天來談談我對intentservice的理解,后面的內容我會從官方文檔對intentservice的解釋、我個人的理解、源碼分析三個方面做講解。

IntentService作為Service的子類,service作為android四大組件之一,它的作用自然不言而喻。所以講IntentService之前肯定要先說說Service。

什么是Service

A Service is an application component representing either an application's desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use. Each service class must have a corresponding <service> declaration in its package's AndroidManifest.xml. Services can be started with Context.startService() and [Context.bindService()](https://developer.android.google.cn/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))
.
Note that services, like other application objects, run in the main thread of their hosting process. This means that, if your service is going to do any CPU intensive (such as MP3 playback) or blocking (such as networking) operations, it should spawn its own thread in which to do that work. More information on this can be found in Processes and Threads. The IntentService
class is available as a standard implementation of Service that has its own thread where it schedules its work to be done.

What is a Service?
Most confusion about the Service class actually revolves around what it is not:

A Service is not a separate process. The Service object itself does not imply it is running in its own process; unless otherwise specified, it runs in the same process as the application it is part of.
A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors).

這是官方文檔對service的定義,下面是我對官方文檔的翻譯(英語水平有限,如有翻譯錯誤,還請見諒)

service是app組件,它代表app希望執行較長的運行操作而不與用戶交互或為其他應
用程序提供功能的愿望.service必須在AndroidManifest.xml中注冊。啟動service的方式有Context.startService()和Context.bindService()。

注意:
service是運行在app的主線程上的,這就意味著,如果你的service將執行任何cpu密集(如mp3播放)或阻止(如網絡)操作,應該放到另外的線程上去完成這項工作。

什么是服務?
關于服務類的大多數混亂實際上都圍繞著它不是什么

1.服務不是一個單獨的進程,服務本身不意味著它在自己的進程中運行,除非有特殊的說明(可在注冊的時候指定進程,它運行在app相同的進程中。
2.服務不是一個線程,這也不是意味著service的工作是在主線程上完成的(為了避免anr的發生);

我對service的理解

1.首先service是運行在主線程上的,所以 service上不能執行耗時操作,不然會導致anr。
2.同一個service在app中只有一個實例,類似單例。
3.什時候使用service呢?我的理解是,你需要做的工作要與可視界面(activity)解耦,也就是頁面的隱藏或是關閉不會影響到你的工作,這樣的工作你需要放到service中去,當然如果的耗時操作,你也是要在service中new thread的。
4.當然service還要一個很重要的作用就是作為兩個不同app之間通訊的媒介,這個用到的是AIDL可以參考https://developer.android.google.cn/guide/components/aidl.html/

什么是IntentService

IntentService is a base class for Service that handle asynchronous requests (expressed as Intent) on demand. Clients send requests through startService(Intent)
calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

This "work queue processor" pattern is commonly used to offload tasks from an application's main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(Intent)
. IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.

All requests are handled on a single worker thread -- they may take as long as necessary (and will not block the application's main loop), but only one request will be processed at a time.

這是官方文檔對service的定義,下面是我對官方文檔的翻譯(英語水平有限,如有翻譯錯誤,還請見諒)

IntentService是Service的子類,按要求處理異步請求。通過startService發起異步任務,該服務根據需要啟動,使用一個工作線程依次處理每個異步任務,并在完成所有異步任務后關閉service。

這種“工作隊列處理器”模式通常用于從應用程序的主線程卸載任務,IntentService的存在就是方便開發者使用這種模式。使用方法:通過繼承IntentService類并實現 onHandleIntent(Intent)方法,IntentService接受到Intent后,啟動IntentService的HandlerThread處理異步任務,并且在適當的時候關閉自己。

所有請求都是在一個單獨的線程上處理的--它們可能需要盡可能長的時間(并且不會阻塞應用程序的主循環),但每次只處理一個請求。

我的理解

1.IntentService用來處理異步任務
2.IntentService中只有一個默認的HandlerThread線程用來處理異步任務,所有異步任務按發起順序以隊列的形式依次在HanderThread中完成,并且在所有任務完成自后自行關閉自己。
3.正如官方的解釋一樣,IntentService適合一些卸載任務、下載更新包的任務并安裝的任務。

源碼分析IntentService

如何實現隊列的形式依次處理異步任務

@Override
public void onCreate() {
    // TODO: It would be nice to have an option to hold a partial wakelock
    // during processing, and to have a static startService(Context, Intent)
    // method that would launch the service & hand off a wakelock.

    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

在IntentService創建的時候默認生成一個HandlerThread(IntentService中唯一的線程,并且已隊列的形式依次處理耗時任務),并指定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);
    }
}

onHandleIntent(Intent intent)是抽象方法,通過實現onHandleIntent方法,將異步任務放在onHandleIntent中實現,ServiceHandler接收到消息后 ,依次調用onHandleIntent方法和stopSelf方法,stopSelf(msg.arg1)只有在Service沒有其他任務時才會關閉自己。所以其實IntentService完成每個異步任務都會調用關閉自己的方法,只有在完成所有異步任務的時候才會正真的關閉自己。

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

/**
 * You should not override this method for your IntentService. Instead,
 * override {@link #onHandleIntent}, which the system calls when the IntentService
 * receives a start request.
 * @see android.app.Service#onStartCommand
 */
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

每次startService()都會往HandlerThread的消息隊列中發送一個消息mServiceHandler.sendMessage(msg)

來個例子

public class MyIntentService extends IntentService {

public static final  String  TAG = "MyIntentService";

public MyIntentService() {
    //調用父類的構造函數
    //構造函數參數=工作線程的名字
    super("MyIntentService");
}

/*復寫onHandleIntent()方法*/
//實現耗時任務的操作
@Override
protected void onHandleIntent(Intent intent) {
    //根據Intent的不同進行不同的事務處理
    String taskName = intent.getExtras().getString("taskName");
    switch (taskName) {
        case "task1":
            Log.e(TAG, "do task1");
            Log.e(TAG, "onHandleIntent()所在線程:"+android.os.Process.myTid());
            break;
        case "task2":
            Log.e(TAG, "do task2");
            Log.e(TAG, "onHandleIntent()所在線程:"+android.os.Process.myTid());
            break;
        default:
            break;
    }
}


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

/*復寫onStartCommand()方法*/
//默認實現將請求的Intent添加到工作隊列里
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.e(TAG, "onStartCommand");
    Log.e(TAG,"MyIntentService所在的線程是"+android.os.Process.myTid());
    return super.onStartCommand(intent, flags, startId);
}

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

}


public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //同一服務只會開啟一個工作線程
    //在onHandleIntent函數里依次處理intent請求。

    findViewById(R.id.test).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG, "MainActivity所在的線程:"+android.os.Process.myTid());
            Intent i = new Intent(v.getContext(),MyIntentService.class);
            Bundle bundle = new Bundle();
            bundle.putString("taskName", "task1");
            i.putExtras(bundle);
            startService(i);

            Intent i2= new Intent(v.getContext(),MyIntentService.class);
            Bundle bundle2 = new Bundle();
            bundle2.putString("taskName", "task2");
            i2.putExtras(bundle2);
            startService(i2);
            startService(i);  //多次啟動
        }
    });

}

}
Paste_Image.png

從打印的日志就能看出,主線程id是8211 IntentService的onStartCommand()也是在主線程8211中執行的,而兩個task都是在另外的一個線程8928中執行的,而且在同一個線程中根據發起順序依次執行。

到這里基本就結束了,希望我寫的東西能對大家有所幫助,如有不足的地方希望大家多多指教。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • 1.什么是Activity?問的不太多,說點有深度的 四大組件之一,一般的,一個用戶交互界面對應一個activit...
    JoonyLee閱讀 5,752評論 2 51
  • 本文出自 Eddy Wiki ,轉載請注明出處:http://eddy.wiki/interview-androi...
    eddy_wiki閱讀 3,290評論 0 20
  • 一、IntentService簡介 IntentService是Service的子類,比普通的Service增加了...
    Ten_Minutes閱讀 1,636評論 2 6
  • 開會,是個司空見慣卻又讓人疲憊不堪的事情。在過去的一年中經歷了公司大大小小的各種會議。管理層周例會,部門例會,產品...
    知否讀書閱讀 632評論 1 7
  • 提起顧城,只是在高中時,語文老師本機介紹過兩首他的詩 -黑夜給了我黑色的眼睛,我卻用它尋找光明 -你一會兒看我,一...
    跳舞的小雪人閱讀 373評論 0 0