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添加音乐文件,并准备布局和图标。
相关推荐
薛晓刚4 分钟前
MySQL的replace使用分析
android·adb
DengDongQi27 分钟前
Jetpack Compose 滚轮选择器
android
stevenzqzq28 分钟前
Android Studio Logcat 基础认知
android·ide·android studio·日志
代码不停36 分钟前
MySQL事务
android·数据库·mysql
朝花不迟暮41 分钟前
使用Android Studio生成apk,卡在Running Gradle task ‘assembleDebug...解决方法
android·ide·android studio
yngsqq1 小时前
使用VS(.NET MAUI)开发第一个安卓APP
android·.net
Android-Flutter1 小时前
android compose LazyVerticalGrid上下滚动的网格布局 使用
android·kotlin
Android-Flutter1 小时前
android compose LazyHorizontalGrid水平滚动的网格布局 使用
android·kotlin
千里马-horse1 小时前
RK3399E Android 11 将自己的库放到系统库方法
android·so·设置系统库
美狐美颜sdk1 小时前
Android直播美颜SDK:选择指南与开发方案
android·人工智能·计算机视觉·第三方美颜sdk·视频美颜sdk·人脸美型sdk