Android四大组件之Service入门基础详解

Android四大组件之Service

1. Service概念

Service服务是一个后台运行的组件,执行长时间运行且不需要用户交互的任务。即使应用被销毁也依然可以工作。服务可以在后台执行任务,如播放音乐、下载文件、处理网络请求等。服务可以在应用组件之间进行通信,如发送广播、使用AIDL进行进程间通信等。服务可以分为两种类型:Started和Bound。

状态 说明描述
Started Android的应用程序组件,如活动,通过startService()启动了服务,则服务是Started状态。一旦启动,服务可以在后台无限期运行,即使启动它的组件已经被销毁。
Bound 当Android的应用程序组件通过bindService()绑定了服务,则服务是Bound状态。Bound状态的服务提供了一个客户服务器接口来允许组件与服务进行交互,如发送请求,获取结果,甚至通过IPC来进行跨进程通信。
Service的特点
  • 运行在主线程中,不会自动创建新线程
  • 没有用户界面
  • 优先级高于后台Activity
  • 可以被其他组件启动和绑定
  • 适合执行长时间的后台任务

2. 创建与启动Service

2.1 创建Service

创建一个Service需要继承Service类并重写关键方法,示例代码如下:

java 复制代码
public class MyService extends Service {
    private static final String TAG = "MyService";
    
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "Service onCreate");
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "Service onStartCommand");
        // 执行后台任务
        return START_STICKY; // 服务被杀死后会重新创建
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "Service onBind");
        return null; // 不支持绑定则返回null
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Service onDestroy");
    }
}

2.2 在AndroidManifest.xml中注册

xml 复制代码
<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="false" />

2.3 启动Service的方式

启动Service有两种方式:startService()和bindService()。

方式一:startService()
java 复制代码
// 启动服务
Intent serviceIntent = new Intent(this, MyService.class);
startService(serviceIntent);

// 停止服务
stopService(serviceIntent);
// 或在Service内部调用
stopSelf();
方式二:bindService()
java 复制代码
private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d(TAG, "Service connected");
    }
    
    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(TAG, "Service disconnected");
    }
};

// 绑定服务
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);

// 解绑服务
unbindService(serviceConnection);

3. Service中的线程处理

3.1 Service运行在主线程

Service默认运行在主线程中,执行耗时操作会阻塞UI,需要手动创建子线程:

java 复制代码
public class MyService extends Service {
    private Thread workThread;
    private boolean isRunning = false;
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        isRunning = true;
        workThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRunning) {
                    try {
                        // 执行耗时任务
                        Thread.sleep(1000);
                        Log.d(TAG, "Background task running...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        workThread.start();
        return START_STICKY;
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        isRunning = false;
        if (workThread != null) {
            workThread.interrupt();
        }
    }
}

3.2 使用IntentService

IntentService自动在子线程中处理任务:

java 复制代码
public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MyIntentService");
    }
    
    @Override
    protected void onHandleIntent(Intent intent) {
        // 在子线程中执行
        String action = intent.getStringExtra("action");
        if ("download".equals(action)) {
            performDownload();
        }
    }
    
    private void performDownload() {
        // 执行下载任务
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
                Log.d(TAG, "Download progress: " + (i + 1) * 10 + "%");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4. Service与Activity间的通信

4.1 通过Binder实现通信

java 复制代码
public class LocalService extends Service {
    private final IBinder binder = new LocalBinder();
    private int count = 0;
    
    public class LocalBinder extends Binder {
        LocalService getService() {
            return LocalService.this;
        }
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
    
    public int getCount() {
        return count;
    }
    
    public void incrementCount() {
        count++;
    }
}

Activity中使用:

java 复制代码
public class MainActivity extends AppCompatActivity {
    private LocalService localService;
    private boolean isBound = false;
    
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
            localService = binder.getService();
            isBound = true;
        }
        
        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false;
        }
    };
    
    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
    
    @Override
    protected void onStop() {
        super.onStop();
        if (isBound) {
            unbindService(serviceConnection);
            isBound = false;
        }
    }
    
    public void onButtonClick(View view) {
        if (isBound) {
            localService.incrementCount();
            int count = localService.getCount();
            Log.d(TAG, "Current count: " + count);
        }
    }
}

4.2 通过广播实现通信

java 复制代码
// Service中发送广播
public class BroadcastService extends Service {
    private static final String ACTION_UPDATE = "com.example.UPDATE";
    
    private void sendUpdateBroadcast(String data) {
        Intent intent = new Intent(ACTION_UPDATE);
        intent.putExtra("data", data);
        sendBroadcast(intent);
    }
}

// Activity中接收广播
public class MainActivity extends AppCompatActivity {
    private BroadcastReceiver updateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String data = intent.getStringExtra("data");
            // 更新UI
            updateUI(data);
        }
    };
    
    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter filter = new IntentFilter("com.example.UPDATE");
        registerReceiver(updateReceiver, filter);
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(updateReceiver);
    }
}

5. Service的高级应用

5.1 前台Service

前台Service具有更高的优先级,不容易被系统杀死:

java 复制代码
public class ForegroundService extends Service {
    private static final int NOTIFICATION_ID = 1;
    private static final String CHANNEL_ID = "ForegroundServiceChannel";
    
    @Override
    public void onCreate() {
        super.onCreate();
        createNotificationChannel();
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        createNotification();
        // 执行后台任务
        return START_STICKY;
    }
    
    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                "Foreground Service Channel",
                NotificationManager.IMPORTANCE_LOW
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }
    
    private void createNotification() {
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("前台服务运行中")
            .setContentText("正在执行后台任务...")
            .setSmallIcon(R.drawable.ic_service)
            .build();
        
        startForeground(NOTIFICATION_ID, notification);
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

5.2 跨进程Service (AIDL)

定义AIDL接口:

aidl 复制代码
// IRemoteService.aidl
interface IRemoteService {
    int add(int a, int b);
    String getMessage();
}

实现远程Service:

java 复制代码
public class RemoteService extends Service {
    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        @Override
        public int add(int a, int b) throws RemoteException {
            return a + b;
        }
        
        @Override
        public String getMessage() throws RemoteException {
            return "Hello from remote service";
        }
    };
    
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

客户端调用:

java 复制代码
public class ClientActivity extends AppCompatActivity {
    private IRemoteService remoteService;
    
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            remoteService = IRemoteService.Stub.asInterface(service);
        }
        
        @Override
        public void onServiceDisconnected(ComponentName name) {
            remoteService = null;
        }
    };
    
    private void callRemoteService() {
        if (remoteService != null) {
            try {
                int result = remoteService.add(5, 3);
                String message = remoteService.getMessage();
                Log.d(TAG, "Result: " + result + ", Message: " + message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

6. Service生命周期管理

Service作为Android四大组件之一,具有一系列生命周期回调函数,用于监测状态变化并执行相应工作。

6.1生命周期方法说明

方法名 调用时机
onCreate() 首次创建服务时调用。如果服务已在运行,不会调用此方法。
onStartCommand() 通过startService()启动服务时调用。
onDestroy() 服务销毁时调用。
onBind() 通过bindService()绑定服务时调用。
onUnbind() 通过unbindService()解绑服务时调用。
onRebind() onUnbind()返回true后,重新绑定时调用。
生命周期调用流程
调用方式 生命周期方法调用顺序
启动Service startService() → onCreate() → onStartCommand()
停止Service stopService() → onDestroy()
绑定Service bindService() → onCreate() → onBind()
解绑Service unbindService() → onUnbind() → onDestroy()
重新绑定Service bindService() → onRebind()
示例代码
java 复制代码
public class LifecycleService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "Service Created");
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "Service Started");
        return START_STICKY;
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "Service Bound");
        return null;
    }
    
    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "Service Unbound");
        return true; // 允许onRebind
    }
    
    @Override
    public void onRebind(Intent intent) {
        Log.d(TAG, "Service Rebound");
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Service Destroyed");
    }
}

6.2 Service最佳实践

性能优化
  • 避免在Service中执行耗时操作,使用子线程
  • 及时停止不需要的Service
  • 使用IntentService处理一次性任务
  • 合理使用前台Service
内存管理
  • 避免内存泄漏,及时释放资源
  • 在onDestroy()中清理线程和监听器
  • 使用弱引用避免循环引用
权限配置
xml 复制代码
<!-- 前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- 唤醒锁权限 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

6.3总结

  • 被启动服务的生命周期: 通过startService()启动,onCreate()调用一次,onStartCommand()可多次调用,服务后台运行直到stopService()或stopSelf()。
  • 被绑定服务的生命周期: 通过bindService()启动,onStartCommand()不调用,服务运行直到unbindService()或Context销毁。
  • 混合模式: 先启动后绑定,需分别调用stopService()和unbindService()来停止。
  • 在onDestroy()中清理资源,如停止线程。
  • 服务的优先级: 前台服务优先级高,系统内存不足时不会被kill。
  • 服务的权限: 配置合适的权限,避免安全问题。
实操Demo

以下是一个简单的音乐播放器示例,使用Service在后台播放音乐。Activity提供播放/暂停按钮,即使应用退到后台,音乐继续播放。

1. MusicService (后台音乐服务)
java 复制代码
// MusicService.java
public class MusicService extends Service {
    private static final String TAG = "MusicService";  // 日志标签
    private MediaPlayer mediaPlayer;  // 媒体播放器
    private static final int NOTIFICATION_ID = 1;  // 通知ID
    private static final String CHANNEL_ID = "MusicChannel";  // 通知通道ID

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "MusicService created");  // 服务创建
        mediaPlayer = MediaPlayer.create(this, R.raw.music);  // 初始化MediaPlayer,假设raw/music是音频文件
        createNotificationChannel();  // 创建通知通道(Android 8.0+)
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getStringExtra("action");  // 获取动作
        if ("play".equals(action)) {
            if (!mediaPlayer.isPlaying()) {
                mediaPlayer.start();  // 开始播放
                Log.d(TAG, "Music started");
            }
            startForeground(NOTIFICATION_ID, createNotification());  // 启动前台服务
        } else if ("pause".equals(action)) {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.pause();  // 暂停播放
                Log.d(TAG, "Music paused");
                stopForeground(true);  // 停止前台服务
            }
        } else if ("reset".equals(action)) {
            mediaPlayer.seekTo(0);  // 重置播放进度
            Log.d(TAG, "Music reset");
        }
        return START_STICKY;  // 服务被杀后自动重启
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null) {
            mediaPlayer.release();  // 释放资源
            mediaPlayer = null;
        }
        Log.d(TAG, "MusicService destroyed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;  // 不支持绑定
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    CHANNEL_ID,
                    "Music Service Channel",
                    NotificationManager.IMPORTANCE_LOW
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }

    private Notification createNotification() {
        return new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("音乐播放中")
                .setContentText("正在播放背景音乐")
                .setSmallIcon(R.drawable.ic_music)  // 假设有图标
                .build();
    }
}
2. MainActivity (用户界面)
java 复制代码
// MainActivity.java
public class MainActivity extends AppCompatActivity {
    private Button playButton;
    private Button pauseButton;
    private Button resetButton;
    private TextView progressText;
    private boolean isPlaying = false;  // 播放状态

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.day2_demo_activity_main);  // 假设布局文件

        playButton = findViewById(R.id.playButton);  // 播放按钮
        pauseButton = findViewById(R.id.pauseButton);  // 暂停按钮
        resetButton = findViewById(R.id.resetButton);  // 重置按钮
        progressText = findViewById(R.id.progressText);  // 显示播放进度

        playButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startMusicService("play");  // 启动播放
                isPlaying = true;
                updateButtonStates();
            }
        });

        pauseButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startMusicService("pause");  // 暂停播放
                isPlaying = false;
                updateButtonStates();
            }
        });

        resetButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startMusicService("reset");  // 重置播放
                isPlaying = false;
                updateButtonStates();
            }
        });

        updateButtonStates();  // 初始化按钮状态
    }

    private void startMusicService(String action) {
        Intent intent = new Intent(this, MusicService.class);
        intent.putExtra("action", action);  // 传递动作
        startService(intent);  // 启动服务
    }

    private void updateButtonStates() {
        playButton.setEnabled(!isPlaying);
        pauseButton.setEnabled(isPlaying);
        resetButton.setEnabled(true);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopService(new Intent(this, MusicService.class));  // 停止服务
    }
}
3. MainActivity.xml配置
xml 复制代码
// MainActivity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="24dp"
    tools:context="Day2_Demo.MainActivity">

    <TextView
        android:id="@+id/welcomeText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="欢迎使用音乐播放器"
        android:textColor="@android:color/black"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/playButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="播放"
        android:textSize="16sp"
        app:cornerRadius="8dp"
        app:layout_constraintTop_toBottomOf="@id/welcomeText"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/pauseButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="暂停"
        android:textSize="16sp"
        app:cornerRadius="8dp"
        app:layout_constraintTop_toBottomOf="@id/playButton"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/resetButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="重置"
        android:textSize="16sp"
        app:cornerRadius="8dp"
        app:layout_constraintTop_toBottomOf="@id/pauseButton"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <TextView
        android:id="@+id/progressText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="播放进度: 00:00"
        android:textColor="@android:color/black"
        android:textSize="16sp"
        app:layout_constraintTop_toBottomOf="@id/resetButton"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
4. AndroidManifest.xml 配置
xml 复制代码
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<service
    android:name=".MusicService"
    android:enabled="true"
    android:exported="false">
    <!-- 前台服务权限 -->
</service>
说明
  • Service使用START_STICKY确保被杀后重启。
  • 前台服务防止系统杀死,确保后台播放。
  • Activity退出后,Service继续运行音乐。
  • 需在res/raw添加音乐文件,并准备布局和图标。
相关推荐
鹏多多.44 分钟前
flutter-使用AnimatedDefaultTextStyle实现文本动画
android·前端·css·flutter·ios·html5·web
似霰2 小时前
安卓系统属性之androidboot.xxx转换成ro.boot.xxx
android·gitee
0wioiw02 小时前
Android-Kotlin基础(Jetpack①-ViewModel)
android
用户2018792831673 小时前
限定参数范围的注解之 "咖啡店定价" 的故事
android·java
xzkyd outpaper3 小时前
Android中视图测量、布局、绘制过程
android
泓博3 小时前
Android底部导航栏图标变黑色
android
包达叔3 小时前
使用 Tauri 开发 Android 应用:环境搭建与入门指南
android
初学者-Study3 小时前
Android UI(一)登录注册
android·ui
视觉CG3 小时前
【JS】扁平树数据转为树结构
android·java·javascript
深盾安全4 小时前
Android 安全编程:Kotlin 如何从语言层保障安全性
android