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
    • 避免无效的服务保活,设计上要接受服务可能被杀死。
相关推荐
To_OC7 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic7 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy8 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志8 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble9 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯10 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜11 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司11 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g11 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森11 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http