Android 進階16:IntentService 使用及源碼解析


    

Android 進階16:IntentService 使用及源碼解析

標籤: android源碼
378人閱讀 評論(0) 收藏 舉報
本文章已收錄於:
分類:
  • It’s time to start living the life you’ve only imagined.

讀完本文你將瞭解:

在前面兩篇文章 源碼解讀 Android 消息機制( Message MessageQueue Handler Looper) HandlerThread 使用場景及源碼解析 中我們瞭解了 Android 中執行異步任務的兩種方式。

本篇文章介紹另外一種:IntentService。

IntentService 簡介

public abstract class IntentService extends Service {...}
  • 1
  • 1

IntentService 是一個抽象類,繼承了 Service

由於是一個 Service,IntentService 的優先級比較高,在後臺不會輕易被系統殺死;它可以接收 Intent 請求,然後在子線程中按順序執行。

官方文檔關於它的介紹:

IntentService 使用工作線程逐一處理所有啓動請求。如果你不需要在 Service 中執行併發任務,IntentService 是最好的選擇。

IntentService 源碼分析

IntentService 源碼很短:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    //內部創建的 Handler
    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;
    }

    //設置當前服務被意外關閉後是否重新
    //如果設置爲 true,onStartCommand() 方法將返回 Service.START_REDELIVER_INTENT,這樣當
    //當前進程在 onHandleIntent() 方法返回前銷燬時,會重啓進程,重新使用之前的 Intent 啓動這個服務
    //(如果有多個 Intent,只會使用最後的一個)
    //如果設置爲 false,onStartCommand() 方法返回 Service.START_NOT_STICKY,當進程銷燬後也不重啓服務
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //創建時啓動一個 HandlerThread
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        //拿到 HandlerThread 中的 Looper,然後創建一個子線程中的 Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        //將 intent 和 startId 以消息的形式發送到 Handler
        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;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();    //值得學習的,在銷燬時退出 Looper
    }

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

    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

從上述代碼可以看到,IntentService 做了以下工作:

  • 創建了一個 HandlerThread 默認的工作線程
  • 使用 HandlerThreadLooper 創建了一個 Handler,這個 Handler 執行在子線程
  • onStartCommand() 中調用 onStart(),然後在 onStart() 中將 intent 和 startId 以消息的形式發送到 Handler
  • Handler 中將消息隊列中的 Intent 按順序傳遞給 onHandleIntent() 方法
  • 在處理完所有啓動請求後自動停止服務,不需要我們調用 stopSelf()
public void handleMessage(Message msg) {
    onHandleIntent((Intent)msg.obj);
    stopSelf(msg.arg1);
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

有同學可能有疑問,在 handleMessage 方法中不是調用了一次 onHandleIntent() 後就調用 stopSelf() 了嗎,這不是隻能執行一個任務麼?

仔細看下可以發現,這個 stopSelf() 方法傳遞了一個 id,這個 id 是啓動服務時 IActivityManager 分配的 id,當我們調用 stopSelf(id) 方法結束服務時,IActivityManager 會對比當前 id 是否爲最新啓動該服務的 id,如果是就關閉服務。

public final void stopSelf(int startId) {
    if (mActivityManager == null) {
        return;
    }
    try {
        mActivityManager.stopServiceToken(
                new ComponentName(this, mClassName), mToken, startId);
    } catch (RemoteException ex) {
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

因此只有當最後一次啓動 IntentService 的任務執行完畢纔會關閉這個服務。

此外還要注意的是,IntentService 中除了 onHandleIntent 方法其他都是運行在主線程的。

IntentService 的使用

通過前面的源碼分析,我們可以看到,最終每個任務的處理都會調用 onHandleIntent(),因此使用 IntentService 也很簡單,只需實現 onHandleIntent() 方法,在這裏執行對應的後臺工作即可。

舉個例子:

我們寫一個使用 IntentService 實現在子線程下載多張 美女圖片 的效果。

創建 IntentService 的子類

/**
 * Description:
 * <br> 使用 IntentService 實現下載
 * <p>
 * <br> Created by shixinzhang on 17/6/8.
 * <p>
 * <br> Email: [email protected]
 * <p>
 * <a  href="https://about.me/shixinzhang">About me</a>
 */

public class DownloadService extends IntentService {
    private static final String TAG = "DownloadService";
    public static final String DOWNLOAD_URL = "down_load_url";
    public static final int WHAT_DOWNLOAD_FINISHED = 1;
    public static final int WHAT_DOWNLOAD_STARTED = 2;

    public DownloadService() {
        super(TAG);
    }

    private static Handler mUIHandler;

    public static void setUIHandler(final Handler UIHandler) {
        mUIHandler = UIHandler;
    }

    /**
     * 這個方法運行在子線程
     *
     * @param intent
     */
    @Override
    protected void onHandleIntent(final Intent intent) {
        String url = intent.getStringExtra(DOWNLOAD_URL);
        if (!TextUtils.isEmpty(url)) {
            sendMessageToMainThread(WHAT_DOWNLOAD_STARTED, "\n " + DateUtils.getCurrentTime() + " 開始下載任務:\n" + url);
            try {
                Bitmap bitmap = downloadUrlToBitmap(url);
                SystemClock.sleep(1000);    //延遲一秒發送消息
                sendMessageToMainThread(WHAT_DOWNLOAD_FINISHED, bitmap);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 發送消息到主線程
     *
     * @param id
     * @param o
     */
    private void sendMessageToMainThread(final int id, final Object o) {
        if (mUIHandler != null) {
            mUIHandler.sendMessage(mUIHandler.obtainMessage(id, o));
        }
    }

    /**
     * 下載圖片
     *
     * @param url
     * @return
     * @throws Exception
     */
    private Bitmap downloadUrlToBitmap(String url) throws Exception {
        HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
        BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
        Bitmap bitmap = BitmapFactory.decodeStream(in);
        urlConnection.disconnect();
        in.close();
        return bitmap;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

在上面的代碼中,我們做了以下幾件事:

  • onHandleIntent() 中接收任務,開始下載,同時將狀態返回給主線程
  • 下載完成後將得到的 Bitmap 通過 Handler 發送到主線程

爲了界面上有明顯效果,設置了一定延時。

IntentService 也是 Service,別忘了在 AndroidManifest 中註冊!

佈局界面

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:gravity="center_horizontal"
              android:orientation="vertical"
              android:padding="8dp">

    <ImageView
        android:id="@+id/iv_display"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <TextView
        android:id="@+id/tv_status"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:padding="8dp"
        android:text="狀態信息:"/>

    <Button
        android:id="@+id/btn_download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="開始下載"/>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

界面上有一個開始下載按鈕,一個顯示下載狀態的 TextView,一個展示圖片的 ImageView.

調用方代碼

/**
 * Description:
 * <br> IntentService 實例
 * <p>
 * <br> Created by shixinzhang on 17/6/9.
 * <p>
 * <br> Email: [email protected]
 * <p>
 * <a  href="https://about.me/shixinzhang">About me</a>
 */

public class IntentServiceActivity extends AppCompatActivity implements Handler.Callback {

    @BindView(R.id.iv_display)
    ImageView mIvDisplay;
    @BindView(R.id.btn_download)
    Button mBtnDownload;
    @BindView(R.id.tv_status)
    TextView mTvStatus;

    private List<String> urlList = Arrays.asList("https://ws1.sinaimg.cn/large/610dc034ly1fgepc1lpvfj20u011i0wv.jpg",
            "https://ws1.sinaimg.cn/large/d23c7564ly1fg6qckyqxkj20u00zmaf1.jpg",
            "https://ws1.sinaimg.cn/large/610dc034ly1fgchgnfn7dj20u00uvgnj.jpg");    //美女圖片地址
    int mFinishCount;   //完成的任務個數

    @Override
    protected void onCreate(@Nullable final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent_service);
        ButterKnife.bind(this);
        DownloadService.setUIHandler(new Handler(this));
    }

    @OnClick(R.id.btn_download)
    public void downloadImage() {
        Intent intent = new Intent(this, DownloadService.class);

        for (String url : urlList) {
            intent.putExtra(DownloadService.DOWNLOAD_URL, url);
            startService(intent);
        }
        mBtnDownload.setEnabled(false);
    }


    @Override
    public boolean handleMessage(final Message msg) {
        if (msg != null) {
            switch (msg.what) {
                case DownloadService.WHAT_DOWNLOAD_FINISHED:
                    mIvDisplay.setImageBitmap((Bitmap) msg.obj);
                    mBtnDownload.setText("完成 " + (++mFinishCount) + "個任務");
                    break;
                case DownloadService.WHAT_DOWNLOAD_STARTED:
                    mTvStatus.setText(mTvStatus.getText() + (String) msg.obj);
                    break;
            }
        }
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

Activity 中做了以下幾件事:

  • 設置 UI 線程的 Handler 給 IntentService
  • 使用 startService(intent) 啓動 IntentService 執行圖片下載任務
  • 在 Handler 的 handleMessage 中根據消息類型進行相應處理

可以看到,調用方的代碼和上一篇使用 HandlerThread 的方法很相似。

運行效果

這裏寫圖片描述

總結

本篇文章介紹了 IntentService 的使用和源碼。

在第一次啓動 IntentService 後,IntentService 仍然可以接受新的請求,接受到的新的請求被放入了工作隊列中,等待被串行執行。

使用 IntentService 顯著簡化了啓動服務的實現,如果您決定還重寫其他回調方法(如 onCreate()、onStartCommand() 或 onDestroy()),請確保調用超類實現,以便 IntentService 能夠妥善處理工作線程的生命週期。

由於大多數啓動服務都不必同時處理多個請求(實際上,這種多線程情況可能很危險),因此使用 IntentService 類實現服務也許是最好的選擇。

一句話總結 IntentService:

  • 優先級比較高的、用於串行執行異步任務、會自盡的 Service。

代碼地址

Thanks

android 開發藝術探索》
https://developer.android.com/guide/components/services.html#ExtendingIntentService
http://rainbow702.iteye.com/blog/1143286
http://blog.csdn.net/javazejian/article/details/52426425

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章