Android Service 基础知识点
- Service的简单概述
- Service的分类
- Service的生命周期
概念 什么是Service?
Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行
Service的分类
- 按启动方式分类:通过Context.startService()或Context.bindService启动
主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService
当应用组件(如 Activity)通过调用 startService()
启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。
该方法启动的服务要进行通信。停止服务使用unbindService
当应用组件通过调用 bindService()
绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
- 按寄存方式分类:本地服务,远程服务
本地服务(Local) 该服务依附在主进程上
服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local服务因为是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。主进程被Kill后,服务便会终止。
远程服务(Remote)该服务是独立的进程
服务为独立的进程,对应进程名格式为所在包名加上你指定的android:process=remote字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。例:一些提供系统服务的Service,这种Service是常驻的。
- 按运行方式分类:前台服务,后台服务
前台服务 会在通知一栏显示 ONGOING 的 Notification
当服务被终止的时候,通知一栏的 Notification 也会消失,这样对于用户有一定的通知作用。前台Service在下拉通知栏有一条显示通知(主要做一些需要用户知道的事,但退出APP还能继续做,像音乐播放器类似的应用,在下拉栏有一些当前播放歌曲的信息和进行一些简单操作;有些下载功能放在Service里也会在下拉栏显示当前下载进度);前台Service优先级较高,不会由于系统内存不足而被回收。
后台服务 默认的服务即为后台服务,即不会在通知一栏显示 ONGOING 的 Notification。
当服务被终止的时候,用户是看不到效果的。某些不需要运行或终止提示的服务,如天气更新,日期同步,邮件同步等;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收
Service生命周期
按启动方式 startService和bindService 实例如下
1. startService 启动状态方法的调用顺序
自定义Service 加上对应的打印日志
public class MyService extends Service {
private String TAG = "MyService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG,"onBind");
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy");
}
}
在MainActivity中调用如下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.bind).setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, MyService.class);
startService(intent);
});
findViewById(R.id.bind).setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, MyService.class);
stopService(intent);
});
}
}
多次点击start启动startService 最后点击stop停止stopService 打印日志如下:
2021-06-07 18:07:43.374 2219-2219 E/MyService: onCreate
2021-06-07 18:07:43.375 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:53.286 2219-2219 E/MyService: onDestroy
2021-06-07 18:07:54.362 2219-2219 E/MyService: onCreate
2021-06-07 18:07:54.362 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:55.715 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:56.035 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:56.215 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:57.306 2219-2219 E/MyService: onDestroy
总结如下
- 一个Service是可以多次启动的,但是onCreate只会回调一次
- 多次启动时 onStartCommand是启动一次就回调一次
- 开启服务可以多次,停止服务只能一次 stopService后Service.onDestroy
onStartCommand 是带有返回值的return super.onStartCommand(intent, flags, startId);,并且是我们开发者可以控制返回什么值。我们知道当系统内存是有限的,当系统内存资源不足,Service是会被销毁的,如果你在Service里做了什么重要事情,那被销毁显然是你不愿意看到的,所以要有一种方法让系统帮我们重启该服务,那要不要重启就由这个返回值 startId 决定了
- START_STICKY:如果service进程被kill掉,系统会尝试重新创建Service,如果在此期间没有任何启动命令被传递到Service,那么参数intent将为null。
- START_NOT_STICKY:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统不会自动重启该服务。
- START_REDELIVER_INTENT:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统会自动重启该服务,并将intent的值传入。
- START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
如果把返回值设置成START_STICKY和START_REDELIVER_INTENT,这样就可以处于不死状态了(这样做其实是不好的)还有一种情况是用户主动在设置界面把你的Service给杀死,但是这会回调onDestroy,你可以在这里发送广播重新启动。
onStartCommand()第二个输入参数flags正是代表此次onStartCommand()方法的启动方式,正常启动时,flags默认为0,被kill后重新启动,参数分为以下两种:
- START_FLAG_RETRY:代表service被kill后重新启动,由于上次返回值为START_STICKY,所以参数 intent 为null
- START_FLAG_REDELIVERY:代表service被kill后重新启动,由于上次返回值为START_REDELIVER_INTENT,所以带输入参数intent
服务必须在AndroidManifest.xml里注册
如果以这种方式启动服务,服务就会在后台无限期运行,即使启动的Activity被销毁了,也不会对Service有任何影响;除非手动调用stopService,服务才会停止
2.bindService启动状态方法的调用顺序
在MainActivity中代码示例如下:
public class MainActivity extends AppCompatActivity {
private final String TAG ="MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnBind = findViewById(R.id.bind);
Button btnUnBind = findViewById(R.id.unBind);
btnBind.setOnClickListener(v -> {
btnBind.setText("绑定成功");
btnUnBind.setText("解绑服务");
Intent intent = new Intent(this, MyService.class);
bindService(intent,connection,BIND_AUTO_CREATE);
});
btnUnBind.setOnClickListener(v -> {
btnBind.setText("绑定服务");
btnUnBind.setText("解绑成功");
unbindService(connection);
});
}
private final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG,"onServiceConnected");
MyBind myBind = (MyBind) service;
//这里就可以调用Service类的方法了
myBind.startDownload();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG,"onServiceDisconnected");
}
};
@Override
protected void onDestroy() {
super.onDestroy();
}
}
Bind的代码示例如下:
public class MyBind extends Binder {
private final String TAG = "MyBind";
public void startDownload(){
Log.e(TAG,"startDownload");
downLoadMusic();
}
private void downLoadMusic(){
Log.e(TAG,"downLoadMusic...");
}
}
Service的代码示例如下:
public class MyService extends Service {
private final String TAG = "MyService";
private MyBind myBind;
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate");
myBind = new MyBind();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG,"onBind");
return myBind;
}
@Override
public void onRebind(Intent intent) {
Log.e(TAG,"onRebind");
super.onRebind(intent);
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG,"onUnbind");
return super.onUnbind(intent);
}
}
多次点击bindService绑定服务,点击unBindService接绑服务,打印日志如下:
2021-06-07 22:17:46.710 1625-1625 E/MyService: onCreate
2021-06-07 22:17:46.713 1625-1625 E/MyService: onBind
2021-06-07 22:17:46.725 1625-1625 E/MainActivity: onServiceConnected
2021-06-07 22:17:46.725 1625-1625 E/MyBind: startDownload
2021-06-07 22:17:46.725 1625-1625 E/MyBind: downLoadMusic...
2021-06-07 22:17:49.774 1625-1625 E/MyService: onUnbind
2021-06-07 22:17:49.775 1625-1625 E/MyService: onDestroy
总结如下
- 服务一绑定,会执行onCreate,再回调onBind,最后会走到ServiceConnection类的onServiceConnected中
- bindService只会绑定一次,不会多次绑定
- 如果想打印onRebind()的话,前提是onUnbind()方法返回值为true,但是如果使用默认的 super.onUnbind(intent)是不行的
注意点
- 不管是startService还是bindService,最后都要stopService和unBindService与之对应,要不然容易造成内存泄漏
- bindService后调用unBindService成功后,如果再调用unBindService将会报异常(java.lang.IllegalArgumentException: Service not registered),可以加一个boolean变量判断。
- 这里有一种特殊情况是先startService,此时回调onCreate->onStartCommand,然后再bindService,回调onBind->onServiceConnected;这时候要想销毁Service必须调用unBindService,再调用stopService。
- bindService成功后,Service就与当前Activity绑定了,它就跟随Activity生命周期走了,如果服务没有销毁,而与之绑定的Activity销毁了,那这个绑定的Service也会被销毁,但是Service里启动的子线程不会被销毁。
- 通过startService启动服务后,服务就与启动它的组件没有联系了,组件销毁了,服务还是继续运行;通过bindService启动服务,与之绑定的组件就可以控制Service的具体执行逻辑了
按Service运行方式分两类:前台服务和后台服务 实例如下
前台服务
MyService中的实例代码如下:
public class MyService extends Service {
private static final int REQUEST_CODE = 1;
private final String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate");
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,REQUEST_CODE,intent,0);
Notification.Builder builder = new Notification.Builder(getApplicationContext());
builder.setContentIntent(pendingIntent)
.setContentTitle("我是前台服务")
.setContentText("这里是要显示的内容")
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher))
.setSmallIcon(R.mipmap.ic_launcher)
.setContentInfo("前台信息")
.setWhen(System.currentTimeMillis());
Notification build = builder.build();
build.defaults = Notification.DEFAULT_SOUND;
startForeground(123,build);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
stopForeground(true);
super.onDestroy();
Log.e(TAG,"onDestroy");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG,"onBind");
return null;
}
}
在MainActivity只需要开启服务就可以了,代码如下:
Intent intent = new Intent(MainActivity.this, MyService.class);
startService(intent);
总结
- 获取PendingIntent对象,至于PendIntent和Intent的区别,以及应用场景,以后会出文讲解一番
- 获取Notification.Builder利于Buidler模式,设置标题,内容,图标等
- Notification.DEFAULT_SOUND设置默认铃声
- startForeground(123,build)让Service变成前台Service,并在系统的状态栏显示出来
后台服务
后台服务默认的就是后台服务,这里就不在赘述了
后续将继续讲解IntentService的由来,以及应用的场景和对应的源码分析
结语:感谢各位大佬们的分享,浏览的用的东西多了,反而会很杂,所以需要梳理记录整理一下,如有错误的需要改进的地方,请留言评论指出,谢谢!
参考文章:
- https://blog.csdn.net/javazejian/article/details/52709857
- https://blog.csdn.net/lingyun_blog/article/details/41518589
- https://blog.csdn.net/qq_30993595/article/details/78452064