Service概念简介
Service 是 Android 系统中的一种组件,用于在后台执行长时间运行的操作或处理异步任务。Service 可以在不依赖于用户界面的情况下运行,并且可以在应用程序被关闭后继续运行。
分类
Service可以分为前台服务和后台服务,后台服务可以做到用户无感知,但是在内存吃紧的情况下容易被系统杀掉;前台服务的优先级比较高,内存吃紧的情况下系统也会尽可能保持其运行,但是前台服务必须要在通知栏让用户感知到,不能偷摸运行。
启动方式
Android Service 可以分为两种类型:Started Service(启动服务) 和 Bound Service(绑定服务)。
Started Service 是通过 startService() 方法启动的,它会在后台执行任务并在完成后停止。Bound Service 是通过 bindService() 方法绑定的,它与其他组件建立连接并可以进行通信。
启动服务:
这种服务开启后和调用者不存在绑定关系,调用者只是单纯开启这种服务,并不能与其通信。并且两者的生命周期没有关联性。只有应用程序被杀,或者某个地方调用停止服务的方法,才会停止这种服务。
绑定服务:
这种服务开启后和调用者存在绑定关系。调用者不仅可以开启这种服务,并且可以调用服务里的方法,与其通信。这种服务的生命周期与调用者绑定,如果调用者销毁,服务也随之销毁。
生命周期
启动服务的生命周期:
onCreate->onStartCommand->onDestroy
绑定服务的生命周期:
onCreate->onBind->onUnbind->onDestroy
控制方法
方法 | 作用 | 系统自动调用方法 | 备注 |
startService | 开启服务 | onCreate,onStartCommand | 启动一个服务 |
stopService | 关闭服务 | onDestroy | 关闭一个启动的服务,如果一个服务又被开启又被绑定,需要stopService和unbindService都调用才能关闭该服务 |
bindService | 绑定服务 | onCreate,onBind | 启动&绑定某个服务,如果该服务已经启动就只执行绑定 |
unbindService | 解绑服务 | onUnbind,onDestroy | 关闭一个绑定的服务 |
生命周期的特殊设置
通过启动服务的方式可以设置该服务的生命周期
/**
* 每次启动服务(调用startService)时,onStartCommand方法都会被调用,因此我们可以通过该方法使用Intent给Service传递所需要的参数,然后在onStartCommand方法中处理的事件,最后根据需求选择不同的Flag返回值
*
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
* except {@link #START_STICKY_COMPATIBILITY}.
* @param flags Additional data about this start request.
* @param startId A unique integer representing this specific request to
* start. Use with {@link #stopSelfResult(int)}.
*
* @return *START_STICKY
* 当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
* * START_NOT_STICKY
* 当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
* * START_REDELIVER_INTENT
* 当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "启动服务", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
在onStartCommand里可以通过return的值影响服务被系统杀掉后可执行的不同操作。
前台服务
可以通过在某个服务onCreate时进行配置,使服务变成前台服务。
需要注意前台服务必须构建并赋值一个Notification给用户看,否则会崩溃。
@Override
public void onCreate() {
Toast.makeText(this, "创建服务", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onCreate");
super.onCreate();
startForeground();
}
private void startForeground() {
String channelId = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
channelId = createNotificationChannel("test.channel", "ForegroundService");
} else {
channelId = "";
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId);
Notification notification = builder.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.setContentTitle("我是主标题")
.setContentText("我是副标题")
.build();
Log.d(TAG, "startForeground");
startForeground(1, notification);
}
@RequiresApi(Build.VERSION_CODES.O)
private String createNotificationChannel(String channelId, String channelName) {
NotificationChannel chan = new NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_NONE);
chan.setLightColor(Color.BLUE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
service.createNotificationChannel(chan);
return channelId;
}
例程
package com.example.serviceapplication.service;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import com.example.serviceapplication.R;
public class TestService extends Service {
TaskBinder mBinder = new TaskBinder();
private final String TAG = "ZZHService";
@Override
public void onCreate() {
Toast.makeText(this, "创建服务", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onCreate");
super.onCreate();
startForeground();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(this, "绑定服务", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onBind");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Toast.makeText(this, "解除绑定服务", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onUnbind");
return true;
}
/**
* 每次启动服务(调用startService)时,onStartCommand方法都会被调用,因此我们可以通过该方法使用Intent给Service传递所需要的参数,然后在onStartCommand方法中处理的事件,最后根据需求选择不同的Flag返回值
*
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
* except {@link #START_STICKY_COMPATIBILITY}.
* @param flags Additional data about this start request.
* @param startId A unique integer representing this specific request to
* start. Use with {@link #stopSelfResult(int)}.
*
* @return *START_STICKY
* 当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
* * START_NOT_STICKY
* 当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
* * START_REDELIVER_INTENT
* 当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "启动服务", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Toast.makeText(this, "服务销毁", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onDestroy");
super.onDestroy();
}
public class TaskBinder extends Binder {
public TestService getService() {
return TestService.this;
}
public void work(String workTask) {
Toast.makeText(this.getService(), "服务正在工作 task = " + workTask, Toast.LENGTH_SHORT).show();
}
}
private void startForeground() {
String channelId = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
channelId = createNotificationChannel("test.channel", "ForegroundService");
} else {
channelId = "";
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId);
Notification notification = builder.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.setContentTitle("我是主标题")
.setContentText("我是副标题")
.build();
Log.d(TAG, "startForeground");
startForeground(1, notification);
}
@RequiresApi(Build.VERSION_CODES.O)
private String createNotificationChannel(String channelId, String channelName) {
NotificationChannel chan = new NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_NONE);
chan.setLightColor(Color.BLUE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
service.createNotificationChannel(chan);
return channelId;
}
}
package com.example.serviceapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.Button;
import com.example.serviceapplication.broadcast.TestBroadcast;
import com.example.serviceapplication.service.TestService;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "ZZHActivity";
Button mStartButton;
Button mStopButton;
Button mBindButton;
Button mUnbindButton;
Button mWorkButton;
TestService mService;
TestService.TaskBinder mBinder;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
/**
* 与服务器端交互的接口方法 绑定服务的时候被回调,在这个方法获取绑定Service传递过来的IBinder对象,
* @param name The concrete component name of the service that has
* been connected.
*
* @param service The IBinder of the Service's communication channel,
* which you can now make calls on.
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = (TestService.TaskBinder) service;
mService = mBinder.getService();
}
/**
* 当取消绑定的时候被回调。但正常情况下是不被调用的,它的调用时机是当Service服务被意外销毁时,
* 例如内存的资源不足时这个方法才被自动调用。
* @param name The concrete component name of the service whose
* connection has been lost.
*/
@Override
public void onServiceDisconnected(ComponentName name) {
mBinder = null;
mService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mStartButton = this.findViewById(R.id.start_btn);
mStopButton = this.findViewById(R.id.stop_btn);
mWorkButton = this.findViewById(R.id.work_btn);
mBindButton = this.findViewById(R.id.bind_btn);
mUnbindButton = this.findViewById(R.id.unbind_btn);
mSendMessageButton = this.findViewById(R.id.send_msg_btn);
mWorkButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mBinder != null) {
mBinder.work("测试");
}
}
});
mStartButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startService(new Intent(getApplicationContext(), TestService.class));
}
});
mStopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopService(new Intent(getApplicationContext(), TestService.class));
}
});
mBindButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), TestService.class);
// flags则是指定绑定时是否自动创建Service。0代表不自动创建、BIND_AUTO_CREATE则代表自动创建
bindService(intent, mServiceConnection, Service.BIND_AUTO_CREATE);
}
});
mUnbindButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mBinder = null;
mService = null;
unbindService(mServiceConnection);
}
});
}
}
Android14适配
在target34(Android14)适配中,所有的前台服务都需要指定自己的类型,这个类型由系统提供,指定的类型和时机服务的工作内容没有强制关系,约等于走个形式。
需要在manifest里修改:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ServiceApplication"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".service.TestService"
android:foregroundServiceType="mediaPlayback"
android:exported="false"
android:enabled="true"/>
</application>
</manifest>
改动点:
1.新增了一条权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
2.为前台服务指定了一个类型:
<service android:name=".service.TestService"
android:foregroundServiceType="mediaPlayback"
android:exported="false"
android:enabled="true"/>