都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运行在后台,有目的处理我们需要的耗时任务。虽然官方已经不推荐我们使用该类了,但是这里作为曾经一个面试的知识点,我们还是有必要去了解清楚。这里就当是学习和记忆了。如果对你有帮助的话,记得留个大拇指或者小星星再走哦~

相关推荐
醉の虾7 分钟前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧16 分钟前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm25 分钟前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
天空中的野鸟25 分钟前
Android音频采集
android·音视频
asleep70138 分钟前
第8章利用CSS制作导航菜单
前端·css
hummhumm42 分钟前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王1 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
疯狂的沙粒1 小时前
对 TypeScript 中高级类型的理解?应该在哪些方面可以更好的使用!
前端·javascript·typescript
小白也想学C2 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程2 小时前
初级数据结构——树
android·java·数据结构