IntentService

3 阅读26分钟

IntentService

📌 面试重要度:⭐⭐⭐⭐

考察频率:字节 65% | 阿里 60% | 腾讯 55%

一、核心概念

1.1 定义与作用

一句话定义: IntentService 是 Android 提供的一个抽象 Service 类,内部基于 HandlerThread 实现,用于在后台串行处理异步任务,任务执行完成后自动停止服务。

为什么重要

  • 架构地位:Android 官方提供的标准后台任务处理方案,简化了 Service + Handler 的组合使用
  • 面试高频:结合了 Service 生命周期、HandlerThread、Handler 消息机制三大知识点,是考察对 Android 组件理解的重要指标
  • 实际价值:自动管理线程和生命周期,避免开发者手动处理复杂的并发逻辑,但已在 Android 11(API 30)被标记为废弃

与普通 Service 的核心区别

  • 普通 Service:默认运行在主线程,需要手动创建线程处理耗时任务,需要手动调用 stopSelf()
  • IntentService:自动在子线程处理任务,串行执行多个 Intent,任务完成自动停止

废弃原因

  • Android 8.0+ 对后台服务限制严格,IntentService 易被系统杀死
  • 功能单一,无法并发处理任务
  • WorkManager 提供了更强大的后台任务解决方案

1.2 与其他概念的关系

在 Handler 体系中的位置

  • HandlerThread(详见 ./01-HandlerThread.md):IntentService 内部使用 HandlerThread 实现异步任务队列
  • Service 组件:IntentService 继承自 Service,具备 Service 的所有特性
  • WorkManager:Google 推荐的 IntentService 替代方案,支持更复杂的后台任务调度

二、核心原理

2.1 工作机制

整体流程: 创建 IntentService 子类 → 实现 onHandleIntent() → 调用 startService() → onCreate() 创建 HandlerThread → onStartCommand() 发送 Intent 消息 → 子线程串行处理 Intent → 任务完成自动 stopSelf()

关键步骤详解

  1. 服务启动:调用 startService(intent) 启动 IntentService
  2. 线程初始化:首次启动时 onCreate() 创建 HandlerThread 并启动
  3. Handler 创建:获取 HandlerThread 的 Looper 创建 ServiceHandler
  4. 任务入队onStartCommand() 将 Intent 封装成 Message 发送到消息队列
  5. 串行处理:ServiceHandler 在子线程按顺序处理每个 Intent,调用 onHandleIntent()
  6. 自动停止:每个 Intent 处理完后调用 stopSelf(startId),最后一个任务完成时服务停止
  7. 线程退出onDestroy() 中调用 mServiceLooper.quit() 退出 HandlerThread

2.2 源码分析

核心类结构
// Android 11 源码:frameworks/base/core/java/android/app/IntentService.java
@Deprecated // API 30 标记废弃
public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;        // HandlerThread 的 Looper
    private volatile ServiceHandler mServiceHandler; // 处理 Intent 的 Handler
    private String mName;                          // 线程名称
    private boolean mRedelivery;                   // 是否重新投递 Intent

    // 构造函数:指定工作线程名称
    public IntentService(String name) {
        super();
        mName = name;
    }

    // 抽象方法:子类必须实现,在工作线程中处理 Intent
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

源码解读

  • mServiceLooper:HandlerThread 的 Looper,用于在子线程处理消息
  • mServiceHandler:自定义 Handler,接收 Intent 消息并调用 onHandleIntent()
  • mRedelivery:设置为 true 时,如果服务被杀死,系统会重新投递未处理完的 Intent
  • @WorkerThread:注解标记 onHandleIntent() 运行在工作线程,提醒开发者不要执行 UI 操作
服务创建流程(onCreate)
// Android 11 源码:frameworks/base/core/java/android/app/IntentService.java
@Override
public void onCreate() {
    super.onCreate();

    // 步骤1:创建 HandlerThread(核心)
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    // 步骤2:获取 Looper 并创建 Handler
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

源码解读

  • 设计意图:在服务创建时初始化工作线程,避免每次处理 Intent 都创建线程
  • 线程命名:使用 IntentService[名称] 格式,便于调试和问题定位
  • Looper 获取thread.getLooper() 会阻塞等待 HandlerThread 的 Looper 创建完成
  • 生命周期:HandlerThread 在整个服务生命周期内持续运行,直到 onDestroy() 被调用
任务分发流程(onStartCommand)
// Android 11 源码:frameworks/base/core/java/android/app/IntentService.java
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    // 步骤1:调用生命周期回调(子类可重写)
    onStart(intent, startId);

    // 步骤2:返回启动模式
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

public void onStart(@Nullable Intent intent, int startId) {
    // 步骤1:获取 Message(复用对象池)
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId; // 保存 startId,用于停止服务
    msg.obj = intent;   // 保存 Intent 数据

    // 步骤2:发送到消息队列(关键)
    mServiceHandler.sendMessage(msg);
}

源码解读

  • startId 作用:每次调用 startService() 系统会分配递增的 startId,用于标识不同的启动请求

  • 消息封装:Intent 被封装成 Message 的 obj 字段,startId 保存在 arg1 字段

  • 返回值意义

    • START_NOT_STICKY(默认):服务被杀死后不会重建
    • START_REDELIVER_INTENT:服务被杀死后重建,并重新投递最后一个 Intent
  • 异步处理sendMessage() 立即返回,实际处理在工作线程异步执行

任务处理流程(ServiceHandler)
// Android 11 源码:frameworks/base/core/java/android/app/IntentService.java
private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper); // 绑定工作线程的 Looper
    }

    @Override
    public void handleMessage(Message msg) {
        // 步骤1:调用抽象方法处理 Intent(子线程执行)
        onHandleIntent((Intent)msg.obj);

        // 步骤2:处理完成后停止服务(关键)
        stopSelf(msg.arg1); // 使用 startId 停止服务
    }
}

源码解读

  • 线程切换:Handler 绑定 HandlerThread 的 Looper,handleMessage() 在工作线程执行

  • 串行处理:MessageQueue 保证消息按顺序处理,多个 Intent 串行执行

  • 智能停止stopSelf(startId) 只有在所有任务完成时才真正停止服务

    • 如果 startId 不是最新的,服务不会停止(说明还有后续任务)
    • 只有最后一个任务(startId 最大)调用 stopSelf() 才会真正停止服务
服务销毁流程(onDestroy)
// Android 11 源码:frameworks/base/core/java/android/app/IntentService.java
@Override
public void onDestroy() {
    // 退出 Looper 循环,终止 HandlerThread
    mServiceLooper.quit();
}

源码解读

  • 资源释放:调用 quit() 终止 Looper 循环,HandlerThread 线程结束
  • 调用时机:所有任务完成后,最后一个 stopSelf() 触发 onDestroy()
  • 注意事项:如果在 onHandleIntent() 中启动了其他线程,需要手动清理,onDestroy() 只会退出 HandlerThread

2.3 重要细节与边界条件

细节1:setIntentRedelivery() 的作用

// 在构造函数中调用
public MyIntentService() {
    super("MyIntentService");
    setIntentRedelivery(true); // 开启 Intent 重新投递
}
  • 默认行为(false) :服务被杀死后不重建,未处理完的 Intent 丢失
  • 开启后(true) :服务被杀死后系统重建服务,重新投递最后一个 Intent
  • 应用场景:重要任务(如上传文件)需要开启,临时任务(如刷新缓存)可关闭

细节2:多次 startService() 的处理

// 发送多个 Intent
startService(new Intent(this, MyService.class).putExtra("task", 1));
startService(new Intent(this, MyService.class).putExtra("task", 2));
startService(new Intent(this, MyService.class).putExtra("task", 3));

// IntentService 会按顺序处理
onHandleIntent(task=1) → onHandleIntent(task=2) → onHandleIntent(task=3) → stopSelf()
  • onCreate() 只调用一次:第一次启动时创建 HandlerThread
  • onStartCommand() 调用多次:每次 startService() 都会调用,将 Intent 入队
  • 串行执行:任务按发送顺序依次处理,不会并发

细节3:onBind() 返回 null

@Override
public IBinder onBind(Intent intent) {
    return null; // 不支持绑定模式
}
  • 原因:IntentService 设计为 Started Service,不支持 Bound Service 模式
  • 影响:无法通过 bindService() 绑定服务,只能通过 startService() 启动

边界情况

  • Intent 为 null:系统重启服务时可能传入 null Intent,需要在 onHandleIntent() 中判空
  • 任务执行时间过长:超过 5 秒(Android 8.0+)可能触发 ANR,需要改用前台服务或 WorkManager
  • 服务被杀死:低内存时系统可能杀死服务,未处理完的任务会丢失(除非开启 setIntentRedelivery(true)

三、实际应用

3.1 典型场景

场景1:后台文件下载

  • 需求:顺序下载多个文件,下载完成后通知用户

  • 使用方式

    public class DownloadService extends IntentService {
        public DownloadService() {
            super("DownloadService");
            setIntentRedelivery(true); // 确保下载任务不丢失
        }
    
        @Override
        protected void onHandleIntent(Intent intent) {
            String url = intent.getStringExtra("url");
            String filename = intent.getStringExtra("filename");
    
            try {
                // 下载文件(耗时操作)
                downloadFile(url, filename);
    
                // 通知主线程更新 UI
                sendBroadcast(new Intent("DOWNLOAD_COMPLETE")
                    .putExtra("filename", filename));
            } catch (IOException e) {
                Log.e(TAG, "Download failed", e);
            }
        }
    }
    
    // 启动下载
    Intent intent = new Intent(this, DownloadService.class);
    intent.putExtra("url", "http://example.com/file1.zip");
    intent.putExtra("filename", "file1.zip");
    startService(intent);
    
  • 注意事项

    • 下载进度无法实时回调(可通过广播或 EventBus 通知)
    • 大文件下载建议使用 DownloadManager 或 WorkManager
    • Android 8.0+ 需要在 5 秒内调用 startForeground() 转为前台服务

场景2:批量数据同步

  • 需求:定期同步服务器数据到本地数据库

  • 使用方式

    public class SyncService extends IntentService {
        public SyncService() {
            super("SyncService");
        }
    
        @Override
        protected void onHandleIntent(Intent intent) {
            String dataType = intent.getStringExtra("type");
    
            switch (dataType) {
                case "users":
                    syncUsers();
                    break;
                case "messages":
                    syncMessages();
                    break;
                case "settings":
                    syncSettings();
                    break;
            }
        }
    
        private void syncUsers() {
            // 网络请求 + 数据库写入
            List<User> users = api.getUsers();
            database.insertUsers(users);
        }
    }
    
    // 触发同步
    startService(new Intent(this, SyncService.class).putExtra("type", "users"));
    startService(new Intent(this, SyncService.class).putExtra("type", "messages"));
    
  • 注意事项

    • 多个同步任务会串行执行,总耗时 = 各任务耗时之和
    • 需要考虑网络异常、数据库错误的处理
    • 定期同步建议使用 AlarmManager + IntentService(或改用 WorkManager)

场景3:日志上传

  • 需求:收集本地日志文件并上传到服务器

  • 使用方式

    public class LogUploadService extends IntentService {
        public LogUploadService() {
            super("LogUploadService");
        }
    
        @Override
        protected void onHandleIntent(Intent intent) {
            File logDir = new File(getFilesDir(), "logs");
            File[] logFiles = logDir.listFiles();
    
            if (logFiles != null) {
                for (File logFile : logFiles) {
                    try {
                        uploadLog(logFile);
                        logFile.delete(); // 上传成功后删除
                    } catch (IOException e) {
                        Log.e(TAG, "Upload failed: " + logFile.getName(), e);
                    }
                }
            }
        }
    }
    
    // 定时触发上传
    AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    Intent intent = new Intent(this, LogUploadService.class);
    PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);
    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
        System.currentTimeMillis(), 24 * 60 * 60 * 1000, pendingIntent);
    
  • 注意事项

    • 上传失败需要保留日志文件,避免数据丢失
    • 网络状态检查(避免移动网络上传大文件)
    • 考虑使用 JobScheduler 或 WorkManager 替代 AlarmManager

3.2 最佳实践

推荐做法

  1. 合理命名工作线程

    public MyIntentService() {
        super("MyApp-Background"); // 便于调试
    }
    
  2. 判空处理 Intent

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null) {
            return; // 系统重启服务时可能为 null
        }
        String action = intent.getAction();
        if (action == null) {
            return;
        }
        // 处理逻辑
    }
    
  3. 异常处理

    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            // 业务逻辑
            performTask(intent);
        } catch (Exception e) {
            Log.e(TAG, "Task failed", e);
            // 上报错误或记录日志
        }
    }
    
  4. 开启 Intent 重新投递(重要任务)

    public DownloadService() {
        super("DownloadService");
        setIntentRedelivery(true); // 服务被杀后重建并重试
    }
    
  5. Android 8.0+ 适配前台服务

    @Override
    protected void onHandleIntent(Intent intent) {
        // 转为前台服务(避免被系统杀死)
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("正在处理任务")
            .setSmallIcon(R.drawable.ic_notification)
            .build();
        startForeground(1, notification);
    
        // 处理任务
        performTask(intent);
    
        // 任务完成后停止前台服务
        stopForeground(true);
    }
    

常见错误

  1. 在 onHandleIntent() 中更新 UI

    // ❌ 错误:工作线程无法直接更新 UI
    @Override
    protected void onHandleIntent(Intent intent) {
        textView.setText("完成"); // 抛出异常
    }
    
    // ✅ 正确:通过 Handler 切换到主线程
    @Override
    protected void onHandleIntent(Intent intent) {
        String result = performTask();
    
        new Handler(Looper.getMainLooper()).post(() -> {
            textView.setText(result);
        });
    
        // 或使用广播
        sendBroadcast(new Intent("TASK_COMPLETE").putExtra("result", result));
    }
    
  2. 忘记调用 super 构造函数

    // ❌ 错误:必须调用父类构造函数传入线程名
    public MyService() {
        // 缺少 super("name")
    }
    
    // ✅ 正确
    public MyService() {
        super("MyService");
    }
    
  3. 手动调用 stopSelf()

    // ❌ 错误:破坏 IntentService 的自动停止机制
    @Override
    protected void onHandleIntent(Intent intent) {
        performTask();
        stopSelf(); // 可能导致后续任务无法执行
    }
    
    // ✅ 正确:不需要手动调用,IntentService 自动处理
    @Override
    protected void onHandleIntent(Intent intent) {
        performTask(); // 处理完自动停止
    }
    

3.3 性能优化建议

优化1:避免频繁启动服务

// ❌ 避免:每个任务都启动一次服务
for (String url : urls) {
    Intent intent = new Intent(this, DownloadService.class);
    intent.putExtra("url", url);
    startService(intent); // 频繁调用 startService()
}

// ✅ 推荐:批量传递任务
Intent intent = new Intent(this, DownloadService.class);
intent.putStringArrayListExtra("urls", new ArrayList<>(urls));
startService(intent); // 只启动一次

// 在 onHandleIntent 中批量处理
@Override
protected void onHandleIntent(Intent intent) {
    ArrayList<String> urls = intent.getStringArrayListExtra("urls");
    for (String url : urls) {
        downloadFile(url);
    }
}

优化2:控制任务执行时间

@Override
protected void onHandleIntent(Intent intent) {
    long startTime = System.currentTimeMillis();

    performTask(intent);

    long duration = System.currentTimeMillis() - startTime;
    if (duration > 5000) {
        Log.w(TAG, "Task took too long: " + duration + "ms");
        // 考虑拆分任务或使用 WorkManager
    }
}

优化3:网络状态检查

@Override
protected void onHandleIntent(Intent intent) {
    // 检查网络状态
    ConnectivityManager cm = (ConnectivityManager)
        getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = cm.getActiveNetworkInfo();

    if (networkInfo == null || !networkInfo.isConnected()) {
        Log.w(TAG, "No network available, skip task");
        return;
    }

    // 检查是否为 Wi-Fi(避免移动网络下载大文件)
    if (networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
        if (isLargeFileTask(intent)) {
            Log.w(TAG, "Not on Wi-Fi, skip large file task");
            return;
        }
    }

    performNetworkTask(intent);
}

四、面试真题解析

4.1 基础必答题(P5必须掌握)


【高频题1】IntentService 和普通 Service 有什么区别?

标准答案(30秒) : IntentService 继承自 Service,主要区别有三点:一是 IntentService 内部使用 HandlerThread 在子线程处理任务,普通 Service 默认运行在主线程;二是 IntentService 会串行处理多个 Intent,任务完成自动调用 stopSelf(),普通 Service 需要手动管理;三是 IntentService 不支持 bindService() 绑定模式。

深入展开(追问后) : 从源码层面看,IntentService 在 onCreate() 中创建了 HandlerThread,在 onStartCommand() 中将 Intent 封装成 Message 发送到消息队列,由 ServiceHandler 在工作线程调用 onHandleIntent() 处理。

处理完后调用 stopSelf(startId),这个方法很关键:

  • 每次 startService() 系统会分配递增的 startId
  • stopSelf(startId) 只有在传入的 startId 是最新的时才真正停止服务
  • 这样保证所有任务都处理完才停止,中间的任务调用 stopSelf() 不会影响后续任务

普通 Service 需要开发者手动创建线程、处理并发、调用 stopSelf(),IntentService 封装了这些逻辑。

面试官追问

  • 追问1:为什么 IntentService 任务是串行执行的?

    • 答:因为内部只有一个 HandlerThread,MessageQueue 保证消息按顺序处理。如果需要并发执行,可以在 onHandleIntent() 中手动创建线程池,或者直接使用普通 Service + 线程池。
  • 追问2:多次调用 startService() 会创建多个 IntentService 实例吗?

    • 答:不会。Service 在同一进程中只有一个实例(单例),多次 startService() 只会多次调用 onStartCommand(),将 Intent 加入消息队列,由同一个 HandlerThread 串行处理。

【高频题2】IntentService 是如何自动停止的?

标准答案(30秒) : IntentService 在 ServiceHandler 的 handleMessage() 方法中,每处理完一个 Intent 就会调用 stopSelf(msg.arg1),这里的 msg.arg1 保存的是 startId。系统内部会判断传入的 startId 是否是最新的,如果是最新的说明所有任务都完成了,就会真正停止服务;如果不是最新的说明还有后续任务,服务继续运行。

深入展开(追问后) : 从源码看 stopSelf(int startId) 的工作原理(ActivityManagerService):

// 系统内部维护最后一个 startId
private int mLastStartId = 0;

public void stopSelf(int startId) {
    if (startId == mLastStartId) {
        // 是最后一个任务,真正停止服务
        stopSelfResult(startId);
    } else {
        // 还有后续任务,忽略此次停止请求
        return;
    }
}

例如连续调用三次 startService(),系统分配 startId 分别为 1、2、3:

  1. 处理 Intent1,调用 stopSelf(1),发现 1 < 3(最新 startId),服务不停止
  2. 处理 Intent2,调用 stopSelf(2),发现 2 < 3,服务不停止
  3. 处理 Intent3,调用 stopSelf(3),发现 3 == 3,服务停止

这种设计避免了任务处理到一半服务就停止的问题。

面试官追问

  • 追问1:如果不传 startId,直接调用 stopSelf() 会怎样?

    • 答:stopSelf() 不带参数时会立即停止服务,不管后续是否有任务。所以 IntentService 内部必须使用 stopSelf(startId) 才能实现自动停止逻辑。
  • 追问2:如果任务执行时间很长,会阻塞后续任务吗?

    • 答:会。IntentService 是串行处理,前一个任务不完成,后续任务无法执行。如果某个任务耗时 10 秒,后面的任务就得等待 10 秒。这也是 IntentService 的局限性,不适合长时间任务或需要并发的场景。

【高频题3】IntentService 为什么被废弃?如何替代?

标准答案(30秒) : IntentService 在 Android 11(API 30)被标记为废弃,主要原因是 Android 8.0+ 对后台服务限制严格,应用退到后台 5 秒后所有后台服务会被杀死,IntentService 很难完成长时间任务。Google 推荐使用 WorkManager 替代,它支持约束条件(如仅 Wi-Fi、充电时执行)、自动重试、任务链式调用,且兼容所有 Android 版本。

深入展开(追问后)废弃原因详解

  1. 后台限制:Android 8.0(API 26)引入后台执行限制,应用不在前台时无法启动后台服务,即使启动了也会在 5 秒内被杀死
  2. 功能单一:只能串行处理任务,无法并发,也无法设置优先级、延迟执行等
  3. 生命周期管理复杂:开发者需要处理 Android 8.0+ 的前台服务通知,增加复杂度
  4. 更好的替代方案:WorkManager 提供了更强大的功能,且由 Jetpack 库持续维护

替代方案对比

方案适用场景优点缺点
WorkManager延迟任务、定期任务保证执行、自动重试、约束条件不适合立即执行的任务
前台服务用户感知的任务(下载、播放)不受后台限制必须显示通知
JobSchedulerAndroid 5.0+ 系统级任务调度系统级优化、批量执行API 复杂,版本兼容性差
Kotlin Coroutines应用内异步任务简洁、高效应用进程结束任务终止

WorkManager 示例

// 创建一次性任务
OneTimeWorkRequest uploadWork = new OneTimeWorkRequest.Builder(UploadWorker.class)
    .setConstraints(new Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED) // 需要网络
        .setRequiresCharging(true) // 需要充电
        .build())
    .build();

WorkManager.getInstance(context).enqueue(uploadWork);

// Worker 实现
public class UploadWorker extends Worker {
    @Override
    public Result doWork() {
        uploadFile();
        return Result.success();
    }
}

面试官追问

  • 追问1:如果就是想用 IntentService 怎么办?

    • 答:可以继续使用,但需要适配 Android 8.0+:

      1. onHandleIntent() 开始时调用 startForeground() 转为前台服务
      2. 显示通知(必需)
      3. 任务完成后调用 stopForeground(true) 移除通知 但这样就失去了 IntentService 的简洁性,不如直接用 WorkManager。
  • 追问2:WorkManager 和 IntentService 的主要区别是什么?

    • 答:

      • 执行时机:WorkManager 不保证立即执行,系统会根据约束条件和电量优化批量执行;IntentService 立即执行
      • 任务持久化:WorkManager 任务持久化到数据库,应用重启后继续执行;IntentService 任务保存在内存,重启后丢失
      • 约束条件:WorkManager 支持网络、充电、存储空间等约束;IntentService 不支持
      • 重试机制:WorkManager 自动重试失败任务;IntentService 需要手动处理

【高频题4】IntentService 的 onHandleIntent() 运行在哪个线程?如何验证?

标准答案(30秒)onHandleIntent() 运行在 HandlerThread 创建的工作线程中,而不是主线程。验证方法有两种:一是打印 Thread.currentThread().getName(),会输出 IntentService[服务名];二是打印 Looper.myLooper() == Looper.getMainLooper(),返回 false 说明不在主线程。

深入展开(追问后) : 从源码追踪线程切换过程:

  1. onCreate() 创建 HandlerThread

    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start(); // 启动工作线程
    
  2. 获取工作线程的 Looper

    mServiceLooper = thread.getLooper(); // 工作线程的 Looper
    
  3. 创建绑定工作线程的 Handler

    mServiceHandler = new ServiceHandler(mServiceLooper); // Handler 绑定工作线程
    
  4. 消息在工作线程处理

    // ServiceHandler 的 handleMessage() 在工作线程执行
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj); // 工作线程调用
    }
    

验证代码

@Override
protected void onHandleIntent(Intent intent) {
    // 方法1:打印线程名
    Log.d(TAG, "Thread name: " + Thread.currentThread().getName());
    // 输出:Thread name: IntentService[MyService]

    // 方法2:检查是否主线程
    boolean isMainThread = Looper.myLooper() == Looper.getMainLooper();
    Log.d(TAG, "Is main thread: " + isMainThread);
    // 输出:Is main thread: false

    // 方法3:打印线程 ID
    Log.d(TAG, "Thread ID: " + Thread.currentThread().getId());
    // 输出:Thread ID: 12345(不是主线程 ID)
}

面试官追问

  • 追问1:能在 onHandleIntent() 中更新 UI 吗?

    • 答:不能直接更新。因为运行在工作线程,访问 UI 会抛出 CalledFromWrongThreadException。需要通过以下方式切换到主线程:

      1. Handler(Looper.getMainLooper()).post()
      2. runOnUiThread()(Activity 方法)
      3. 发送广播,Activity 注册 BroadcastReceiver 接收
      4. 使用 LiveData、EventBus 等观察者模式
  • 追问2:onStartCommand() 运行在哪个线程?

    • 答:运行在主线程。onStartCommand() 是 Service 的生命周期回调,由系统在主线程调用。只有 onHandleIntent() 运行在工作线程,其他所有生命周期方法(onCreate、onStartCommand、onDestroy)都在主线程。

【高频题5】IntentService 的 setIntentRedelivery() 有什么用?

标准答案(30秒)setIntentRedelivery(true) 设置 Intent 重新投递机制。当服务在处理 Intent 时被系统杀死,如果设置为 true,系统会重建服务并重新投递最后一个 Intent;如果为 false(默认),Intent 会丢失。它通过改变 onStartCommand() 的返回值实现:true 返回 START_REDELIVER_INTENT,false 返回 START_NOT_STICKY

深入展开(追问后) : 从源码看实现原理:

public void setIntentRedelivery(boolean enabled) {
    mRedelivery = enabled;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    // 根据 mRedelivery 返回不同的启动模式
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

三种启动模式对比

返回值行为IntentService 使用场景
START_NOT_STICKY服务被杀后不重建默认模式,临时任务
START_STICKY服务被杀后重建,Intent 为 nullIntentService 不使用
START_REDELIVER_INTENT服务被杀后重建,重新投递最后一个 Intent重要任务,开启 setIntentRedelivery(true)

使用示例

public class DownloadService extends IntentService {
    public DownloadService() {
        super("DownloadService");
        setIntentRedelivery(true); // 下载任务需要保证执行
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null) {
            // 系统重建服务时可能为 null
            return;
        }

        String url = intent.getStringExtra("url");
        downloadFile(url); // 即使服务被杀,重建后会继续下载
    }
}

面试官追问

  • 追问1:如果发送了 3 个 Intent,服务在处理第 2 个时被杀,会重新投递哪个?

    • 答:只重新投递最后一个 Intent,即第 3 个。START_REDELIVER_INTENT 只保证最后一个 Intent 不丢失,前面已处理的和正在处理的都会丢失。如果需要保证所有任务都执行,应该:

      1. 使用 WorkManager(任务持久化)
      2. 或在 onHandleIntent() 开始时调用 startForeground() 转为前台服务
  • 追问2:什么场景应该开启 setIntentRedelivery(true)?

    • 答:

      • 应该开启:文件下载、数据上传、支付请求等不能丢失的任务
      • 可以关闭:刷新缓存、日志记录等允许丢失的任务
      • 开启后会占用系统资源,需要权衡任务重要性和系统负担

4.2 进阶加分题(P6/P6+)


【进阶题1】IntentService 如何在 Android 8.0+ 上正常工作?

参考答案: Android 8.0(API 26)引入后台执行限制,应用退到后台后无法启动后台服务,即使启动了也会在 5 秒内被杀死并抛出 IllegalStateException。要让 IntentService 正常工作,需要转为前台服务:

适配方案

public class MyIntentService extends IntentService {
    private static final String CHANNEL_ID = "service_channel";

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

    @Override
    public void onCreate() {
        super.onCreate();

        // Android 8.0+ 需要创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                "后台任务",
                NotificationManager.IMPORTANCE_LOW
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 任务开始时转为前台服务
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("正在处理任务")
                .setContentText("请稍候...")
                .setSmallIcon(R.drawable.ic_notification)
                .build();

            startForeground(1, notification);
        }

        try {
            // 执行任务
            performTask(intent);
        } finally {
            // 任务完成后停止前台服务
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                stopForeground(true);
            }
        }
    }
}

启动服务适配

Intent intent = new Intent(this, MyIntentService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent); // Android 8.0+ 使用
} else {
    startService(intent);
}

追问

  • 为什么 Android 8.0 要限制后台服务?

    • 答:主要目的是优化电池续航和系统性能。研究显示,大量应用在后台启动服务执行任务,即使用户已关闭应用,导致电量消耗快、系统卡顿。前台服务必须显示通知,让用户知道哪些应用在后台运行,可以手动停止。

【进阶题2】如果多个应用组件同时启动同一个 IntentService,会发生什么?

参考答案: 不会创建多个 IntentService 实例,所有 Intent 都会发送到同一个服务实例的消息队列,按顺序串行处理。

原因分析

  1. Service 单例特性:同一进程中,相同类名的 Service 只有一个实例(由 ActivityManagerService 保证)
  2. 首次启动创建:第一次 startService() 时系统创建 Service 实例并调用 onCreate()
  3. 后续启动复用:后续 startService() 复用已存在的实例,只调用 onStartCommand()
  4. 消息队列排队:所有 Intent 都发送到同一个 HandlerThread 的消息队列,串行处理

示例场景

// Activity A 启动服务
Intent intent1 = new Intent(this, MyService.class);
intent1.putExtra("from", "Activity A");
startService(intent1);

// Activity B 同时启动服务
Intent intent2 = new Intent(this, MyService.class);
intent2.putExtra("from", "Activity B");
startService(intent2);

// BroadcastReceiver 也启动服务
Intent intent3 = new Intent(context, MyService.class);
intent3.putExtra("from", "Receiver");
context.startService(intent3);

// 执行顺序
onCreate() → 创建 HandlerThread
onStartCommand(intent1) → 发送 Message1
onStartCommand(intent2) → 发送 Message2
onStartCommand(intent3) → 发送 Message3
onHandleIntent(intent1) → 处理完成
onHandleIntent(intent2) → 处理完成
onHandleIntent(intent3) → 处理完成
stopSelf() → 所有任务完成后停止
onDestroy() → 销毁服务

追问

  • 如果需要并发处理多个 Intent 怎么办?

    • 答:IntentService 不支持并发,可以:

      1. onHandleIntent() 中手动创建线程池并发执行子任务
      2. 使用普通 Service + ThreadPoolExecutor
      3. 使用 WorkManager 并行 Worker

【进阶题3】IntentService 的内存占用如何优化?

参考答案: IntentService 的内存占用主要来自三个方面:HandlerThread 线程栈、MessageQueue 中的消息、onHandleIntent() 中的业务对象。

优化策略

  1. 及时释放大对象

    @Override
    protected void onHandleIntent(Intent intent) {
        Bitmap bitmap = null;
        try {
            bitmap = loadLargeBitmap();
            processBitmap(bitmap);
        } finally {
            // 及时回收
            if (bitmap != null && !bitmap.isRecycled()) {
                bitmap.recycle();
            }
        }
    }
    
  2. 避免在 Intent 中传递大数据

    // ❌ 避免:传递大对象
    intent.putExtra("bitmap", largeBitmap); // Parcelable 序列化占用大量内存
    
    // ✅ 推荐:传递文件路径或 ID
    intent.putExtra("imagePath", "/sdcard/image.jpg");
    // 在 onHandleIntent 中读取文件
    
  3. 控制消息队列长度

    // 如果任务积压过多,考虑丢弃或合并
    private final AtomicInteger pendingTasks = new AtomicInteger(0);
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int pending = pendingTasks.incrementAndGet();
        if (pending > 100) {
            Log.w(TAG, "Too many pending tasks, skip this one");
            pendingTasks.decrementAndGet();
            return START_NOT_STICKY;
        }
        return super.onStartCommand(intent, flags, startId);
    }
    
    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            performTask(intent);
        } finally {
            pendingTasks.decrementAndGet();
        }
    }
    
  4. 任务完成后及时停止服务

    // IntentService 自动停止,但如果长时间没任务,可手动停止
    @Override
    protected void onHandleIntent(Intent intent) {
        performTask(intent);
        // 不需要额外操作,IntentService 自动处理
    }
    

追问

  • HandlerThread 的线程栈占用多少内存?

    • 答:默认线程栈大小取决于系统,通常为 1MB。可以通过检查 /proc/[pid]/maps 查看实际占用。HandlerThread 本身是轻量级的,主要内存占用来自业务对象。

4.3 实战场景题


【场景题】设计一个支持断点续传的文件下载服务

问题描述: 使用 IntentService 实现文件下载服务,要求:

  1. 支持多个文件顺序下载
  2. 支持断点续传(应用重启后继续下载)
  3. 下载进度实时通知 UI
  4. 处理网络异常和重试

答案思路

1. 分析

  • 串行下载:IntentService 天然支持,多个下载任务排队执行
  • 断点续传:需要记录已下载大小,使用 HTTP Range 请求
  • 进度通知:通过广播或 LiveData 通知 UI
  • 异常处理:网络异常时重试,超过次数记录失败状态

2. 实现方案

public class DownloadService extends IntentService {
    public static final String ACTION_DOWNLOAD = "com.example.DOWNLOAD";
    public static final String EXTRA_URL = "url";
    public static final String EXTRA_FILE_PATH = "file_path";

    private static final int MAX_RETRY = 3;

    public DownloadService() {
        super("DownloadService");
        setIntentRedelivery(true); // 保证下载任务不丢失
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null || !ACTION_DOWNLOAD.equals(intent.getAction())) {
            return;
        }

        String url = intent.getStringExtra(EXTRA_URL);
        String filePath = intent.getStringExtra(EXTRA_FILE_PATH);

        // 执行下载(支持断点续传)
        downloadWithResume(url, filePath);
    }

    private void downloadWithResume(String url, String filePath) {
        File file = new File(filePath);
        long downloadedSize = file.exists() ? file.length() : 0;

        int retryCount = 0;
        while (retryCount < MAX_RETRY) {
            try {
                // 1. 创建 HTTP 连接
                HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();

                // 2. 设置断点续传(关键)
                if (downloadedSize > 0) {
                    connection.setRequestProperty("Range", "bytes=" + downloadedSize + "-");
                }

                // 3. 获取文件总大小
                long totalSize = connection.getContentLength() + downloadedSize;

                // 4. 下载文件
                InputStream input = connection.getInputStream();
                RandomAccessFile output = new RandomAccessFile(file, "rw");
                output.seek(downloadedSize); // 从断点位置开始写入

                byte[] buffer = new byte[4096];
                int bytesRead;
                long currentSize = downloadedSize;

                while ((bytesRead = input.read(buffer)) != -1) {
                    output.write(buffer, 0, bytesRead);
                    currentSize += bytesRead;

                    // 5. 发送进度广播(每 100KB 发送一次)
                    if (currentSize % (100 * 1024) == 0 || currentSize == totalSize) {
                        int progress = (int) (currentSize * 100 / totalSize);
                        sendProgressBroadcast(url, progress, currentSize, totalSize);
                    }
                }

                output.close();
                input.close();

                // 6. 下载成功
                sendCompleteBroadcast(url, filePath);
                return;

            } catch (IOException e) {
                retryCount++;
                Log.e(TAG, "Download failed, retry " + retryCount, e);

                if (retryCount >= MAX_RETRY) {
                    // 超过重试次数,发送失败广播
                    sendFailBroadcast(url, e.getMessage());
                } else {
                    // 等待后重试
                    try {
                        Thread.sleep(2000 * retryCount); // 指数退避
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }
    }

    // 发送进度广播
    private void sendProgressBroadcast(String url, int progress, long current, long total) {
        Intent intent = new Intent("DOWNLOAD_PROGRESS");
        intent.putExtra("url", url);
        intent.putExtra("progress", progress);
        intent.putExtra("current", current);
        intent.putExtra("total", total);
        sendBroadcast(intent);
    }

    // 发送完成广播
    private void sendCompleteBroadcast(String url, String filePath) {
        Intent intent = new Intent("DOWNLOAD_COMPLETE");
        intent.putExtra("url", url);
        intent.putExtra("file_path", filePath);
        sendBroadcast(intent);
    }

    // 发送失败广播
    private void sendFailBroadcast(String url, String error) {
        Intent intent = new Intent("DOWNLOAD_FAIL");
        intent.putExtra("url", url);
        intent.putExtra("error", error);
        sendBroadcast(intent);
    }
}

// 启动下载
Intent intent = new Intent(this, DownloadService.class);
intent.setAction(DownloadService.ACTION_DOWNLOAD);
intent.putExtra(DownloadService.EXTRA_URL, "http://example.com/file.zip");
intent.putExtra(DownloadService.EXTRA_FILE_PATH, "/sdcard/Download/file.zip");
startService(intent);

// Activity 中接收进度
private BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if ("DOWNLOAD_PROGRESS".equals(action)) {
            int progress = intent.getIntExtra("progress", 0);
            progressBar.setProgress(progress);
        } else if ("DOWNLOAD_COMPLETE".equals(action)) {
            Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show();
        } else if ("DOWNLOAD_FAIL".equals(action)) {
            String error = intent.getStringExtra("error");
            Toast.makeText(context, "下载失败: " + error, Toast.LENGTH_SHORT).show();
        }
    }
};

3. 关键点

  • 使用 RandomAccessFile 支持从任意位置写入文件
  • HTTP Range 请求头实现断点续传
  • 通过广播实时通知下载进度
  • 指数退避重试策略避免频繁请求
  • setIntentRedelivery(true) 保证应用重启后继续下载

追问

  • 方案缺点?

    • 答:

      1. 广播发送频繁可能影响性能(可改用 LocalBroadcastManager 或 EventBus)
      2. IntentService 串行下载,无法同时下载多个文件(可改用线程池)
      3. Android 8.0+ 需要前台服务,必须显示通知
      4. 下载大文件时内存占用高(可使用流式下载,不要一次性读入内存)
  • 其他方案?

    • 答:

      1. DownloadManager:系统提供的下载服务,自动支持断点续传、通知栏显示
      2. WorkManager + CoroutineWorker:现代化方案,支持约束条件、自动重试
      3. OkHttp + RxJava:应用内下载,灵活控制,但需要处理生命周期
  • 如何优化?

    • 答:

      1. 使用 LocalBroadcastManager 或 LiveData 替代全局广播
      2. 降低进度通知频率(如每 500KB 通知一次)
      3. 使用 BufferedInputStream 和 BufferedOutputStream 提升 I/O 性能
      4. 下载状态持久化到数据库,支持查询下载历史

五、对比与总结

5.1 关键API对比

API/方法作用使用场景注意事项
IntentService(String name)构造函数,指定工作线程名称创建 IntentService 子类必须调用 super(name)
onHandleIntent(Intent)抽象方法,处理 Intent 任务子类实现业务逻辑运行在工作线程,不能更新 UI
setIntentRedelivery(boolean)设置 Intent 重新投递重要任务开启,临时任务关闭影响 onStartCommand() 返回值
onStartCommand()系统调用,将 Intent 入队自动调用,通常不需重写返回 START_REDELIVER_INTENTSTART_NOT_STICKY
onCreate()创建 HandlerThread自动调用,通常不需重写只调用一次
onDestroy()退出 HandlerThread自动调用,通常不需重写所有任务完成后调用
startService(Intent)启动服务并发送 Intent外部启动服务Android 8.0+ 需要前台服务
stopSelf(int startId)停止服务(内部调用)自动调用,不需手动调用使用 startId 保证所有任务完成

5.2 核心要点速记

一句话记忆: IntentService = Service + HandlerThread + 自动停止,用于在后台串行处理异步任务,已被 WorkManager 替代。

3个关键点

  1. 基于 HandlerThread:内部创建 HandlerThread 在子线程处理任务,避免阻塞主线程
  2. 串行处理 Intent:多个 Intent 按顺序执行,MessageQueue 保证串行
  3. 自动停止服务:通过 stopSelf(startId) 实现所有任务完成后自动停止

面试官最爱问

  1. IntentService 和普通 Service 的区别? (必问)
  2. IntentService 如何自动停止? (高频)
  3. 为什么 IntentService 被废弃?如何替代? (高频)
  4. onHandleIntent() 运行在哪个线程? (常问)
  5. setIntentRedelivery() 的作用? (进阶)

六、关联知识点

前置知识

  • Service 基础:理解 Service 生命周期和启动模式
  • HandlerThread(详见:./01-HandlerThread.md):理解 HandlerThread 的工作原理
  • Handler 消息机制(详见:../01-Handler基础/):理解 Handler、Looper、MessageQueue 的关系

后续扩展

  • WorkManager:学习 IntentService 的替代方案,现代化后台任务调度
  • 前台服务:了解如何在 Android 8.0+ 上适配后台服务限制
  • JobScheduler:系统级任务调度器,适合周期性任务
  • DownloadManager:系统提供的下载服务,自动支持断点续传

相关文件

  • ./01-HandlerThread.md - IntentService 内部使用 HandlerThread 实现异步任务队列
  • ./03-主线程Looper创建时机.md - 理解主线程与工作线程的区别
  • ../05-同步屏障/ - 深入理解消息优先级机制