Android语言基础教程(244)Android管理Service的生命周期经典范例之视力保护程序:别让App在后台摸鱼!Android Service生命周期拿捏指南,附赠“防瞎眼”代码护体

  • 时间:2025-11-19 19:23 作者: 来源: 阅读:0
  • 扫一扫,手机访问
摘要:Service,一个在后台默默干活的“老黄牛” 嘿,屏幕前的那位开发者,对,就是你!是不是感觉眼睛又干又涩,腰酸背痛,一看时间才发现已经在电脑前连续“肝”了仨小时?咱们搞开发的,视力那可是革命的本钱啊! 今天,咱们不聊高深莫测的架构,也不扯花里胡哨的UI,就来聊聊Android里一个看似简单,却让无数英雄好汉折腰的基石——Service,以及它那“跌宕起伏”的生命周期。 想象一下,Servi

Service,一个在后台默默干活的“老黄牛”

嘿,屏幕前的那位开发者,对,就是你!是不是感觉眼睛又干又涩,腰酸背痛,一看时间才发现已经在电脑前连续“肝”了仨小时?咱们搞开发的,视力那可是革命的本钱啊!

今天,咱们不聊高深莫测的架构,也不扯花里胡哨的UI,就来聊聊Android里一个看似简单,却让无数英雄好汉折腰的基石——Service,以及它那“跌宕起伏”的生命周期

想象一下,Service就是你App里一个任劳任怨的后台工人。它没有脸(UI),但你却可以吩咐它默默干活,比如在后台播着音乐、下载文件,或者——像我们今天要做的——定时提醒你起来活动,保护你那双珍贵的“卡姿兰大眼睛”。

但问题来了:这个“工人”什么时候上班?什么时候摸鱼?什么时候会被系统这个“无情老板”给炒鱿鱼?搞不清楚这些,你的Service就可能分分钟“暴毙”,让你的提醒功能形同虚设。

别慌,今天就跟着我,通过一个超实用的 “视力保护小助手” 范例,把Service的生命周期扒得底裤都不剩!

第一章:Service生命周期——一场关于“出生入死”的哲学

在开始写代码前,咱们得先搞懂Service的“人生轨迹”。官方文档那张图可能看得你头晕,我来给你翻译成人话。

Service的生命周期,主要取决于你“启动”它的方式。有两种经典姿势:

姿势一: startService() —— “单飞模式”

你一声令下 ( startService),Service就屁颠屁颠地开始工作。它就像一个独立团,一旦启动,就能在后台运行很长时间,哪怕启动它的Activity(你的App界面)已经被销毁了,它依然在坚守岗位。

生命周期路径: onCreate() -> onStartCommand() -> ... (运行中) ... -> stopSelf() stopService() -> onDestroy()形象比喻: 就像你点了个外卖,下单后 ( startService),外卖小哥 ( Service) 就开始送货了。他不需要和你一直保持联系,直到把饭送到你手上或者你取消了订单 ( stopService),他的任务才结束。

姿势二: bindService() —— “搭伙过日子模式”

一个或多个组件(比如Activity)通过 bindService()与Service建立连接。这时,Service更像是一个提供服务的“顾问”。一旦所有绑定的客户都解绑了,Service一般就会自行了断 ( onDestroy)。

生命周期路径: onCreate() -> onBind() -> ... (通信中) ... -> onUnbind() -> onDestroy()形象比喻: 就像你请了个私人律师 ( Service),你 ( Activity) 需要和他建立联系 ( bindService),随时咨询法律问题。如果你不再需要他了(解绑),而恰好又没有其他客户,那律师就可能下班回家了。

最骚的姿势三:混合双打 —— “先单飞,再搭伙”

这也是我们今天范例的核心!我们的视力保护程序,既需要长期在后台运行(单飞,定时提醒),又需要与前台Activity通信(搭伙,显示下次提醒时间、取消提醒)。

这种模式下的生命周期就变得“丰富多彩”了:
onCreate() -> onStartCommand() (被start) -> onBind() (被bind) -> ... (既在运行,又在被通信) ... -> onUnbind() (所有绑定的都离开了) -> ... (依然在运行) ... -> stopSelf() -> onDestroy()

看懂了吗?关键在于,即使所有绑定的Activity都解绑了,只要之前被 startService()过,这个Service就还活着!这才是实现我们长期后台任务的关键。

第二章:实战!打造你的“防瞎眼”卫士

理论BB再多,不如代码来一波。开整!

第一步:创建我们的苦力——EyesProtectionService


// EyesProtectionService.java
public class EyesProtectionService extends Service {
 
    // 核心:定时任务的线程池
    private ScheduledExecutorService scheduler;
    private ScheduledFuture<?> future;
 
    // 核心:与Activity通信的信使
    private Messenger activityMessenger;
 
    // 常量:定义消息类型
    public static final int MSG_REGISTER_CLIENT = 1;
    public static final int MSG_UNREGISTER_CLIENT = 2;
    public static final int MSG_UPDATE_COUNTDOWN = 3;
    public static final int MSG_SHOW_REMINDER = 4;
 
    // 处理来自Activity的消息
    private class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_REGISTER_CLIENT:
                    // Activity来建立连接了,记下它的联系方式(Messenger)
                    activityMessenger = msg.replyTo;
                    break;
                case MSG_UNREGISTER_CLIENT:
                    // Activity要断开连接了,毁掉联系方式
                    activityMessenger = null;
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
 
    // 这是Service对外通信的“接口”
    final Messenger serviceMessenger = new Messenger(new IncomingHandler());
 
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("EyesProtection", "Service: onCreate -> 服务被创建了,初始化线程池");
        // 初始化定时器
        scheduler = Executors.newScheduledThreadPool(1);
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("EyesProtection", "Service: onStartCommand -> 服务被启动,开始定时任务");
        startTimerTask();
        // 返回 START_STICKY,保证服务被系统杀掉后有机会重启
        return START_STICKY;
    }
 
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("EyesProtection", "Service: onBind -> 有Activity来绑定我了,把Binder(信使)给它");
        // 把我们的信使(Messenger)通过Binder返回给Activity
        return serviceMessenger.getBinder();
    }
 
    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("EyesProtection", "Service: onUnbind -> 所有Activity都和我解绑了,但我还在后台运行哦!");
        // 注意:这里返回true,表示如果下次再有Activity绑定,会调用onRebind而不是onBind
        return true;
    }
 
    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.d("EyesProtection", "Service: onRebind -> 又有Activity来绑定我了(老客户回归)");
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("EyesProtection", "Service: onDestroy -> 服务被销毁,释放资源");
        stopTimerTask();
        scheduler.shutdown();
    }
 
    /**
     * 核心逻辑:启动定时任务
     */
    private void startTimerTask() {
        if (future != null && !future.isDone()) {
            future.cancel(true);
        }
 
        // 设定每20分钟提醒一次
        final long period = 20 * 60 * 1000; // 毫秒
        final long initialDelay = 0; // 立即开始
 
        future = scheduler.scheduleAtFixedRate(new Runnable() {
            long countdown = period;
 
            @Override
            public void run() {
                countdown -= 1000; // 每秒减1秒
 
                // 1. 实时更新倒计时给Activity(如果绑定了的话)
                sendMessageToActivity(MSG_UPDATE_COUNTDOWN, countdown);
 
                // 2. 时间到!弹出提醒
                if (countdown <= 0) {
                    sendMessageToActivity(MSG_SHOW_REMINDER, 0);
                    // 重置倒计时
                    countdown = period;
                }
            }
        }, initialDelay, 1000, TimeUnit.MILLISECONDS); // 每隔1秒执行一次
    }
 
    private void stopTimerTask() {
        if (future != null) {
            future.cancel(true);
            future = null;
        }
    }
 
    /**
     * 通过Messenger给Activity发消息
     */
    private void sendMessageToActivity(int what, long value) {
        if (activityMessenger != null) {
            try {
                Message msg = Message.obtain(null, what, (int) value, 0);
                activityMessenger.send(msg);
            } catch (RemoteException e) {
                // Activity可能已经挂了,清空Messenger
                activityMessenger = null;
            }
        }
    }
}

代码灵魂解读:

onCreate/onDestroy:服务的“生”与“死”。在这里完成资源的初始化和释放,比如我们的定时器线程池。 onStartCommand:这是单飞模式的命门!当我们的MainActivity调用 startService时,它被触发。我们在这里启动了核心的定时任务。返回值 START_STICKY是个心机Boy,它告诉系统:“如果因为内存不足杀了我,等内存宽裕了,记得把我重新拉起来!” 这保证了提醒的持久性。 onBind/onUnbind/onRebind:这是搭伙模式的舞台。我们在这里通过 Messenger(一个基于AIDL的轻量级IPC工具)把Service和Activity连接起来,让它们可以安全地“隔空对话”。 startTimerTask:核心逻辑。用一个定时线程池,每秒检查一次是否到了休息时间,并通过 sendMessageToActivity方法将倒计时和提醒发送给前台界面。
第二步:打造前台界面——MainActivity


// MainActivity.java
public class MainActivity extends AppCompatActivity {
 
    private TextView tvCountdown;
    private Button btnToggle;
 
    // 与服务通信的关键先生
    private Messenger serviceMessenger;
    private boolean bound = false;
 
    // 用于接收Service消息的Handler
    private Handler activityHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case EyesProtectionService.MSG_UPDATE_COUNTDOWN:
                    // 收到倒计时更新,刷新UI
                    long seconds = msg.arg1 / 1000;
                    long minutes = seconds / 60;
                    seconds = seconds % 60;
                    tvCountdown.setText(String.format("下次提醒: %02d:%02d", minutes, seconds));
                    break;
                case EyesProtectionService.MSG_SHOW_REMINDER:
                    // 收到提醒!弹个窗,震个动
                    showReminderDialog();
                    break;
            }
            return false;
        }
    });
 
    // Activity自己的信使,用于接收消息
    final Messenger activityMessenger = new Messenger(activityHandler);
 
    // 连接Service的“桥梁”
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("EyesProtection", "Activity: onServiceConnected -> 成功连接到Service!");
            // 拿到Service的信使
            serviceMessenger = new Messenger(service);
            bound = true;
            // 告诉Service:“我准备好了,可以给我发消息了!”
            try {
                Message msg = Message.obtain(null, EyesProtectionService.MSG_REGISTER_CLIENT);
                msg.replyTo = activityMessenger;
                serviceMessenger.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("EyesProtection", "Activity: onServiceDisconnected -> 与Service的连接意外断开了!");
            serviceMessenger = null;
            bound = false;
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        tvCountdown = findViewById(R.id.tv_countdown);
        btnToggle = findViewById(R.id.btn_toggle);
 
        btnToggle.setOnClickListener(v -> toggleService());
    }
 
    @Override
    protected void onStart() {
        super.onStart();
        // 启动并绑定服务!混合双打开始了!
        Intent intent = new Intent(this, EyesProtectionService.class);
        startService(intent); // 单飞:保证服务长期运行
        bindService(intent, connection, Context.BIND_AUTO_CREATE); // 搭伙:建立通信
    }
 
    @Override
    protected void onStop() {
        super.onStop();
        // 解绑服务(但服务不会停止,因为被start过)
        if (bound) {
            if (serviceMessenger != null) {
                try {
                    // 告诉Service:“我要走了,别再给我发消息了”
                    Message msg = Message.obtain(null, EyesProtectionService.MSG_UNREGISTER_CLIENT);
                    msg.replyTo = activityMessenger;
                    serviceMessenger.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            unbindService(connection);
            bound = false;
        }
    }
 
    private void toggleService() {
        Intent intent = new Intent(this, EyesProtectionService.class);
        if (bound) {
            // 停止服务
            stopService(intent);
            tvCountdown.setText("服务已停止");
            btnToggle.setText("启动保护");
            bound = false;
        } else {
            // 重新启动并绑定
            startService(intent);
            bindService(intent, connection, Context.BIND_AUTO_CREATE);
            btnToggle.setText("停止保护");
        }
    }
 
    private void showReminderDialog() {
        new AlertDialog.Builder(this)
                .setTitle("护眼时间到!")
                .setMessage("亲爱的程序员,你已经连续工作20分钟了!快起来活动一下,看看远方,让你的眼睛休息一下吧!")
                .setPositiveButton("朕知道了", null)
                .setCancelable(false)
                .show();
        // 这里可以加上振动、铃声等效果,提醒更给力
    }
}

代码灵魂解读:

onStart:这里是混合双打的起手式。先 startService()让服务在后台跑起来,再 bindService()建立连接,准备通信。 ServiceConnection:连接服务的回调。在 onServiceConnected中,我们拿到了Service的 Messenger,并立即发送一个注册消息,告诉Service:“嘿,这是我的联系方式 ( activityMessenger),有消息就往这儿发!” activityHandler:消息处理中心。在这里,我们接收来自Service的倒计时更新和提醒指令,并更新UI或弹出对话框。 onStop:当Activity不可见时,我们解绑服务,避免内存泄漏。但请注意!我们只调用了 unbindService()没有调用 stopService()!所以,Service依然在后台默默地为你计时呢!这就是混合模式的神奇之处。 toggleService:手动停止服务的开关。只有在这里,我们才调用 stopService(),真正结束Service的生命。
第三步:别忘了在AndroidManifest.xml里给Service上户口!


<application ...>
    <activity ...>
        ...
    </activity>
    <service
        android:name=".EyesProtectionService"
        android:enabled="true"
        android:exported="false" />
</application>

第三章:运行与复盘——看Service如何“演绎一生”

现在,运行这个App吧!

启动App:LogCat里会依次出现: Service: onCreate Service: onStartCommand Activity: onServiceConnected Service: onBind
看,Service被创建、启动、然后被绑定,生命周期清晰无比。 按Home键回到桌面:Activity的 onStop被调用,它解绑了服务。 Activity: onServiceDisconnected (注意:这个可能会在解绑后稍后触发,取决于系统) Service: onUnbind
但你的通知栏可能没有变化,Service还在!倒计时仍在继续!因为它被 startService()过。 20分钟后:即使你的App在后台,一个提醒对话框会突然弹出(如果系统允许的话,或者你需要用前台服务+通知来保证),告诉你该休息了!再次打开App:Activity重新绑定服务。 Service: onRebind (因为我们之前 onUnbind返回了true)
界面上的倒计时会立刻更新为当前准确的时间。 点击“停止保护”:Activity调用 stopService(),Service走到生命的终点: Service: onDestroy

结语:你与Service,从此是兄弟

通过这个“视力保护程序”的实战,我们不仅做出了一个对自己健康有益的小工具,更重要的是,我们彻底拿捏了Android Service生命周期的精髓。

记住这几个关键点:

想让服务一直跑,就用 startService()想和服务说说话,就用 bindService()又想一直跑又想说话?那就“混合双打”! onCreate/onDestroy 是生命的两端。 onStartCommand 的返回值决定了服务“死后”的态度。妥善管理连接和消息,避免内存泄漏。

希望这篇有血有肉、有笑有料的文章,能让你对Service的理解提升一个Level。从此,让你的后台任务像老黄牛一样可靠,再也不怕它在后台“摸鱼”或“暴毙”了。

快去运行代码,保护你的眼睛,也巩固你的知识吧!我们下期再见!

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部