Android:Service知识总结

谨以文章记录学习历程,如有错误还请指明。

前言

记得我的第一部手机是Nokia的,当时的Symbian系统相较于其他手机最突出的一点就是,Symbian系统支持后台功能,毕竟这使得我们可以一边听着音乐一边聊着qq,在当时这可是很酷的一件事,毕竟Symbian系统出现以前,只能用一部mp3听音乐,另外再拿一部手机打电话。

Android显然看到了这个发光点,自始至终就支持后台功能,IOS随着时间的推移也发现这个功能的重要性,在后续版本加入了后台功能。
本文就将针对这一炫酷的后台功能(Service)开始讲解,深入总结Service的方方面面的知识


简介

  • 服务(Service) 是Android实现程序后台运行的解决方案,是Android四大组件之一。
  • 适合于执行不需要和用户交互而且需要长期运行的任务。

生命周期

首先放上Google官方文档给出的生命周期示意图:
(单独使用startService()和单独使用bindService()时的生命周期)

Google 服务生命周期示意图

回调方法介绍

回调方法 说明
onCreate() 首次创建服务时调用,执行一次性设置程序(在调用 onStartCommand()onBind() 之前)。如果服务已在运行,则不会调用此方法。
onStartCommand() 当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果您实现此方法,则在服务工作完成后,需要由您通过调用 stopSelf()stopService() 来停止服务。(如果您只想提供绑定,则无需实现此方法。)
onDestroy() 当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用。
onBind() 当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。请务必实现此方法,但如果您并不希望允许绑定,则应返回 null
onUnbind() 解绑服务

特别事项

  1. onCreate()只调1次
  2. startService()调用次数=onStartCommand()调用次数。
  3. Service通过bindService()被绑定启动后,将一直运行,直到调用unBindService()或者调用bindService()Context被销毁(即 使用bindService()建立的连接断开)。

尽管Context销毁时,Service会自动停止,但只要调用bindService()进行绑定,仍然还是需要在某处调用unBindService()解除绑定

  1. onStartCommand()方法必须返回一个整数,描述系统在杀死服务后如何继续运行。

    • START_NOT_STICKY:
      不会重新创建服务,除非有未发送的intent。当应用程序可以简单地重新启动任何未完成的工作时,这是避免在不必要的情况下运行服务的最安全的选项。
    • START_STICKY
      重新创建服务并调用onStartCommand(),但不会再次送入上一个intent。相反除非有未发送完的启动服务的intent,否则使将用null intent调用onStartCommand()。这适用于不执行命令的媒体播放器(或类似的服务),但它们会持续运行并随时待命。
    • START_REDELIVER_INTENT
      重新创建服务并调用onStartCommand(),并将上一个intent交付给服务。任何未处理的intent都将依次传递。这适用于需要立即恢复工作的活跃服务,例如下载文件。
  2. 手机屏幕旋转时,Activity会销毁 & 重新创建,因此使用bindService()建立的连接会断开

startService()与bindService()混合使用时的生命周期:

我们给出两个例子:

  • startService() -> bindService()或者 bindService() -> startService(),这二者顺序仅仅影响onBind()onStartCommand()的顺序
  • 多次重复startService()bindService()也同样遵循上述 特别事项
  1. 按顺序1,2,3,4执行
    (1)startService():调用onCreate()->onStartCommand()
    (2)bindService():调用onBind()
    (3)stopService():没有调用onDestory()Service仍然在运行!
    (4)unbindService():调用onUnbind() -> onDestory(),此时Service关闭!

官方解释:若被停止的服务依然有ServiceConnection 与其绑定,则服务不能销毁,直至我们把所有ServiceConnection 解绑

  1. 将上述3,4调换
    (1)startService():调用onCreate()->onStartCommand()
    (2)bindService():调用onBind()
    (3)unbindService():调用onUnbind()Service仍然在运行!
    (4)stopService():调用onDestory(),此时Service关闭!

官方解释:
当所有ServiceConnection 解绑后,系统会自动销毁服务(不包括同时用startService() 启动的情况)。此时,我们不得不再调用一次stopService() 来销毁它

给出Google官方的图,方便直观的理解:


分类

按启动方式:

(上面生命周期已经介绍的很详细了,只简要概括)

类别 区别 应用场景
Started Service
(startService() 启动的服务)
停止服务使用stopService() 不需要与Service通信
服务长期运行
Bound Service
(bindService() 启动的服务)
停止服务使用unbindService() 需要与Service通信
混合服务Service
(startService() 同时也 bindService() 启动的服务)
停止服务应同时使用stopService(()unbindService() 需要与Service通信
服务长期运行

(官方的分类只有前两种,上面的混合服务Service只是我个人的看法,也是为了后续方便介绍如何使用)

按运行地点
类别 区别 优点 缺点 应用
本地服务(Local) 该服务依附在主进程上 节约了资源
通信方便不需要IPC,也不需要AIDL
主进程被Kill后,服务便会终止。 我们大部分写的Service就是这种服务,如音乐后台播放
远程服务(Remote) 该服务是独立的进程 有较高的灵活性,Activity所在进程被Kill的时候,该服务依然在运行 独立进程,占用一定资源
使用AIDL进行IPC麻烦
一些提供系统服务的Service,这种Service是常驻的。
按运行类型
类别 区别 应用
前台服务 通知栏显示Notification 当服务被终止的时候,通知栏的 Notification 也会消失,这样对于用户有一定的通知作用。常见的如音乐播放服务。
后台服务 默认的服务即为后台服务,用户不可见 当服务被终止的时候,用户是看不到效果的。某些不需要运行或终止提示的服务,如天气更新,日期同步,邮件同步等。

具体实现

实际上,在上述不同场景下的分类多多少少都有所重叠,比如:

  • Bound Service包括 本地(Binder) 和 远程服务(MessageAIDL)
  • Started Service/Bound Service都可以在位于前台,也都可以在后台

Demo地址

GitHub:DemoForService

1. Started Service

  • 继承Service
  • 复写onCreate() , onStartCommand() , onDestroy() , onBind() 方法
  • AndroidManifest.xml中注册
  • 通过startService() , stopService() 启动和停止服务
  • 前台通知只需要再合适的地方调用startForeground()Demo 通知栏适配Android8.0

下面以Demo具体说明:
MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = "MainActivity";


    private Button mButton1;
    private Button mButton2;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton1 = findViewById(R.id.button1);
        mButton2 = findViewById(R.id.button2);

        mButton1.setOnClickListener(this);
        mButton2.setOnClickListener(this);
     
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button1:
                //启动服务
                startService(new Intent(MainActivity.this,StartedService.class));              
                break;
            case R.id.button2:
                //停止服务
                stopService(new Intent(MainActivity.this,StartedService.class));
                break;
         }
    }
 }

StartedService.java

//继承Service类
public class StartedService extends Service {
    //TAG标记
    private static final String TAG = "StartedService";

    //复写onCreate()方法,输出一段内容
    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate: "+ TAG);
        super.onCreate();

    }

    //复写onStartCommand(方法),输出一段内容
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: "+ TAG);

        //适配Android 8.0的通知
        Intent notificationIntent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);

        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        //实例化通道channel
        final String ID = "channalID";
        final String name = "name";
        NotificationChannel channel = new NotificationChannel(ID,name, NotificationManager.IMPORTANCE_HIGH);
        //调用该方法之后,该channel的ID才能被通知使用
        manager.createNotificationChannel(channel);

        //添加建立好的通道的ID
        Notification notification = new NotificationCompat.Builder(this,ID)
                .setContentTitle("前台服务标题")
                .setContentText("前台服务内容")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true)
                .setChannelId(ID)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .build();

        //开启前台服务
        startForeground(1,notification);
        return super.onStartCommand(intent, flags, startId);
    }

    //复写onDestroy()方法,输出一段内容
    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: "+TAG);
        stopForeground(true);
        super.onDestroy();
    }

    //不需要通信,返回空即可
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

AndroidMenifest.xml


前台Service优先级较高,不会由于系统内存不足而被回收;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收前台Service优先级较高,不会由于系统内存不足而被回收;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收

Androidmanifest里Service的常见属性说明

属性 说明 备注
android:name Service的类名
android:label Service的名字 若不设置,默认为Service类名
android:icon Service的图标
android:permission 申明此Service的权限 有提供了该权限的应用才能控制或连接此服务
android:process 表示该服务是否在另一个进程中运行(远程服务) 不设置默认为本地服务
remote则设置成远程服务
android:enabled 系统默认启动 true:Service 将会默认被系统启动
不设置则默认为false
android:exported 该服务是否能够被其他应用程序所控制或连接 不设置默认此项为 false

结果展示

首次启动服务

再次启动服务

停止服务

前台服务

2. Bound Service

2.1 本地服务-Binder实现

  • 继承Service类,并新建子类继承Binder类,写入关联方法,创建实例
  • 调用方通过ServiceConnection实例与Service子类建立联系
  • 复写onCreate() , onBind() , onUnBind() , onDestroy()

下面给出部分Demo代码作介绍:

MainActivity.java

  • 在Activity通过调用MyBinder类中的public方法来实现Activity与Service的联系
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = "MainActivity";

    private LocalService.LocalBinder mBinder;
    private LocalService mLocalService;

    ...
    private Button mButton3;
    private Button mButton4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         ...
        mButton3 = findViewById(R.id.button3);
        mButton4 = findViewById(R.id.button4);

        ...
        mButton3.setOnClickListener(this);
        mButton4.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            ...
            //调用bindService()
            case R.id.button3:
                //这里传入BIND_AUTO_CREATE表示在Activity和Service建立关联后自动创建Service
                //这会使得LocalService中的onCreate()方法得到执行,但onStartCommand()方法不会执行
                bindService(new Intent(MainActivity.this,LocalService.class),mConnection,BIND_AUTO_CREATE);
                break;
            //调用unBindService()
            case R.id.button4:
                unbindService(mConnection);
                break;

        }
    }
    //创建ServiceConnection的匿名类
    private ServiceConnection mConnection = new ServiceConnection() {

        //在建立关联的时候调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            //得到MyBinder的实例,可以调用MyBinder中的方法
            mBinder = (LocalService.LocalBinder) service;
            //得到LocalService的实例,可以调用其中方法
            mLocalService = mBinder.getService();
            //调用MyBinder类中的方法
            mBinder.showBinderInfo();
            //调用LocalService服务中的方法
            mLocalService.showServiceInfo();
        }

        //注意:调用unBindService()方法时,不会调用该方法!!!
        //该方法只有在系统回收该服务时候才会调用!!!
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

}

LocalService

  • 在新建子类继承Service类,并新建一个子类继承自Binder类、写入与Activity关联需要的方法、创建实例
//继承Service类
public class LocalService extends Service {

    private static final String TAG = "LocalService";

    //创建LocalBinder实例
    private final LocalBinder mBinder = new LocalBinder();

    //新建LocalBinder类继承自Binder类
    public class LocalBinder extends Binder{
        //返回当前服务对象的方法
        LocalService getService(){
            return LocalService.this;
        }
        //LocalBinder中的公开方法,供建立联系的Activity访问
        void showBinderInfo(){
            Log.d(TAG, "showInfo: 我是来自Binder的方法");
        }
    }
    //复写方法
    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate: "+ TAG);
        super.onCreate();
    }

    //复写方法
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: "+TAG);
        return super.onStartCommand(intent, flags, startId);
    }

    //复写方法
    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    //复写方法
    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: "+TAG);
        super.onDestroy();
    }

    //返回当前LocalBinder实例
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {

        return mBinder;
    }

    //服务中的公开方法,供建立联系的Activity访问
    public void showServiceInfo(){
        Log.d(TAG, "showServiceInfo: 我是来自LocalService的方法");
    }
}

AndroidManifest.xml

 

结果展示

绑定启动服务

解绑服务

注意:多次解绑会报错(java.lang.IllegalArgumentException),原因大概是解绑之后,找不到该注册的服务。详细需要深入源码找原因。

2.2远程服务-IPC&AIDL

具体见文章:Android多线程基础解析


总结

  • 本文对Android中的服务Service的方方面面做出详细的总结。
  • 笔者水平有限,如有错漏,欢迎指正。
  • 接下来我也会将所学的知识分享出来,有兴趣可以继续关注whd_Alive的Android开发笔记

欢迎关注whd_Alive的

  • 不定期分享Android开发相关的技术干货,期待与你的交流,共勉。

你可能感兴趣的:(Android:Service知识总结)