Android Service 使用篇

Service 是什么?

Service 是 Android 四大组件之一,专门用于在后台执行长时间运行的操作,不需要用户界面。它是你的 App 的"幕后工作者"。

一、Service 的核心用途

  1. 执行耗时操作: 网络下载、大文件处理、复杂计算(避免在主线程做这些导致 ANR)。
  2. 播放媒体: 音乐播放器、电台 App 的核心,音乐在后台播放时 Activity 可以关闭。
  3. 文件上传/下载: 即使 App 退到后台,上传下载任务也能继续。
  4. 与远程进程通信: 通过 AIDL 实现跨 App 的功能调用(如天气服务提供数据给多个 App)。
  5. 定时任务: (结合 AlarmManager 或 WorkManager) 在特定时间或间隔执行操作。
  6. 后台位置更新: (需谨慎处理权限和电量) 持续获取位置信息。

二、如何使用 Service?两种启动模式

Service 的生命周期和行为取决于你如何启动它:

  1. Started Service (启动服务)

    • 作用: 执行一个单次操作 ,操作完成后服务应该自己停止。例如:下载一个文件、上传一张图片、播放一首歌(虽然播放器通常用绑定模式)。

    • 如何启动: 在 Activity 或其他组件中调用 startService(Intent)

    • 特点:

      • 启动它的组件(如 Activity)销毁了,服务依然可以独立运行在后台
      • 服务执行的任务不直接返回结果给启动者。通常通过广播 (Broadcast)、通知 (Notification)、更新文件/数据库等方式告知结果。
      • 服务必须自己调用 stopSelf() 或在其他组件调用 stopService(Intent) 来停止,否则会一直运行(耗电!)。
    • 生命周期方法调用顺序:

      • onCreate() -> onStartCommand(Intent, int, int) -> (服务运行...) -> stopSelf()/stopService() -> onDestroy()
      • onStartCommand() 是核心,每次调用 startService() 都会触发它(即使服务已在运行),Intent 携带启动参数。

    示例代码 (启动一个下载服务):

    scala 复制代码
    // 在 Activity 中
    Intent downloadIntent = new Intent(this, DownloadService.class);
    downloadIntent.putExtra("url", "https://example.com/bigfile.zip");
    startService(downloadIntent); // 启动服务
    
    // 在 DownloadService 中
    public class DownloadService extends Service {
        @Override
        public void onCreate() {
            super.onCreate();
            // 初始化工作,如创建通知、线程池等 (只调用一次)
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            // 每次 startService() 都会调用这里
            String url = intent.getStringExtra("url");
            // 启动线程或 AsyncTask 执行下载 (记住:不能在主线程做耗时操作!)
            new DownloadTask().execute(url);
            // 告诉系统如果服务被意外杀死后如何处理
            return START_NOT_STICKY; // 常用:不重新创建服务
        }
    
        private class DownloadTask extends AsyncTask<String, Void, Void> {
            @Override
            protected Void doInBackground(String... urls) {
                // 执行实际的下载逻辑...
                // 下载完成或失败后,记得调用 stopSelf() 或发送广播/通知
                stopSelf(); // 任务完成,停止服务
                return null;
            }
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            // 清理资源,如关闭线程、移除通知
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null; // 纯启动服务不需要实现绑定
        }
    }
  2. Bound Service (绑定服务)

    • 作用: 提供客户端-服务器 接口,让其他组件(如 Activity)绑定 到服务上,进行交互 (调用方法、获取数据、监听状态)。例如:音乐播放器服务提供 play(), pause(), getPosition() 等方法给 Activity 调用;股票 App 服务提供实时股价数据流。

    • 如何绑定: 在 Activity 或其他组件中调用 bindService(Intent, ServiceConnection, int)

    • 特点:

      • 多个组件可以同时绑定到同一个服务。
      • 组件与服务建立了一条通信通道(通常是 Binder 对象),组件可以直接调用服务暴露的方法。
      • 所有绑定者都解绑 (unbindService()) 后,系统通常会销毁 该服务(除非它同时被 startService() 启动了)。
      • 绑定服务的组件(如 Activity)销毁时,会自动解绑
    • 生命周期方法调用顺序:

      • onCreate() -> onBind(Intent) -> (服务运行,客户端通过返回的 IBinder 通信) -> onUnbind(Intent) -> onDestroy()
      • onBind() 是关键,它返回一个 IBinder 对象,作为客户端与服务通信的桥梁。

    实现绑定服务的三种方式:

  • 1. 扩展 Binder 类 (最简单,同一进程内):

    • 在服务内部创建一个继承 Binder 的类,提供公共方法让客户端调用。
    • onBind() 返回这个 Binder 实例。
    • 客户端在 ServiceConnectiononServiceConnected() 中获取这个 Binder 并强转成服务内部类类型,即可调用方法。
    scala 复制代码
    // LocalService.java (服务端)
    public class LocalService extends Service {
        private final IBinder binder = new LocalBinder(); // 自定义Binder
    
        public class LocalBinder extends Binder {
            LocalService getService() {
                return LocalService.this; // 返回服务实例本身
            }
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return binder;
        }
    
        // 公共方法供客户端调用
        public int getRandomNumber() {
            return new Random().nextInt(100);
        }
    }
    
    // MainActivity.java (客户端)
    public class MainActivity extends Activity {
        LocalService mService;
        boolean mBound = false;
    
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // 获取Binder并转换
                LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
                mService = binder.getService(); // 拿到服务实例
                mBound = true;
                // 现在可以调用 mService.getRandomNumber() 了!
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                mBound = false;
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // ...
            Intent intent = new Intent(this, LocalService.class);
            bindService(intent, connection, Context.BIND_AUTO_CREATE); // 绑定服务
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            if (mBound) {
                unbindService(connection); // 解绑服务
                mBound = false;
            }
        }
    }
  • 2. 使用 Messenger (跨进程较简单):

    • 服务定义一个 Handler 来处理客户端通过 Message 对象发送的请求。
    • onBind() 返回一个 Messenger (基于 Handler 创建)。
    • 客户端拿到 Messenger 后,可以发送 Message 对象给服务端。服务端也可以回复消息。
    • 比 AIDL 简单,但通信是单向或请求-响应式的,不如 AIDL 灵活。
  • 服务端实现

scala 复制代码
// MessengerService.java
public class MessengerService extends Service {
    private static final String TAG = "MessengerService";
    public static final int MSG_SAY_HELLO = 1;
    public static final int MSG_REPLY = 2;

    // 服务端的 Handler,处理客户端发来的消息
    private static class ServiceHandler extends Handler {
        private final WeakReference<Context> mContextRef;
        private Messenger mClientMessenger; // 客户端的 Messenger(用于双向通信)

        ServiceHandler(Context context) {
            mContextRef = new WeakReference<>(context);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Log.d(TAG, "收到客户端消息: " + msg.getData().getString("data"));
                    // 如果是双向通信,可以从 msg.replyTo 获取客户端的 Messenger
                    mClientMessenger = msg.replyTo;
                    if (mClientMessenger != null) {
                        try {
                            // 向客户端回复消息
                            Message replyMsg = Message.obtain(null, MSG_REPLY);
                            Bundle bundle = new Bundle();
                            bundle.putString("reply", "你好客户端,我是服务端");
                            replyMsg.setData(bundle);
                            mClientMessenger.send(replyMsg);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    // 服务端的 Messenger(将 Handler 绑定到 Messenger)
    private final Messenger mMessenger = new Messenger(new ServiceHandler(this));

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return mMessenger.getBinder(); // 返回 Messenger 的 Binder 对象
    }
}
  • 客户端实现
java 复制代码
// MainActivity.java
public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private Messenger mServiceMessenger; // 服务端的 Messenger
    private boolean mBound; // 是否已绑定服务

    // 客户端的 Handler,用于接收服务端回复(双向通信时才需要)
    private static class ClientHandler extends Handler {
        private final WeakReference<MainActivity> mActivityRef;

        ClientHandler(MainActivity activity) {
            mActivityRef = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivityRef.get();
            if (activity != null && msg.what == MessengerService.MSG_REPLY) {
                String reply = msg.getData().getString("reply");
                Log.d(TAG, "收到服务端回复: " + reply);
                Toast.makeText(activity, reply, Toast.LENGTH_SHORT).show();
            }
        }
    }

    // 客户端的 Messenger(用于双向通信)
    private final Messenger mClientMessenger = new Messenger(new ClientHandler(this));

    // ServiceConnection 用于绑定/解绑服务
    private final ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "服务已连接");
            // 通过服务端返回的 IBinder 创建 Messenger
            mServiceMessenger = new Messenger(service);
            mBound = true;

            // 向服务端发送消息(单向通信示例)
            sendMessageToService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "服务已断开");
            mServiceMessenger = null;
            mBound = false;
        }
    };

    private void sendMessageToService() {
        if (!mBound) return;
        try {
            // 创建消息
            Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO);
            Bundle data = new Bundle();
            data.putString("data", "你好服务端,我是客户端");
            msg.setData(data);

            // 如果是双向通信,设置 replyTo
            msg.replyTo = mClientMessenger;

            // 发送消息
            mServiceMessenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_bind).setOnClickListener(v -> bindService());
        findViewById(R.id.btn_unbind).setOnClickListener(v -> unbindService());
    }

    private void bindService() {
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    private void unbindService() {
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService();
    }
}
  • 3. 使用 AIDL (Android Interface Definition Language - 跨进程复杂交互):

    markdown 复制代码
    -   定义 AIDL 接口文件 (.aidl),描述服务公开的方法。
    -   编译时 Android SDK 工具会生成对应的 Java 接口和 Stub 类。
    -   服务继承 `Stub` 类并实现 AIDL 接口定义的方法,在 `onBind()` 中返回这个 `Stub` 实例。
    -   客户端绑定后,将 `onServiceConnected()` 返回的 `IBinder` 转换成 AIDL 接口类型,即可调用远程方法。
    -   这是实现**跨进程通信 (IPC)**  最强大但也最复杂的方式。系统服务(如 `LocationManagerService`)大多通过 AIDL 暴露接口。

三、关键概念与注意事项

  1. 生命周期: 深刻理解 StartedBound 两种模式下的生命周期回调 (onCreate, onStartCommand, onBind, onUnbind, onDestroy) 及其触发条件。服务不会自动停止! 启动服务需手动停,绑定服务需所有客户端解绑。

  2. 主线程警告: Service 的所有生命周期方法默认都在主线程 (UI 线程) 执行绝对不能在 onStartCommand()onBind() 里直接执行耗时操作 ,否则会导致 ANR (Application Not Responding)。必须使用工作线程 (Thread, HandlerThread, ExecutorService, AsyncTask - 已废弃, 推荐 Kotlin 协程/RxJava) 或 IntentService (见下文)。

  3. 前台服务 (Foreground Service):

    • 为什么需要? Android 8.0 (Oreo) 后,普通后台服务受到严格限制,容易被系统杀死。需要长时间运行且用户可感知的服务(如音乐播放、导航、文件下载)应提升为前台服务。
    • 怎么做? 在服务中调用 startForeground(int id, Notification notification)必须提供一个持续显示的通知 (Notification),告知用户服务正在运行。这会给服务更高的优先级,减少被杀死的概率。
    • 完成后调用 stopForeground(boolean removeNotification) 可降回后台(服务本身不会停止)。
  4. IntentService:

    • 系统提供的 Service 子类,专门用于处理异步的、一次性的启动请求
    • 优点: 内部自带工作线程,请求会排队处理 (onHandleIntent(Intent) 在工作线程执行),所有请求处理完后自动调用 stopSelf()
    • 缺点: 不能绑定,不能直接处理并发任务(排队串行执行),Android 8.0 后同样受后台限制影响,适合简单任务。Android 11 标记为废弃 ,官方推荐使用 JobIntentService (也废弃) 或更现代的 WorkManager
  5. Android 8.0+ 后台限制:

    • 背景:为了优化电池续航和系统性能。

    • 核心规则: 当 App 进入后台 (用户不再与其交互,如按 Home 键或切换到其他 App)后,它有几分钟的时间窗口 可以创建和运行服务。时间窗口结束后,系统会停止所有后台服务

    • 应对策略:

      • 前台服务: 需要长时间运行且用户可感知的任务。
      • JobScheduler / WorkManager: 安排稍后执行或满足条件(如充电、有网络)时执行的后台任务。这是 Google 强烈推荐的现代后台任务处理方式。WorkManager 兼容旧版本,能处理网络、存储空间、电量等约束。
      • Foreground Service + 通知: 最常用手段。
      • 豁免情况: 处理高优先级 FCM 消息、活动识别等特定场景。
  6. 服务保活误区:

    • 不要尝试用 startForeground() 但隐藏通知(会被系统检测并杀死)。
    • 不要循环调用 startService()AlarmManager 频繁唤醒(浪费电,用户体验差,新系统会限制)。
    • 正确思路: 接受服务会被杀死的现实。使用前台服务让用户知情;使用 WorkManager 保证任务最终会被执行;服务重启时 (onStartCommandflags) 考虑恢复状态;重要数据及时持久化(数据库/文件)。

四、总结

  • Service 是 Android 的幕后工作者,用于后台执行长时间任务。

  • 两种启动模式:

    • Started Service (startService): 执行单次任务,启动者与服务无直接通信,需手动停止。
    • Bound Service (bindService): 提供交互接口,客户端与服务直接通信,所有客户端解绑后服务通常停止。实现方式有 Binder (同进程)、Messenger (跨进程简单)、AIDL (跨进程强大)。
  • 关键要点:

    • 服务生命周期方法在主线程运行,耗时操作必须开工作线程
    • Android 8.0+ 后台服务受限 ,需要长时间运行的服务务必使用前台服务并显示通知。
    • IntentService 是简化启动服务的工具(已废弃,了解即可)。
    • 现代后台任务首选 WorkManager
    • 避免无效的服务保活,设计上要接受服务可能被杀死。
相关推荐
字节架构前端18 分钟前
k8s场景下的指标监控体系构建——Prometheus 简介
前端·架构
奕羽晨31 分钟前
关于CSS的一些读书笔记
前端·css
Poetry23739 分钟前
大屏数据可视化适配方案
前端
遂心_1 小时前
用React Hooks + Stylus打造文艺范的Todo应用
前端·javascript·react.js
轻语呢喃1 小时前
<a href=‘ ./XXX ’>,<a href="#XXX">,<Link to="/XXX">本质与区别
前端·react.js·html
用户3802258598241 小时前
vue3源码解析:watch的实现
前端·vue.js·源码
F2E_Zhangmo1 小时前
第一章 uniapp实现兼容多端的树状族谱关系图,创建可缩放移动区域
前端·javascript·uni-app
鹏程十八少1 小时前
3. Android 第三方框架 Okhttp, Retrofit 的动态代理和适配器模式深度解读三
前端
阿怼丶1 小时前
🚶‍♂️基于 Three.js 的自定义角色漫游系统实战:支持碰撞检测与动画控制
前端·three.js