Service作为安卓的四大组件之一作用非凡,主要来执行一些后台任务。一些需要长期运行的任务、甚至是app退出后还需要执行的任务都离不开Service。这里节就复习回顾下这个重要的组件。
服务是一种应用程序组件,安卓四大组件之一,表示应用程序希望在不与用户交互的情况下执行较长时间运行的操作,或者为其他应用程序提供使用的功能。和Activity不同,Service是一个可以长期运行在后台而没有界面的控件。用于执行一些长期耗时任务(是不是想起了Thread嘿嘿先留个坑)。
1、服务并不是一个单独的进程。Service对象本身并不意味着它运行在自己的进程中; 除非另有说明,否则它将在与其所属的应用程序相同的进程中运行。默认为主进程中。
2、服务不是一个线程。它本身也是默认运行在主线程中的。
和activity一样,service也有自己的生命周期,我们可以先简单了解下。
左:start方式
右:bind方式
(1)编译器快速创建
和activity一样编译器提供了快速创建方式。在相应的包上:new - service即可。之后编译器会自动帮我们在清manifest文件注册service,不用我们手动去注册。
(2)手动创建
1、创建类继承Service
2、manifest文件注册下
总的来说创建方式和activity类似,因为同样为四大组件嘛,所以相似点还是很多的。
这种方式的启动是比较简单的,通过上面的周期图我们也可以看出其生命周期的过程:
onCreate --> onStartCommand --> onDestory
(1)简单实践
-------------------------MyService.java---------------------------
public class MyService extends Service {
private static final String TAG = "23333";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
super.onDestroy();
}
// bind服务时用的方法
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
-------------- MainActivity.java ----------------------------------
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent = new Intent(this, MyService.class);
}
public void startService(View view) {
startService(intent);
}
public void stopService(View view) {
stopService(intent);
}
点击startService两次、stopService一次结果如上图:
1、onCreate方法和activity的onCreate方法一样整个生命周期只会执行一次,表示服务开始创建。
2、onStartCommand 表示开启服务,这个方法在你每次调用startService(intent)方法时触发
3、onDestroy表示服务销毁,这个方法的生命周期想必我们已经猜到了,执行一次。当你调用 stopService(intent)时触发。或者我们在MyService.java中调用 stopSelf()时触发。
(2)start方式开启服务特点
1、通过start方式开启的Service,Service和开启service的组件之间几乎没啥联系,只是在适当的时候我们start一下,然后任务完成后我们stop一下即可。
2、start方式开启的Service,Service便会一直运行在后台(activity销毁也不会影响Service的运行,只要app没被销毁。更确切的说是服务所在的进程没被杀死。)除非我们手动stopService、或者系统内存不足杀死Service所在的进程。
(3)onStartCommand 方法的返回值
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
1、此方法由系统调用。每次客户端通过startService开启服务后,这个方法便由系统回调。我们不要直接调用此方法。
2、常见返回值:
- public static final int START_STICKY_COMPATIBILITY = 0:
START_STICKY返回值的兼容性版本,但是不能确保服务被再次开启,当服务被杀死后。- public static final int START_STICKY = 1:
服务所在系统进程在 onStartCommand 方法执行后(返回值为START_STICKY )被杀死。系统会重新开启服务。但是系统不会使用你上次开启服务的intent,而是使用个空intent对象开启服务。这种模式对 用户确定开启并关闭任意一段时间 这种场景是适用的。例如 后台播放音乐。- public static final int START_NOT_STICKY = 2:
服务所在系统进程在 onStartCommand 方法执行后(返回值为START_NOT_STICKY )被杀死。系统不会重新开启服务。除非用户再次调用startService开启。- public static final int START_REDELIVER_INTENT = 3:
服务所在系统进程在 onStartCommand 方法执行后(返回值为START_REDELIVER_INTENT )被杀死。系统会重新开启服务。并且使用最近一次开启服务的intent对象。这适用于主动执行应该立即恢复的作业的服务。例如下载文件。
通过总结startService的方式我们了解到这种方式Activity和Service之间几乎没啥联系,当需要Activity和Service之间进行信息交流时,更贴近实际情况来说,当我们想在Activity中调用Service的方法时该怎么办呢?这时就需要bindService方式啦。。。
(1)bind方式
public interface MusicManager {
void playMusic();
void pauseMusic();
void stopMusic();
}
public class MusicService extends Service {
private static final String TAG = MusicService.class.getSimpleName();
public MusicService() {
Log.i(TAG, "MusicService:");
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return new MyBind();
}
private class MyBind extends Binder implements MusicManager {
@Override
public void playMusic() {
Log.i(TAG, "playMusic: ");
}
@Override
public void pauseMusic() {
Log.i(TAG, "pauseMusic: ");
}
@Override
public void stopMusic() {
Log.i(TAG, "stopMusic: ");
}
}
}
public class MainActivity extends AppCompatActivity {
private MusicManager musicManager;
private ServiceConnection conn;
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent = new Intent(this, MusicService.class);
conn = new MyServiceConnection();
}
public void bindService(View view) {
bindService(intent, conn, BIND_AUTO_CREATE);
}
public void unBindService(View view) {
unbindService(conn);
}
public void playMusic(View view) {
if (null != musicManager){
musicManager.playMusic();
}
}
public void pauseMusic(View view) {
if (null != musicManager){
musicManager.pauseMusic();
}
}
public void stopMusic(View view) {
if (null != musicManager){
musicManager.stopMusic();
}
}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
musicManager = (MusicManager) iBinder;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
}
}
public boolean bindService(Intent service, ServiceConnection conn, int flags):
1、intent对象
2、ServiceConnection 接口实现类对象,实现接口写回调方法即可。
3、Flag。其实flag我们一般使用BIND_AUTO_CREATE即可,表示Service不存在时创建。
ps:服务的启动是异步的,如上栗子如果我们直接在onCreate中通过回调获得IBinder对象直接操作播放、暂停、停止方法会可能报空指针。
(3)操作
1、点击按钮绑定服务后,回调方法如下:
2、再次点击绑定无效果
1、点击解绑按钮效果如下:
2、再次点击解绑,activity直接崩溃,只能解绑一次。
1、服务的onBinder方法必须要有返回值对象,否则onServiceConnected回调方法不会执行的
2、第二次点击绑定按钮 ,绑定服务是无响应的 只能绑定一次。
3、当Activity销毁的时候 要在ondestory()中解绑服务 ,否则销毁Activity使自动解绑(不求同时生 但求同时死)
4、服务的多次解绑会报异常 不要多次点击解绑
5、通过bind方式开启的服务在开发者选项的正在运行的服务中是看不到服务的。
需求 :既想让服务在后台长期运行 又想操作服务内部的方法解决:以混合的方式开启服务(两种开启服务的方法都需要调用)
开启服务的思路:
1、 先调用startService 方法开启服务(保证服务在后台长期运行)
2、 调用bindService 绑定服务
关闭时:
1、 先调用unbindService(此时服务不会真正解绑)
2、 再调用stopService 解绑服务
ps:
1、一般音乐播放器常用这种方式,为了进行保活有时还会把service变成前台进程。
2、这种方式开启onCreate 方法只会执行一次,startService 调用多少次,onStartCommand 就会执行多少次。
Thread 是程序执行的最小单元,它是分配CPU的基本单位,android系统中UI线程也是线程的一种,当然Thread还可以用于执行一些耗时异步的操作。
Service是Android的一种机制,服务是运行在主线程上的,它是由系统进程托管。它与其他组件之间的通信类似于client和server,是一种轻量级的IPC通信,这种通信的载体是binder,它是在linux层交换信息的一种IPC,而所谓的Service后台任务只不过是指没有UI的组件罢了。
在android系统中,线程一般指的是工作线程(即后台线程),而主线程是一种特殊的工作线程,它负责将事件分派给相应的用户界面小工具,如绘图事件及事件响应,因此为了保证应用 UI 的响应能力主线程上不可执行耗时操作。如果执行的操作不能很快完成,则应确保它们在单独的工作线程执行。
Service 则是android系统中的组件,一般情况下它运行于主线程中,因此在Service中是不可以执行耗时操作的,否则系统会报ANR异常,之所以称Service为后台服务,大部分原因是它本身没有UI,用户无法感知(当然也可以利用某些手段让用户知道),但如果需要让Service执行耗时任务,可在Service中开启单独线程去执行
当要执行耗时的网络或者数据库查询以及其他阻塞UI线程或密集使用CPU的任务时,都应该使用工作线程(Thread),这样才能保证UI线程不被占用而影响用户体验。
在应用程序中,如果需要长时间的在后台运行,而且不需要交互的情况下,使用服务。比如播放音乐,通过Service+Notification方式在后台执行同时在通知栏显示着
在服务中开启一个通知栏即可,注意点如下:
1、添加 FOREGROUND_SERVICE 权限,
2、在 onStartCommand 中必须要调用 startForeground 构造一个通知栏,不然 ANR
3、前台服务只能是startService,不能是bindService
//MyService.java
public void onCreate() {
super.onCreate();
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 通知渠道的id
String id = "my_channel_01";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel mChannel = new NotificationChannel(id, "我是渠道", importance);
mNotificationManager.createNotificationChannel(mChannel);
String CHANNEL_ID = "my_channel_01"; //渠道id
Notification notification = new Notification.Builder(this, CHANNEL_ID)
.setContentTitle("我是标题").setContentText("我是内容")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.build();
startForeground(1, notification);
Log.i(TAG, "onCreate: ");
}
1、实现原理:
a、需要两个前台服务A、B
b、A服务开启,然后在A服务中开启个通知栏
c、B服务开启,在B服务中开启通知栏,然后立即关闭B服务。
ps:两个服务公用一个Notification ID2、说明:这个漏洞在 android7.1已被google修复。
服务不仅可以在同进程之间进行信息交流,还可以在不同的进程间进行IPC。使用服务进行ipc典型使用就是aidl啦,其实IPC的方式也有很多。这里就结合服务相关介绍个典型的方式吧:
Android IPC-AIDl使用详解
这里涉及到系统源码分析,等以后有能力了在总结。。。
“温故而知新”知识点都是在回顾中产生新的收获,于是就简单的总结下。服务作为四大组件之一还是很重要的,相信在以后的使用中会逐渐加深理解。