Android IntentService完全解析 當Service遇到Handler

許佳佳233發表於2016-04-06

轉載請標明出處: 
http://blog.csdn.net/lmj623565791/article/details/47143563; 
本文出自:【張鴻洋的部落格】

一 概述

大家都清楚,在Android的開發中,凡是遇到耗時的操作儘可能的會交給Service去做,比如我們上傳多張圖,上傳的過程使用者可能將應用置於後臺,然後幹別的去了,我們的Activity就很可能會被殺死,所以可以考慮將上傳操作交給Service去做,如果擔心Service被殺,還能通過設定startForeground(int, Notification)方法提升其優先順序。

那麼,在Service裡面我們肯定不能直接進行耗時操作,一般都需要去開啟子執行緒去做一些事情,自己去管理Service的生命週期以及子執行緒並非是個優雅的做法;好在Android給我們提供了一個類,叫做IntentService,我們看下注釋。

IntentService is a base class for {@link Service}s that handle asynchronous 
requests (expressed as {@link Intent}s) on demand. Clients send requests 
through {@link android.content.Context#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.

意思說IntentService是一個基於Service的一個類,用來處理非同步的請求。你可以通過startService(Intent)來提交請求,該Service會在需要的時候建立,當完成所有的任務以後自己關閉,且請求是在工作執行緒處理的。

這麼說,我們使用了IntentService最起碼有兩個好處,一方面不需要自己去new Thread了;另一方面不需要考慮在什麼時候關閉該Service了。

好了,那麼接下來我們就來看一個完整的例子。

二 IntentService的使用

我們就來演示一個多個圖片上傳的案例,當然我們會模擬上傳的耗時,畢竟我們的重心在IntentService的使用和原始碼解析上。

首先看下效果圖

效果圖

每當我們點選一次按鈕,會將一個任務交給後臺的Service去處理,後臺的Service每處理完成一個請求就會反饋給Activity,然後Activity去更新UI。當所有的任務完成的時候,後臺的Service會退出,不會佔據任何記憶體。

Service

package com.zhy.blogcodes.intentservice;

import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class UploadImgService extends IntentService
{
    private static final String ACTION_UPLOAD_IMG = "com.zhy.blogcodes.intentservice.action.UPLOAD_IMAGE";
    public static final String EXTRA_IMG_PATH = "com.zhy.blogcodes.intentservice.extra.IMG_PATH";

    public static void startUploadImg(Context context, String path)
    {
        Intent intent = new Intent(context, UploadImgService.class);
        intent.setAction(ACTION_UPLOAD_IMG);
        intent.putExtra(EXTRA_IMG_PATH, path);
        context.startService(intent);
    }


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

    @Override
    protected void onHandleIntent(Intent intent)
    {
        if (intent != null)
        {
            final String action = intent.getAction();
            if (ACTION_UPLOAD_IMG.equals(action))
            {
                final String path = intent.getStringExtra(EXTRA_IMG_PATH);
                handleUploadImg(path);
            }
        }
    }

    private void handleUploadImg(String path)
    {
        try
        {
            //模擬上傳耗時
            Thread.sleep(3000);

            Intent intent = new Intent(IntentServiceActivity.UPLOAD_RESULT);
            intent.putExtra(EXTRA_IMG_PATH, path);
            sendBroadcast(intent);

        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }


    }

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

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

程式碼很短,主要就是繼承IntentService,然後複寫onHandleIntent方法,根據傳入的intent來選擇具體的操作。startUploadImg是我寫的一個輔助方法,省的每次都去構建Intent,startService了。

Activity

package com.zhy.blogcodes.intentservice;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.zhy.blogcodes.R;

public class IntentServiceActivity extends AppCompatActivity
{

    public static final String UPLOAD_RESULT = "com.zhy.blogcodes.intentservice.UPLOAD_RESULT";

    private LinearLayout mLyTaskContainer;

    private BroadcastReceiver uploadImgReceiver = new BroadcastReceiver()
    {
        @Override
        public void onReceive(Context context, Intent intent)
        {
            if (intent.getAction() == UPLOAD_RESULT)
            {
                String path = intent.getStringExtra(UploadImgService.EXTRA_IMG_PATH);

                handleResult(path);

            }

        }
    };

    private void handleResult(String path)
    {
        TextView tv = (TextView) mLyTaskContainer.findViewWithTag(path);
        tv.setText(path + " upload success ~~~ ");
    }


    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent_service);

        mLyTaskContainer = (LinearLayout) findViewById(R.id.id_ll_taskcontainer);

        registerReceiver();
    }

    private void registerReceiver()
    {
        IntentFilter filter = new IntentFilter();
        filter.addAction(UPLOAD_RESULT);
        registerReceiver(uploadImgReceiver, filter);
    }

    int i = 0;

    public void addTask(View view)
    {
        //模擬路徑
        String path = "/sdcard/imgs/" + (++i) + ".png";
        UploadImgService.startUploadImg(this, path);

        TextView tv = new TextView(this);
        mLyTaskContainer.addView(tv);
        tv.setText(path + " is uploading ...");
        tv.setTag(path);
    }


    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        unregisterReceiver(uploadImgReceiver);
    }
}


Activity中,每當我點選一次按鈕呼叫addTask,就回模擬建立一個任務,然後交給IntentService去處理。

注意,當Service的每個任務完成的時候,會傳送一個廣播,我們在Activity的onCreate和onDestroy裡面分別註冊和解註冊了廣播;當收到廣播則更新指定的UI。

佈局檔案

<LinearLayout android:id="@+id/id_ll_taskcontainer"
              xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
             >


    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:onClick="addTask" android:text="add Task"/>
</LinearLayout>

ok,這樣我們就完成了我們的效果圖的需求;通過上例,大家可以看到我們可以使用IntentService非常方便的處理後臺任務,遮蔽了諸多細節;而Service與Activity通訊呢,我們選擇了廣播的方式(當然這裡也可以使用LocalBroadcastManager)。

學會了使用之後,我們再一鼓作氣的看看其內部的實現。

三 IntentService原始碼解析

直接看IntentService原始碼

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;


public abstract class IntentService 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);
            stopSelf(msg.arg1);
        }
    }


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


    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

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

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

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


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }


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


    protected abstract void onHandleIntent(Intent intent);
}

可以看到它在onCreate裡面初始化了一個HandlerThread,關於HandlerThread的使用和原始碼 
分析參考:Android HandlerThread 完全解析,看到這估計已經能猜到它的邏輯了:

就是每次呼叫onStartCommand的時候,通過mServiceHandler傳送一個訊息,訊息中包含我們的intent。然後在該mServiceHandler的handleMessage中去回撥onHandleIntent(intent);就可以了。

那麼我們具體看一下原始碼,果然是這樣,onStartCommand中回撥了onStart,onStart中通過mServiceHandler傳送訊息到該handler的handleMessage中去。最後handleMessage中回撥onHandleIntent(intent)。

注意下:回撥完成後回撥用 stopSelf(msg.arg1),注意這個msg.arg1是個int值,相當於一個請求的唯一標識。每傳送一個請求,會生成一個唯一的標識,然後將請求放入佇列,當全部執行完成(最後一個請求也就相當於getLastStartId == startId),或者當前傳送的標識是最近發出的那一個(getLastStartId == startId),則會銷燬我們的Service.

如果傳入的是-1則直接銷燬。

那麼,當任務完成銷燬Service回撥onDestory,可以看到在onDestroy中釋放了我們的Looper:mServiceLooper.quit()。

ok~ 如果你的需求可以使用IntentService來做,可以儘可能的使用,設計的還是相當讚的。當然了,如果你需要考慮併發等等需求,那麼可能需要自己去擴充套件建立執行緒池等。

原始碼點選下載

ok~~

相關文章