IntentService-你可能需要知道这些

Service作为Android四大组件之一,在我们的日常开发中占着不可或缺的一席,而轻量级的IntentService作为对Service的一个补充,也越来越收到大家的推崇,本博文就带大家来学习和分析下IntentService的原理。
IntentService的实现离不开HandlerThread这个大功臣,因此我们还会附带介绍HandlerThread的一些内容。

为什么要使用IntentService

在回答这个问题之前,我们先看下我们常用的Service的特点:

  1. Service是一个不可见的Activity,因此它的几个方法(onCreate\onStartCommand\onBind)是运行在主线程中的,因此不要在Service中做一些重量级的操作,否则可能会导致ANR。(广播BC的onReceive方法也是运行在主线程中,这一点经常在面试中会被问到。)

  2. Service与Thread是存在区别的,Service不是一个单独的线程,也不是一个单独的进程。

  3. 如果不是调用了stopSelf或者stopService,我们启动的Service,可能会一直存在。(会占用系统内存和资源)

分析完Service的特点,我们知道在一些场景下,Service对于我们太重了,我们需要一个更轻量级的服务。

IntentService作为对Service的补充(或者说Service的轻量级实现),很好的弥补了Service的缺陷,我们来看下IntentService的特点:

  1. 启动一个单独的线程(工作线程)来处理任务和请求,所有的任务都在该改线程中处理。

  2. 因为是在单独的线程中处理任务和请求,其onHandleIntent方法运行在单独的线程中,而非主线程,因此可以执行异步操作。(面试中经常被问到)

  3. 按照发送顺序处理任务和请求。当没有任务和请求时,IntentService会自动销毁。因此它不会一直占用资源和内存。

  4. onBind方法的默认实现返回值为null,因此不要尝试调用bindService去调用IntentService。(设计的目的是为了处理简单的异步任务)

这就是我们为什么要使用IntentService的原因。

示例

使用IntentService非常简单,下面给出IntentService的一个简单实现:

public class WorkerService extends IntentService {

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public WorkerService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //TODO:处理任务和请求
        //不要再单独启动线程去处理任务和请求
    }
}

你还要需要做如下操作,才能正常使用IntentService。

  • 在AndroidManifest.xml文件中注册。
  • 像使用Service一样使用IntentService。

HandlerThread

在分析IntentService的源码前,我们先来看下IntentService使用到的一个类:HandlerThread。上源码:

/**
 * Handy class for starting a new thread that has a looper. The looper can then be 
 * used to create handler classes. Note that start() must still be called.
 */
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    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;
    }
    
    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason is isAlive() returns false, this method will return null. If this thread 
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
    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;
    }

    /**
     * Quits the handler thread's looper.
     * <p>
     * Causes the handler thread's looper to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     *
     * @see #quitSafely
     */
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    /**
     * Quits the handler thread's looper safely.
     * <p>
     * Causes the handler thread's looper to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * Pending delayed messages with due times in the future will not be delivered.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p>
     * If the thread has not been started or has finished (that is if
     * {@link #getLooper} returns null), then false is returned.
     * Otherwise the looper is asked to quit and true is returned.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     */
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}

从源码的角度,我们得出以下结论:

  1. HandlerThread是一个Thread。(这不废话吗,HandlerThread继承了Tread)

  2. 用于启动一个带有Looper属性线程的方便类。(谷歌的良苦用心,有了HandlerThread我们使用[Hanlder+Thread]就更简单方便了)

那么它与普通的Thread有什么区别呢?

  1. HandlerThread自带Looper,该Looper可直接用于创建Handler。

我们分别来看下使用Thread和使用HandlerThread创建Handller的方式。

1. Thread + Handler

private MyHandler mHandler;

    public void buildHandler() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 为当前线程设置Looper
                Looper.prepare();
                // 使用当前线程的Looper构造Handler
                mHandler = new MyHandler(Looper.myLooper());
                // 开始处理MessageQueue
                Looper.loop();
            }
        }).start();

    }

    class MyHandler extends Handler {

        MyHandler(Looper looper){
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }


2. HandlerThread + Handler

private MyHandler mHandler;

    public void buildHandler() {
        // 构造HandlerThread
        HandlerThread handlerThread = new HandlerThread("WorkThread");
        handlerThread.start();
        // 直接使用HandlerThread的looper创建Handler
        mHandler = new MyHandler(handlerThread.getLooper());
    }

    class MyHandler extends Handler {

        MyHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

3. 对比

// 为当前线程设置Looper
Looper.prepare();
// 使用当前线程的Looper构造Handler
mHandler = new MyHandler(Looper.myLooper());
// 开始处理MessageQueue
Looper.loop();

**********************************************************

// 构造HandlerThread
HandlerThread handlerThread = new HandlerThread("WorkThread");
handlerThread.start();
// 直接使用HandlerThread的looper创建Handler
mHandler = new MyHandler(handlerThread.getLooper());

对比很明显,我们在使用HandlerThread创建Handler更简单方便,那么消失的这段代码去哪了?

// 为当前线程设置Looper
Looper.prepare();

// 开始处理MessageQueue
Looper.loop();

答案就是HandlerThread的run()方法自动帮我们完成了,我们来看下HandlerThread的run()方法。

    @Override
    public void run() {
        mTid = Process.myTid();
        // 为当前线程设置Looper
        Looper.prepare();
        synchronized (this) {
            // getLooper()返回的就是mLooper
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        // 开始处理消息队列
        Looper.loop();
        mTid = -1;
    }

相信大家看到这,应该对HandlerThread有了一定了解,并能清晰明了的认识到它与普通Thread的区别。

一探IntentService究竟

本章节带大家来分析下IntentService的原理和实现,知其然还要知其所以然。

IntentService.java

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

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * Sets intent redelivery preferences.  Usually called from the constructor
     * with your preferred semantics.
     *
     * <p>If enabled is true,
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
     * {@link #onHandleIntent(Intent)} returns, the process will be restarted
     * and the intent redelivered.  If multiple Intents have been sent, only
     * the most recent one is guaranteed to be redelivered.
     *
     * <p>If enabled is false (the default),
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
     * dies along with it.
     */
    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(@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;
    }

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

    /**
     * Unless you provide binding for your service, you don't need to implement this
     * method, because the default implementation returns null.
     * @see android.app.Service#onBind
     */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * This method is invoked on the worker thread with a request to process.
     * Only one Intent is processed at a time, but the processing happens on a
     * worker thread that runs independently from other application logic.
     * So, if this code takes a long time, it will hold up other requests to
     * the same IntentService, but it will not hold up anything else.
     * When all requests have been handled, the IntentService stops itself,
     * so you should not call {@link #stopSelf}.
     *
     * @param intent The value passed to {@link
     *               android.content.Context#startService(Intent)}.
     *               This may be null if the service is being restarted after
     *               its process has gone away; see
     *               {@link android.app.Service#onStartCommand}
     *               for details.
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

代码非常简洁,而且逻辑也比较清楚,下面我们来详细分析。

  1. 一些属性
    // 工作线程的Looper
    private volatile Looper mServiceLooper;
    // 结合HandlerThread来处理任务和请求
    private volatile ServiceHandler mServiceHandler;
    // Service名称
    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);
        }
    }
  1. 构造方法
 //实现IntentService时构造方法要调用IntentService的构造方法
 public IntentService(String name) {
        super();
        mName = name;
    }
  1. 销毁重启
    如果IntentService存在待处理的任务、或者正在处理任务时候,IntentService所处的进程被杀死,那么将根据mRedelivery的值来决定是否重启后分发Intent。

mRedelivery为true的情况:进程杀死会被重启,且分发未处理完毕的Intent。存在多个Intent的情况下,只会分发最近一次发送的Intent.

mRedelivery为false的情况:进程杀死后不会被重启,Intent随着进程的消亡而消亡。

一般我们在构造方法中,调用如下的方法来设置。

public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }
  1. onCreate
   @Override
    public void onCreate() {
        super.onCreate();
        // 创建、启动工作线程
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        // 关联处理任务的Looper和Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

经过这一步,处理任务的工作线程和Handler就已经准备完毕,IntentService处于就绪状态,此时就可以处理发送过来的任务了。

  1. onStartCommand
    //mRedelivery的值决定了方法的返回值。
    //不要重写该方法,去重写onHnadleIntent方法
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
  1. onBind
    // 一般情况下,我们不会重写该方法
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
  1. onHandleIntent
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
  1. 该方法是我们需要重写的方法,它运行在工作线程中。(我们启动的HandlerThread中)

  2. 我们处理任务的地方。(耗时操作等)

  3. 该方法在ServiceHandler的handleMessage中被调用。

  4. onDestory

    @Override
    public void onDestroy() {
        // 退出消息循环
        mServiceLooper.quit();
    }

HandlerThread

原理

经过上个章节的分析,可以清晰的看到IntentService的实现原理就是:
HandlerThread+Handler,是不是感觉so easy.

结束语

IntentService是通过HandlerThread+Handler的方式实现的一个异步操作,可以执行耗时操作同时不会产生ANR的情况,而且具备生命周期管理,是我们执行简单异步任务的一个不错的选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容