嘿,各位Android开发者们,有没有遇到过这种场景?你精心设计的App,用户听着你App里的音乐,切出去回个微信,结果音乐戛然而止…… 那一刻,用户想卸载App的心都有了吧?
别慌,这往往是因为你没请对“后台打工人”——今天的主角:Started Service。
想象一下,你的App是一个公司:
Activity:就是公司的前台和接待室,光鲜亮丽,直接跟用户(顾客)打交道。但用户一走,前台可能就下班了。Service:则是公司里默默无闻的技术部、后勤部。他们不需要直接面对用户,但公司核心的业务逻辑(比如播放音乐、下载文件、同步数据)全靠他们支撑。即使用户离开了App(前台),这些部门依然可以继续加班干活。而Started Service,就是一种“一次吩咐,长期干活”的打工人。你(Activity)给它一个指令,它就开始工作,即使你离开了,它也能持续运行,直到任务完成或你明确让它下班。
今天,咱们就抛开枯燥的官方文档,用一场“人力资源部”的视角,深度分析如何“招聘”并“管理”好这位名叫 “继承Service类” 的王牌员工。
首先,想招聘,得知道这个岗位的基本要求。在Android世界里,你想创建一个Started Service,继承
android.app.Service 类是你的不二法门。
这就像你想招个程序员,他必须会写代码一样基础且重要。
这个
Service 类本身是个“抽象”的领导,它定义了一些基本行为规范(方法),但具体怎么干活,需要你这个“老板”来重写。
它的核心生命周期方法,就是这位打工人的“工作流程”:
onCreate(): 员工入职培训。
当Service第一次被创建时,系统会自动调用。这只会在它“生命”的开始发生一次。适合做什么:初始化一些耗时的资源,比如开启线程、初始化音乐播放器、绑定数据库连接等。就像给新员工配电脑、开通账号。
onStartCommand(Intent intent, int flags, int startId): 接到具体工作指令。
这是Started Service的灵魂所在! 每次你的Activity(或其他组件)通过
startService(intent) 方法启动这个Service时,这个方法都会被调用。参数解读:
Intent intent: 老板派活时给的“工作单”,里面可以携带数据,比如“要下载的文件URL”、“要播放的音乐ID”。
int flags: 系统给的“派活标志”,通常不用太关心。
int startId: 本次启动的唯一ID。就像一个工单号,如果你连续派了多个活,可以用这个ID来区分和管理。
这个方法的返回值,决定了这位打工人的“抗压能力”和“工作态度”! 我们稍后重点讲。
onDestroy(): 员工离职清算。
当Service不再被使用并被销毁时调用。可能是你(老板)主动调用
stopSelf() 或
stopService(Intent),也可能是系统在资源极度紧张时“强制裁员”。适合做什么:释放所有资源,比如停止音乐播放、关闭网络连接、终止线程等。避免造成内存泄漏,做个有素质的前雇主。
刚才说了,
onStartCommand 的返回值是重中之重。它告诉系统:“如果我这个打工人不幸被系统‘杀掉’了,系统你后面应该怎么处理他?”
它主要有三个选择,对应三种“工作态度”:
START_NOT_STICKY (非粘性员工):
态度:“如果系统你把我干掉了,除非老板你重新明确叫我,否则我就不回来了。”场景:适用于那些“中断了也无所谓”的任务。比如定时从网络拉取一些非关键性的新闻资讯。如果中途系统资源不足杀了Service,那就等下次App需要时再启动就好。这是最常用、最省心的选项。
START_STICKY (粘性员工):
态度:“系统你杀了我?没关系,等系统资源够了,你得主动把我重新‘复活’!不过,之前老板给我的那个工作单(Intent)我忘了,你得给我个空的。”场景:适用于需要长期运行,但不依赖具体指令的Service,比如后台音乐播放器。系统杀掉后,音乐停了,但资源足够时Service会被重新创建,虽然不记得之前具体播哪首,但App的界面可以重新发指令让它播放。
START_REDELIVER_INTENT (负责任粘性员工):
态度:“系统你杀了我?不仅要把我复活,还得把之前没干完的那个活(Intent)原封不动地还给我,我得继续干!”场景:适用于绝对不能中断的任务,比如文件下载。系统杀掉后,一有机会就会重新创建Service,并且把上次那个包含下载URL的Intent再传给它,确保下载任务能继续或重新开始。
选择建议:除非你的任务真的非常重要(如文件下载),否则优先使用
START_NOT_STICKY,做一个不給系统添乱的好公民。
光说不练假把式。现在,我们来亲手创建一个Started Service,它的功能很“实用”:在后台默默运行,每隔几秒就在Logcat里打印一条“摸鱼提醒”,提醒你该起来活动了!
步骤一:创建MyBackgroundService类
// MyBackgroundService.java
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MyBackgroundService extends Service {
private static final String TAG = "MyBackgroundService";
private boolean isRunning = false;
private Thread backgroundThread;
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "打工人已入职,开始摸鱼监测任务!");
isRunning = true;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "老板发来指令:开始摸鱼监测!工单号:" + startId);
// 在后台开启一个线程执行任务,避免阻塞主线程(ANR!)
if (backgroundThread == null) {
backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while (isRunning) {
try {
// 每隔3秒打印一次日志
Thread.sleep(3000);
count++;
Log.d(TAG, "【摸鱼提醒 #" + count + "】 你已经盯着屏幕3秒了,起来喝口水,扭扭脖子吧!");
} catch (InterruptedException e) {
e.printStackTrace();
break; // 如果线程被中断,就退出循环
}
}
Log.d(TAG, "打工人收到下班信号,摸鱼监测任务结束。");
}
});
backgroundThread.start(); // 启动线程
}
// 我们选择当Service被系统杀死后,不自动重新启动(非粘性)
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "老板辞退了打工人,开始办理离职手续...");
isRunning = false; // 通知线程停止循环
// 中断线程,确保资源释放
if (backgroundThread != null) {
backgroundThread.interrupt();
backgroundThread = null;
}
}
// Started Service可以不需要绑定,所以这个返回null即可
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
代码深度解析:
isRunning 标志位:这是一个非常重要的“开关”。我们通过它来控制后台线程的循环。在
onDestroy() 时将其设为
false,线程就会优雅地结束,而不是被暴力中断。开新线程!开新线程!开新线程!:重要的事情说三遍。所有耗时操作绝对不能在
onStartCommand 的主线程中直接进行!否则必然导致ANR(Application Not Responding),你的App会卡死并被系统强制关闭。我们这里用
Thread 是最简单的演示,在实际项目中,你可能会使用
HandlerThread、
ExecutorService 或
Kotlin协程 等更优雅的方式。
onBind 返回null:因为我们创建的是纯粹的Started Service,不需要与Activity绑定通信,所以直接返回
null。
步骤二:在AndroidManifest.xml中声明你的Service
就像新员工入职需要在HR系统里登记一样,Service也必须在清单文件中注册。
<!-- 在 <application> 标签内添加 -->
<service
android:name=".MyBackgroundService"
android:enabled="true"
android:exported="false" />
exported="false" 表示这个Service只供自己的App内部使用,其他App不能来启动它,更安全。
步骤三:在Activity中启动和停止Service
现在,老板(Activity)可以给打工人(Service)派活和叫停了。
// MainActivity.java
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private Button startButton, stopButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startButton = findViewById(R.id.btn_start_service);
stopButton = findViewById(R.id.btn_stop_service);
startButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建Intent,明确指定要启动哪个Service
Intent startIntent = new Intent(MainActivity.this, MyBackgroundService.class);
// 可以携带数据
startIntent.putExtra("EXTRA_DATA", "来自前台的指令");
// 启动Service!
startService(startIntent);
}
});
stopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 停止Service
Intent stopIntent = new Intent(MainActivity.this, MyBackgroundService.class);
stopService(stopIntent);
}
});
}
}
对应的布局文件
activity_main.xml 很简单,就是两个按钮。
现在,运行你的App吧!点击“启动服务”后,即使你退出App回到桌面,打开Logcat,你依然能看到每隔3秒就出现的“摸鱼提醒”。这位打工人正在后台兢兢业业地工作着。
但是!请冷静思考一下:
这个“打工人”虽然能干,但它有点“耗电”。在Android O(8.0)及之后的版本中,Google为了控制后台耗电,对后台Service的限制越来越严格。一个App在后台随意启动Service的行为会被系统无情地限制。
所以,在现代Android开发中,对于需要长期在后台执行的任务,我们有了更优的替代方案:
JobIntentService /
JobService: 结合JobScheduler,让系统在合适的时机(比如充电且连接Wi-Fi时)帮你执行任务。
WorkManager: 这是目前官方推荐的、用于处理延迟性和可保证执行的后台任务的终极解决方案。它能兼容所有API级别,自动处理系统的后台限制。
结论:
继承
Service 类来创建Started Service,是Android后台开发的基石。它让你理解了后台任务的基本生命周期和运行机制。虽然在某些场景下已被更先进的组件替代,但这份“管理打工人”的核心思想是相通的。
先把这位“原始”的打工人摸透,你才能更好地驾驭
WorkManager 这样的“智能机器人管家”。现在,你不仅学会了如何创建它,更深度理解了它的工作方式和现代开发的演进方向。快去你的App里,试试培养一个得力的后台助手吧!