IntentService——Handler與Service的結合

綜述

  我們都知道Service是作爲後臺服務運行再程序中的。但是Service他依然是運行在主線程中的,所以我們依然不能在Service中進行耗時的操作。所以當我們在Service處理時,我們需要在Service中開啓一個子線程,並且在子線程中運行。當然爲了簡化我們的操作,在Android中爲我們提供了IntentService來進行這一處理,下面我們就來看一下這個IntentService用法以及它的工作原理。

用法簡介

  IntentService它繼承自Service,一來說我們開啓一個Service可以通過startService和bindService兩個方式進行開啓一個服務,但是對於IntentService我們採用startService方法進行開啓服務,對於爲什麼要這麼做,在後面會進行分析講解。下面我們來看一下如何使用這個IntentService的。

效果演示

  在這裏我們做一個倒計時的程序,以毫秒爲單位。這裏先看一下效果演示。
這裏寫圖片描述

代碼分析

  在這裏我們使用到了開源框架EventBus,對於EventBus的使用可以參考 EventBus3.0使用詳解這篇文章。由於我們用到這EventBus,首先我們創建一個實體類,在EventBus中進行發送,接收處理。

package com.example.ljd.intentservice;

public class Counter {

    public Counter(int progress,int tag) {
        this.progress = progress;
        this.tag = tag;
    }

    public int progress;      //進度顯示

    public int tag;           //TextView的Tag
}

  下面我們看一下IntentService中的代碼。

package com.example.ljd.intentservice;

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

import org.greenrobot.eventbus.EventBus;


public class MyIntentService extends IntentService {

    private static final String ACTION_COUNTER = "com.example.ljd.intentservice.action.COUNTER";

    private static final String EXTRA_SEC = "com.example.ljd.intentservice.extra.SECOND";

    private static final String EXTRA_TAG = "com.example.ljd.intentservice.extra.TAG";

    private static final int SLEEP_TIME = 1;

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


    public static void startDownload(Context context, int second,int tag) {
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_COUNTER);
        intent.putExtra(EXTRA_SEC, second);
        intent.putExtra(EXTRA_TAG,tag);
        context.startService(intent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_COUNTER.equals(action)) {
                final int second = intent.getIntExtra(EXTRA_SEC, 0);
                final int tag = intent.getIntExtra(EXTRA_TAG,0);
                handleActionFoo(second,tag);
            }
        }
    }

    private void handleActionFoo(int sec,int tag) {
        int millis = sec * 1000;
        Counter counter = new Counter(0,tag);

        for (int i = millis;i >= 0; i-=SLEEP_TIME){
            counter.progress = i;
            EventBus.getDefault().post(counter);
            try {
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}

  在上面的handleActionFoo方法中進行我們的耗時任務。然後我們在看一下Activity中的代碼。

package com.example.ljd.intentservice;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.util.Random;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private int mTextViewTag;
    private Button mAddButton;
    private LinearLayout mContainerLinear;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getDefault().register(this);
        mTextViewTag = 0;
        mAddButton = (Button) findViewById(R.id.add_btn);
        mContainerLinear = (LinearLayout) findViewById(R.id.linear_container);
        mAddButton.setOnClickListener(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void OnEventProgress(Counter counter){
        TextView textView = (TextView)mContainerLinear.findViewWithTag(counter.tag);
        textView.setText(counter.progress + "ms");
    }

    @Override
    protected void onDestroy() {
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.add_btn:
                //生成1~3之間的隨機數
                Random random = new Random();
                int num = random.nextInt(3)%(3) + 1;

                TextView textView = new TextView(this);
                textView.setTag(mTextViewTag);
                textView.setText(num * 1000 + "ms");
                mContainerLinear.addView(textView);
                MyIntentService.startDownload(this,num,mTextViewTag);
                mTextViewTag++;
                break;
            default:
                break;
        }
    }
}

  最後是佈局代碼。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.ljd.intentservice.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/add_btn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="add"/>
    </LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:id="@+id/linear_container"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </LinearLayout>
    </ScrollView>

</LinearLayout>

  對於上面代碼實現起來都是非常的簡單,在這裏就不在進行詳細介紹。

IntentService工作原理分析

  其實對於IntentService的工作原理也不復雜,既然在IntentService中能夠進行耗時操作,也就是說在這個IntentService中必然也創建了一個子線程,在Android中我們稱爲工作者線程。然後在這個工作者線程中進行我們的任務。在分析IntentService之前,我們先看一下HandlerThread。

HandlerThread

  其實HandlerThread就是一個工作者線程,在這裏看一下HandlerThread的源碼。

package android.os;

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    public int getThreadId() {
        return mTid;
    }
}

  看過上篇文章 Android的消息機制——Handler的工作過程就很容易理解這個HandlerThread了。還記的我們在上篇文章的最後,新建了一個包含Looper的子線程。而這個HandlerThread也就是一個包含Looper的子線程。所以當我們需要創建一個包含Looper的線程時直接使用HandlerThread即可。對於HandlerThread有以下幾點需要說明一下。
  1. 在構造方法中設置線程優先級的時候,使用的Process是android.os包中的而不是java.lang包內的。
  2. 如果在Looper開啓消息循環之前我們進行一些設置,我們可以繼承HandlerThread並且重寫onLooperPrepared方法。
  3. 通過getLooper方法我們獲取HandlerThread的Looper對象時,有可能Looper還未創建完成。所以在getLooper中未創建Looper是進行了線程等待操作,在創建完Looper以後在返回Looper對象。

IntentService

  下面我們再看一下IntentService。

package android.app;

import android.annotation.WorkerThread;
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() {
        // 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);
    }

    @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;
    }

    @WorkerThread
    protected abstract void onHandleIntent(Intent intent);
}

  我們看一下這個IntentService的構造是不是很簡單。在這裏主要看一下onCreate和onStart方法即可。在onCreate中,我們開啓了一個HandlerThread線程,之後獲取HandlerThread線程中的Looper,並通過這個Looper創建了一個Handler。然後在onStart方法中通過這個Handler將intent與startId作爲Message的參數進行發送到消息隊列中,然後交由Handler中的handleMessage中進行處理。由於在onStart方法是在主線程內運行的,而Handler是通過工作者線程HandlerThread中的Looper創建的。所以也就是在主線程中發送消息,在工作者接收到消息後便可以進行一些耗時的操作。
  我們在看一下handleMessage中的操作,在handleMessage中調用onHandleIntent方法,他是一個抽象方法,所以在我們的Service中複寫onHandleIntent方法並且將耗時的操作寫在onHandleIntent方法內即可。當執行完onHandleIntent後通過stopSelf來停止服務,這樣就不用我們手動停止服務了。所以也就回答了我們上面那個爲什麼要使用startService而不用onBind來開啓一個IntentService。

總結

  從我們的示例和源碼分析中可以看出來。對於通過IntentService來執行任務,他是串行的。也就是說只有在上一個任務執行完以後纔會執行下一個任務。因爲Handler中將消息插入消息隊列,而隊列又是先進先出的數據結構。所以只有在上個任務執行完成以後才能夠獲取到下一個任務進行操作。在這裏也就說明了對於高併發的任務同過IntentService是不合適。

源碼下載

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