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()
关键步骤详解:
- 服务启动:调用
startService(intent)启动 IntentService - 线程初始化:首次启动时
onCreate()创建 HandlerThread 并启动 - Handler 创建:获取 HandlerThread 的 Looper 创建 ServiceHandler
- 任务入队:
onStartCommand()将 Intent 封装成 Message 发送到消息队列 - 串行处理:ServiceHandler 在子线程按顺序处理每个 Intent,调用
onHandleIntent() - 自动停止:每个 Intent 处理完后调用
stopSelf(startId),最后一个任务完成时服务停止 - 线程退出:
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 最佳实践
✅ 推荐做法:
-
合理命名工作线程
public MyIntentService() { super("MyApp-Background"); // 便于调试 } -
判空处理 Intent
@Override protected void onHandleIntent(Intent intent) { if (intent == null) { return; // 系统重启服务时可能为 null } String action = intent.getAction(); if (action == null) { return; } // 处理逻辑 } -
异常处理
@Override protected void onHandleIntent(Intent intent) { try { // 业务逻辑 performTask(intent); } catch (Exception e) { Log.e(TAG, "Task failed", e); // 上报错误或记录日志 } } -
开启 Intent 重新投递(重要任务)
public DownloadService() { super("DownloadService"); setIntentRedelivery(true); // 服务被杀后重建并重试 } -
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); }
❌ 常见错误:
-
在 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)); } -
忘记调用 super 构造函数
// ❌ 错误:必须调用父类构造函数传入线程名 public MyService() { // 缺少 super("name") } // ✅ 正确 public MyService() { super("MyService"); } -
手动调用 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 + 线程池。
- 答:因为内部只有一个 HandlerThread,MessageQueue 保证消息按顺序处理。如果需要并发执行,可以在
-
追问2:多次调用 startService() 会创建多个 IntentService 实例吗?
- 答:不会。Service 在同一进程中只有一个实例(单例),多次
startService()只会多次调用onStartCommand(),将 Intent 加入消息队列,由同一个 HandlerThread 串行处理。
- 答:不会。Service 在同一进程中只有一个实例(单例),多次
【高频题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:
- 处理 Intent1,调用
stopSelf(1),发现 1 < 3(最新 startId),服务不停止 - 处理 Intent2,调用
stopSelf(2),发现 2 < 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 版本。
深入展开(追问后) : 废弃原因详解:
- 后台限制:Android 8.0(API 26)引入后台执行限制,应用不在前台时无法启动后台服务,即使启动了也会在 5 秒内被杀死
- 功能单一:只能串行处理任务,无法并发,也无法设置优先级、延迟执行等
- 生命周期管理复杂:开发者需要处理 Android 8.0+ 的前台服务通知,增加复杂度
- 更好的替代方案:WorkManager 提供了更强大的功能,且由 Jetpack 库持续维护
替代方案对比:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| WorkManager | 延迟任务、定期任务 | 保证执行、自动重试、约束条件 | 不适合立即执行的任务 |
| 前台服务 | 用户感知的任务(下载、播放) | 不受后台限制 | 必须显示通知 |
| JobScheduler | Android 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+:
- 在
onHandleIntent()开始时调用startForeground()转为前台服务 - 显示通知(必需)
- 任务完成后调用
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 说明不在主线程。
深入展开(追问后) : 从源码追踪线程切换过程:
-
onCreate() 创建 HandlerThread:
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); // 启动工作线程 -
获取工作线程的 Looper:
mServiceLooper = thread.getLooper(); // 工作线程的 Looper -
创建绑定工作线程的 Handler:
mServiceHandler = new ServiceHandler(mServiceLooper); // Handler 绑定工作线程 -
消息在工作线程处理:
// 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。需要通过以下方式切换到主线程:Handler(Looper.getMainLooper()).post()runOnUiThread()(Activity 方法)- 发送广播,Activity 注册 BroadcastReceiver 接收
- 使用 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 为 null | IntentService 不使用 |
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 不丢失,前面已处理的和正在处理的都会丢失。如果需要保证所有任务都执行,应该:- 使用 WorkManager(任务持久化)
- 或在
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 都会发送到同一个服务实例的消息队列,按顺序串行处理。
原因分析:
- Service 单例特性:同一进程中,相同类名的 Service 只有一个实例(由 ActivityManagerService 保证)
- 首次启动创建:第一次
startService()时系统创建 Service 实例并调用onCreate() - 后续启动复用:后续
startService()复用已存在的实例,只调用onStartCommand() - 消息队列排队:所有 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 不支持并发,可以:
- 在
onHandleIntent()中手动创建线程池并发执行子任务 - 使用普通 Service + ThreadPoolExecutor
- 使用 WorkManager 并行 Worker
- 在
-
【进阶题3】IntentService 的内存占用如何优化?
参考答案: IntentService 的内存占用主要来自三个方面:HandlerThread 线程栈、MessageQueue 中的消息、onHandleIntent() 中的业务对象。
优化策略:
-
及时释放大对象
@Override protected void onHandleIntent(Intent intent) { Bitmap bitmap = null; try { bitmap = loadLargeBitmap(); processBitmap(bitmap); } finally { // 及时回收 if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); } } } -
避免在 Intent 中传递大数据
// ❌ 避免:传递大对象 intent.putExtra("bitmap", largeBitmap); // Parcelable 序列化占用大量内存 // ✅ 推荐:传递文件路径或 ID intent.putExtra("imagePath", "/sdcard/image.jpg"); // 在 onHandleIntent 中读取文件 -
控制消息队列长度
// 如果任务积压过多,考虑丢弃或合并 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(); } } -
任务完成后及时停止服务
// IntentService 自动停止,但如果长时间没任务,可手动停止 @Override protected void onHandleIntent(Intent intent) { performTask(intent); // 不需要额外操作,IntentService 自动处理 }
追问:
-
HandlerThread 的线程栈占用多少内存?
- 答:默认线程栈大小取决于系统,通常为 1MB。可以通过检查
/proc/[pid]/maps查看实际占用。HandlerThread 本身是轻量级的,主要内存占用来自业务对象。
- 答:默认线程栈大小取决于系统,通常为 1MB。可以通过检查
4.3 实战场景题
【场景题】设计一个支持断点续传的文件下载服务
问题描述: 使用 IntentService 实现文件下载服务,要求:
- 支持多个文件顺序下载
- 支持断点续传(应用重启后继续下载)
- 下载进度实时通知 UI
- 处理网络异常和重试
答案思路:
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)保证应用重启后继续下载
追问:
-
方案缺点?
-
答:
- 广播发送频繁可能影响性能(可改用 LocalBroadcastManager 或 EventBus)
- IntentService 串行下载,无法同时下载多个文件(可改用线程池)
- Android 8.0+ 需要前台服务,必须显示通知
- 下载大文件时内存占用高(可使用流式下载,不要一次性读入内存)
-
-
其他方案?
-
答:
- DownloadManager:系统提供的下载服务,自动支持断点续传、通知栏显示
- WorkManager + CoroutineWorker:现代化方案,支持约束条件、自动重试
- OkHttp + RxJava:应用内下载,灵活控制,但需要处理生命周期
-
-
如何优化?
-
答:
- 使用 LocalBroadcastManager 或 LiveData 替代全局广播
- 降低进度通知频率(如每 500KB 通知一次)
- 使用 BufferedInputStream 和 BufferedOutputStream 提升 I/O 性能
- 下载状态持久化到数据库,支持查询下载历史
-
五、对比与总结
5.1 关键API对比
| API/方法 | 作用 | 使用场景 | 注意事项 |
|---|---|---|---|
IntentService(String name) | 构造函数,指定工作线程名称 | 创建 IntentService 子类 | 必须调用 super(name) |
onHandleIntent(Intent) | 抽象方法,处理 Intent 任务 | 子类实现业务逻辑 | 运行在工作线程,不能更新 UI |
setIntentRedelivery(boolean) | 设置 Intent 重新投递 | 重要任务开启,临时任务关闭 | 影响 onStartCommand() 返回值 |
onStartCommand() | 系统调用,将 Intent 入队 | 自动调用,通常不需重写 | 返回 START_REDELIVER_INTENT 或 START_NOT_STICKY |
onCreate() | 创建 HandlerThread | 自动调用,通常不需重写 | 只调用一次 |
onDestroy() | 退出 HandlerThread | 自动调用,通常不需重写 | 所有任务完成后调用 |
startService(Intent) | 启动服务并发送 Intent | 外部启动服务 | Android 8.0+ 需要前台服务 |
stopSelf(int startId) | 停止服务(内部调用) | 自动调用,不需手动调用 | 使用 startId 保证所有任务完成 |
5.2 核心要点速记
一句话记忆: IntentService = Service + HandlerThread + 自动停止,用于在后台串行处理异步任务,已被 WorkManager 替代。
3个关键点:
- 基于 HandlerThread:内部创建 HandlerThread 在子线程处理任务,避免阻塞主线程
- 串行处理 Intent:多个 Intent 按顺序执行,MessageQueue 保证串行
- 自动停止服务:通过
stopSelf(startId)实现所有任务完成后自动停止
面试官最爱问:
- IntentService 和普通 Service 的区别? (必问)
- IntentService 如何自动停止? (高频)
- 为什么 IntentService 被废弃?如何替代? (高频)
- onHandleIntent() 运行在哪个线程? (常问)
- 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-同步屏障/- 深入理解消息优先级机制