都2024年了,为什么我还要写一篇关于IntentService的文章?

前言

先来说一下我要写这篇文章的原因,前两天下午大概四点钟的时候,我听到坐在我工位斜对面的同事在面试。然后他就问面试者:"IntentService有了解过吗?能说一下它是怎么实现的吗?"。接着这个问题后他又问了一个问题:"任务执行完成后,需要手动关闭Service吗?"。当时其实我也不知道,因为IntentService在我的印象中面试官也有问过我?当时我也没有回答出来,我记得比较清楚的是,当时的面试官问了我这么一个问题:"你知道IntentService现在已经不维护了吗?已经被标注为废弃了。"说到这里我就产生了兴趣,那么今天我就把IntentService的使用和原理分享给大家。希望对你也能有所帮助。

1.HandleThread

在介绍IntentService之前我们有必要先来介绍一下HandlerThread这个类的实现。HandlerTread继承自Thread,并为我们提供了两个带参数的构造函数,具体的代码如下:

scala 复制代码
public class HandlerThread extends Thread {
    int mPriority;
    Looper mLooper;
    private Handler mHandler;

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

这里我们可以看到创建HandlerThread对象的方式有两种,一种是传递一个name参数给mPriority变量设置默认值,一种是传递一个name参数和一个线程优先级的参数priority,将传入的priority赋值给mPriority。了解了HandlerThread的创建方式以后我们再来看一下HandelerThread的一个核心的方法,run()函数的实现:

ini 复制代码
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

那么下面我们就来分析一下这段代码。首先第一行代码,我不需要太多的关注,就是获取当前线程的id,然后赋值给当前类的成员变量mTid。接着我们使用Looper.prepare()方法在当前线程中创建了一个Looper对象,我们知道Looper对象其实是保存在Thread.threadLocals变量中的,该变量的类型是ThreadLocalMap。而Looper对象中持有ThreadLocal的类引用,ThreadLocalMapThreadLocal中的一个静态内部类:

arduino 复制代码
static class ThreadLocalMap { }

在调用Looper.prepare()方法的时候,就是借助Looper类中的ThreadLocal变量来完成Looper对象的存储。

csharp 复制代码
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

这里prepare()方法,又调用了Looper类中带参的prepare(boolean quitAllowed)方法。在该方法内部一开始就调用ThreadLocal类中的get()方法来判断当前线程中是否已经存储了Looper对象。

java 复制代码
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);// 取出当前线程中的ThreadLocalMap对象
    if (map != null) { //不为空,取出Looper对象返回
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue(); // 为空,创建ThreadLocalMap对象保存到当前线程,存储Looper对象,再将Looper对象返回
}

如果Looper对象不为空就直接抛出异常,也就是说在一个线程中Looper.prepare()方法只能被调用一次,一个线程中只能创建一个唯一的Looper对象。接着我们打开sThreadLocal.set(new Looper(quitAllowed))方法:

scss 复制代码
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

到这里就比较好理解了,首先我们获取当前的线程,然后取出ThreadLocalMap对象,如果为空我们就创建一个ThreadLocalMap,并且将创建的ThreadLocalMap对象赋值给Thread.threadLocals

javascript 复制代码
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

这里我们以当前的ThreadLocal作为keyLooper对象作为valueLooper对象间接的存在了当前的线程中。关于ThreadLocalMap的数据结构由于不是这篇文章的重点,这里就不展开介绍了,感兴趣的读者可以自己去了解一下。下面我们接着来介绍run()函数中的代码:

ini 复制代码
synchronized (this) {
    mLooper = Looper.myLooper();
    notifyAll();
}

这里就是使用同步代码块将当前线程加锁,然后将刚才存在线程中的Looper对象保存到IntentService类中,接着唤醒所有wait()状态中的线程。

ini 复制代码
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;

接着下面的代码就是设置我们当前线程的优先级,这里提供了一个onLooperPrepared()的方法,但是并没有具体的实现:

csharp 复制代码
protected void onLooperPrepared() { }

注释写的也很清楚:如果需要在 Looper 循环之前执行某些设置,可以显式重写该方法。 最后调用Looper.loop()方法来进行消息的处理。到这里我们就可以很明显的知道HandleThread这个类的意图了,创建一个新的子线程,用来处理Handler发出来的消息。

2.IntentServise

了解了HandlerThread的用途,我们再来看IntentService就比较好理解了。

scala 复制代码
@Deprecated
public abstract class IntentService extends Service { }

这里首先我想将之前面试中遇到的一个问题先抛出来,就是IntentService类中的有关注释: "有关如何创建服务的详细讨论,请阅读服务开发人员指南。已弃用的 IntentServiceAndroid 8.0(API 级别 26)施加的所有后台执行限制的约束。请考虑使用 androidx.work.WorkManagerandroidx.core.app.JobIntentService,它们在 Android 8.0 或更高版本上运行时使用作业而不是服务。另请参阅:androidx.core.app.JobIntentService"。

意思就是说IntentService在安卓8.0以后的版本就不维护了,如果还想使用和IntentService类似的功能,建议我们使用JetPack中的WorkManager或者JobIntentService

下面我们就来分析一下IntentService这个类,首先IntentService它是一个抽象类,这就意味着我们在使用它之前必须要创建一个类来继承它。

scala 复制代码
class MyService extends IntentService {
    
    public MyService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        ...
    }
}

这里我们定义了一个类MyService继承自IntentService,并重写了IntentService中的抽象方法onHandleIntent。关于这个方法的详细作用,笔者打算放到文章的结尾再来详细介绍。这里我们先来看一下IntentService中的几个关键的方法:
1. onCreate()

ini 复制代码
public void onCreate() {
    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

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

ServiceonCreate()方法中,我们首先创建了HandlerThread对象,接着我们调用线程中的start()方法让线程运行起来,这样在HandlerThreadrun()函数中我们就会创建属于该线程的Looper对象,然后处理Handler发送的消息。在IntentService内部定义了mServiceLooper变量来保存我们在HandlerThread中创建的Looper对象,然后我们使用该Looper对象创建了ServiceHandler对象。

ServiceHandlerIntentService中的一个内部类,具体的代码实现如下:

scala 复制代码
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);
    }
}

ServiceHandler类中的代码也比较简单,拥有一个带有Looper类型参数的构造函数,和重写的方法handlerMessage()。这里我们先来简单分析一下消息的发送流程,我们知道在使用Handler发送消息的时候,会将当前发送消息的Handler对象赋值给Message对象中的target变量,然后将该消息放入消息队列MessageQueue中,在Looper.loop()方法中我们取出该消息,然后调用msg.target.dispatchMessage()来分发消息,对于正常的情况来说,如果消息的callback属性没有被赋值,或者创建Handler对象的时候没有传入Callback类型的参数,最终我们会调用到我们上面重写的方法handlerMessage(),关于这块的逻辑我们可以看HandlerdispatchMessage()方法的实现。

less 复制代码
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

2.onStartCommand()、onStart()

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

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

我们知道对于一个Service来说,onCreate()方法只会在该Service创建的时候调用,而onStartCommand()方法会被多次调用。在onStartCommand()方法被调用以后,接着这里又调用了onStart()方法,在onStart()方法内部我们使用Handler中的obtainMessage()方法创建了一个Message对象,并将intentstartId参数保存到了Messgae对象中。也就是说在我们的Service启动以后,这里我们就会使用Hanlder来发一条消息,然后在ServiceHandler中的handleMessage()中来处理该消息。

在我们自己定义的MyService中重写的onHandlerIntent()方法中取出intent中的参数,也就是我们想让IntentService做的事情。这里比如说我们需要下载一张大图片或者想要升级apk等比较耗时的任务,我们都可以放到该方法中去处理。

less 复制代码
protected void onHandleIntent(@Nullable Intent intent) {
   // 取出intent中的参数,做逻辑处理
}

onHandleIntent()中的任务处理完成以后,这里会调用stopSelf()来关闭服务。

scss 复制代码
public void handleMessage(Message msg) {
    onHandleIntent((Intent)msg.obj);
    stopSelf(msg.arg1);
}

在服务关闭的时候会调用它的生命周期方法onDestroy():

csharp 复制代码
public void onDestroy() {
    mServiceLooper.quit();
}

然后我们使用Looper对象调用quit()方法停止处理消息。

总结

分析完IntentService的使用流程我们再来宏观的看下这个类的意图,启动一个Service运行在后台,有目的处理我们需要的耗时任务。虽然官方已经不推荐我们使用该类了,但是这里作为曾经一个面试的知识点,我们还是有必要去了解清楚。这里就当是学习和记忆了。如果对你有帮助的话,记得留个大拇指或者小星星再走哦~

相关推荐
万少1 小时前
HarmonyOS官方模板集成创新活动-流蓝卡片
前端·harmonyos
-To be number.wan4 小时前
C++ 赋值运算符重载:深拷贝 vs 浅拷贝的生死线!
前端·c++
噢,我明白了4 小时前
JavaScript 中处理时间格式的核心方式
前端·javascript
纸上的彩虹5 小时前
半年一百个页面,重构系统也重构了我对前端工作的理解
前端·程序员·架构
李艺为5 小时前
根据apk包名动态修改Android品牌与型号
android·开发语言
be or not to be5 小时前
深入理解 CSS 浮动布局(float)
前端·css
LYFlied6 小时前
【每日算法】LeetCode 1143. 最长公共子序列
前端·算法·leetcode·职场和发展·动态规划
老华带你飞6 小时前
农产品销售管理|基于java + vue农产品销售管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
小徐_23336 小时前
2025 前端开源三年,npm 发包卡我半天
前端·npm·github
Tom4i7 小时前
【网络优化】Android 如何监听系统网络连接成功
android·网络