学习笔记之简析安卓Android服务Service

前言

平时的开发中,我们用的最多就是在Activity中进行开发。当我们需要处理一个耗时的逻辑的时候,为防止ANR我们会开启一个子线程来执行,例如网络请求等。但是当我们需要一个逻辑在后台持续运行,并且能和多个活动进行通信,例如后台播放器,子线程貌似就显得无能为力了。这个时候就需要用到服务了。顾名思义,他作为后台逻辑,为前台的Activity服务。接下来看看什么是服务。

什么是service?

简介

服务Service,是Android四大组件之一。服务顾名思义就是用于为前台界面服务的一个组件。当我们把应用放到后台,微信还是可以接受信息,歌曲还是可以播放,这个就是服务完成的。服务用的最多也是后台工作。和作为四大组件的Activity一样,Service也是拥有类似的启动模式,生命周期等等。

服务分类

服务分为前台服务和后台服务两种:前台服务就像平时我们播放音乐的时候通知栏会有一个常驻的通知,然后后台播放歌曲,那个就是前台服务;
后台服务一般我们感受不到,他在后台默默为前台服务,提供数据运算等等。例如当我们使用微信返回桌面的时候,还是可以接受信息,这个就是服务的作用。

代码简析

先看看Service的代码:

public class MyService extends Service {
    //构造器。一定要有一个空参数的构造器,不然无法启动成功
    public MyService() {
    }

    //服务被创建的时候会调用。和Activity类似
    @Override
    public void onCreate() {
        super.onCreate();
    }

    //服务启动的时候会调用
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
    
    //绑定服务的时候会调用这个方法,并返回一个IBinder对象,这里用了内部类的方法
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
    //内部类,在这里可以写方法供给绑定的活动使用。Binder是实现IBinder接口的类
    class MyBinder extends Binder {

    }
}

可以见到只需要建立一个类,并继承service类,即可创建服务。服务作为四大组件之一,和活动也是一样有类似的生命周期。相关的方法我在代码中注释了,看不太懂也没关系,主要先接触一下服务。然后作为四大组件之一,当然也是需要注册的:

<service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"/>

第一参数表示能否被启动,第二个参数表示能否被隐式启动。

与线程的区别

很多读者可能会有一个误解,就是以为服务和活动不是在同一个线程,直接在服务中执行耗时任务,然后就会发现出现了ANR。服务不就是在后台的吗?实际上,服务是运行在主线程的,他只是整个类在前台界面退出到桌面甚至被销毁,他依然可以存在并运行,但是如果要执行耗时逻辑,则必须在服务中开启一个子线程运行。

启动服务的两种方式

启动服务和启动Activity是类似,都是通过Intent来启动对应的Service。有显式和隐式两种。这里主要介绍显式启动。除此之外服务还有绑定这一种启动方式。来看一下吧:

直接启动

Intent intent = new Intent(this,MyService.class);
startService(intent);

通过代码可以看到直接启动一个服务和直接启动一个活动几乎是一模一样的。区别就是调用的方法不同这也可以理解,启动活动是startActivity,那启动服务肯定就是startService了。
调用这个方法之后服务就会被创建,然后依次调用服务的onCreate和onStartCommand两个方法。

这里需要强调一下就是每个服务只能被创建一次,也就onCreate方法只会执行一次。多次使用startService去启动同一个服务,只会重复执行onStartCommand方法,而不会再次执行onCreate方法,因为只能Service只能存在一个实例。

绑定服务

和启动活动不一样的是,服务他可以通过绑定来启动,这也容易理解,因为服务本来就是为前台活动而服务的那么和活动绑定也是为了更好的通信。服务与活动绑定之后两者的关系就更加的密切了,他们之间的通信业变得容易起来。先看看代码:

private MyService.MyBinder binder;

ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                binder = (MyService.MyBinder)service;
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };

Intent intent = new Intent(this,MyService.class);
bindService(intent, connection, BIND_AUTO_CREATE);

代码不长我们一个个来看。首先看到最后一行,可以看到绑定服务主要是执行bindService这个方法。这个方法传入三个参数:Intent,Connection,int。

Intent容易理解,和直接启动服务一样。最后的int类型参数,是一个flag标志,BIND_AUTO_CREATE的数值是1 ,表示绑定的时候顺带创建服务,并调用onCreate方法,一般情况下传入这个参数即可。
第二个参数Connection是一个ServiceConnection对象,这个对象可以通过匿名类来创建,主要重写两个方法。第一个方法是绑定成功之后要做的事情,会返回一个IBinder参数,这个IBinder参数就是服务中的onBinder方法返回的数据。通过这个IBinder就可以调用服务中的方法了。

另外,当一个活动已经绑定了这个服务,另外一个活动柜再次绑定这个服务的时候,不会再调用服务的onCreate方法,而是会把同一个IBinder参数再返回,也就是两个活动拿到同一个IBinder对象。

服务的生命周期

先看看服务的生命总周期:
在这里插入图片描述
中间那段其实是比较不严谨。先从头理清一下:
服务被启动或者绑定都会调用onCreate方法,然后如果是直接启动的话就会接着调用onStartCommand方法,如果是绑定的话就会调用onBinder方法。
多次启动的话会调用onStartCommand方法,不能进行多次绑定。多个活动绑定同个服务,会拿到同一个IBinder对象,也就是不会重复调用onCreate和onBinder方法。

当服务是直接启动的时候,在服务中调用stopSelf()或者调用stopService(Intent intent)来停止服务,这个时候服务就会调用onDestroy方法。
当服务是绑定启动的时候,调用unbindService(ServiceConnection connection)方法就可以解除绑定了。这里传入的参数就是前面我们绑定服务的connection。

重点来了,当服务被直接启动有被绑定时,需要同时调用两者的方法才能把服务停下来。

服务与活动之间的通信

当使用启动方法来启动服务后,貌似服务就与活动没什么联系,而事实上也确实是这样,那么如果需要服务不断地与活动进行通信,那怎么办呢,就用到绑定这种方法了。

前面讲到了,绑定会反回一个IBinder对象。我们可以在Service中写一个内部类来继承Binder(Binder是实现IBinder接口的类),然后在这个类中就可以写我们需要的方法。通过这个Binder对象就可以调用Service中的非private方法了,甚至把整个Service都返回也是可以的。这样就可以实现两者之间的通信了。

使用服务的重点之一:多线程

前面我们讲到,Service并不是运行在子线程的,而是运行在主线程,所以我们不能直接在服务中执行耗时的逻辑,不然就会产生ANR。因而每个耗时的逻辑都应该在子线程中执行。开启子线程可以参考下面的例子:

new Thread(new Runnable() {
            @Override
            public void run() {
             //这里写要执行的耗时逻辑
                }
            }
        }).start();

关于多线程编程这里不多做解析,只是强调一个问题:耗时逻辑一定要在子线程中运行,Service本身并不是运行在子线程中的,而是运行在主线程,这一点一定要记住。

IntentService

由于上面讲的子线程问题,很多时候会忘了开启线程,又或者在耗时逻辑结束后要关闭服务却忘了关。这个时候IntentService就出来,先看代码:

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {

    } 
}

这个IntentService和原来的Service类基本是差不多的,区别就在于两个:构造方法和onHandleIntent这个方法。构造方法要调用父类的构造器;onHandleIntent这个方法是默认执行在子线程的,不需要我们自己去开辟线程,只需要直接把耗时逻辑写在里面就行,而且执行完之后还会自动把服务关闭。除了这两个其他和普通的Service基本是一模一样的。

前台服务

关于前台服务,相信各位读者肯定是非常熟悉的。当我们播放音乐的时候,我们拉下通知栏,是不是有一个播放栏在那里,没错那个就是前台服务。
前台服务,顾明思义,就是把一个服务放到前台来,他不再是躲在背后了而是来到了前台,被用户所感知。大家终于知道后台有什么服务正在运行了,终于知道谁在为我们默默付出了。那接下来就来看看这个前台服务究竟怎么实现。

前台服务的作用

了解怎么使用之前先来看看他有什么作用,用一个东西前得先了解为什么要用它,有什么好处。

  1. 首先一个就是像音乐播放器一样的常驻通知栏,可以提供给用户一些简单的操作,例如上下曲。可以在不用切换到对应的app就可以操作后台服务,这是前台服务的一个很重要的作用。
  2. 第二个就是避免服务被回收。我们都知道服务一般是在后台的,用户看不见的,所以当内存不够的时候,后台的任务就会被回收。而当使用了前台服务的时候,服务就被用户所感知,就不会被回收了。

如何使用前台服务

前台服务的使用非常的简单,和使用通知Notification是差不多的。只是前台服务,叫做服务,所以必须依赖于服务而存在,不想通知那样随时随地可以发出。那接下来先看看简单的代码实现:

public class MyService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
		Intent intent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
        Notification notification = new NotificationCompat.Builder(this,"default_channel")
                .setSmallIcon(R.mipmap.ic_launcher)//设置小图标
                .setContentTitle("yinyue")
                .setContentText("qwer")
                .setContentIntent(pendingIntent)//设置点击intent
                .build();
        startForeground(1,notification);
        }
}

代码非常简单,和创建一条通知是差不多,如果你尚未知道如何去创建一条通知可以先去学习一下关于Notification的内容。这里就不展开叙述了。最主要的区别就是最后调用了startForeground这个方法来启用一个前台服务。
若要实现像音乐播放器那样的自定义标题栏,可以使用remoteView来实现。具体这里也不展开,感兴趣的读者可以自行了解。

远程服务

很多读者可能会对服务有个误解,就是服务本身是运行在子线程的,但事实上服务本身和活动一样都是运行在子线程的,在服务中的任何耗时任务都必须开启一个子线程来完成,否则就会出现ANR。那么有没有一种服务是本身就运行在与主线程不同的线程呢?答案是肯定的,也就有了远程服务的存在。不过远程服务并不是运行在与别的线程,而是运行在另一个进程。相当于是另一个应用的服务。还是一样,先来了解一下远程服务有什么用处。

远程服务的作用

  1. 不用开启子线程,可直接运行耗时任务。因为远程服务已经不运行在主线程了,甚至是不在该应用的进程中,所以直接运行耗时任务并不会造成ANR。
  2. 远程服务为进程共享。因为远程服务是独立的一个进程,相当于一个独立的应用,可以服务于其他的应用,所以,远程服务是可以被多个应用使用。所以当其他的应用需要用到这个服务的时候可以通过隐式启动的方式来使用这个服务。

如果定义一个远程服务

定义一个远程服务可不谓太简单。只需要在AndroidManifest中指定一个proccess(进程)即可,看代码:

<service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote"
             />

上面代码的最后一行为服务指定了一个新的进程,进程名字是包名+“:remote”

跨进程通信AIDL

由于服务和活动处在不同的进程了,这个时候我们就需要使用到跨进程通信AIDL了。名字看起来高大上,其实也不难,来看看怎么实现吧。

  1. 首先在项目目录的main文件下新建一个AIDL文件,然后就会自动生成一个接口文件,如下图:
    在这里插入图片描述
    打开之后会看到代码:
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

}
  1. 来解释一下这个AIDL文件。很明显这是一个接口文件。活动要使用远程服务就只能通过这个接口来使用远程服务中的方法。我们都知道常规的绑定服务是返回一个IBinder对象,远程服务返回的是这个接口的一个实现对象,所以只能使用这个接口里面的方法。因为我们需要在这个接口里定义我们需要用到的方法,然后在服务里面去实现这个接口。
    这里面有一个已经生成好的方法,意思是告诉我们哪些数据可以被当成参数。由于属于跨进程通信,所以只有能被序列化的数据才能传输。自定义对象要实现Parcelable接口,再给这个类定义一个同名的AIDL文件。感兴趣的读者可以自行了解,这里就不展开了。
    所以在这个接口文件中我们可以定义我们需要用到的方法,这里我增加了一个方法:(注意修改完了要build一下修改才会生效)
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    int getNum();


}

3.定义好接口之后就是实现这个接口了。我们前面是在服务中自定义了一个实现Binder的类,然后返回这个类的一个对象来给Activity使用。这里我们不用再去编写一个类,直接实现我们刚刚的那个接口即可。先来看看代码:

public class MyService extends Service {
  ...

    IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }


        @Override
        public int getNum() throws RemoteException {
            return getN();
        }
    };
    
	private int getN(){
        return 8;
    }
    ...
  }

相信代码不难看懂。但是可能会有读者会疑问这个Stub是什么鬼?其实这个Stub就是一个继承了IBinder实现了我们定义AIDL接口的抽象类。这也就是为什么我们可以直接给onBind方法返回,因为这个是继承了IBinder类的。
4.最后就是Activity中的代码了。Activity中的代码也和绑定普通服务差不多,先来看看代码吧:

private TextView textView;
private IMyAidlInterface binder;
void bindService(){
        ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                binder = IMyAidlInterface.Stub.asInterface(service) ;
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
    }

我们之前直接返回的是一个IBinder对象直接用,这里我们不能直接使用,这里返回的是一个IMyAidlInterface.Stub对象,所以我们要把它转化成IMyAidlInterface对象来使用里面的方法。Stub类中提供了一个方法来转化,这样我们拿到IMyAidlInterface对象之后,就可以调用服务中的方法了。剩下的就和普通的服务一样了。

远程服务小结

远程服务的使用一般用于一个服务提供给多个应用 使用的情况下,别的应用可以通过隐式启动来使用这个服务。达到一个服务给多个应用同时提供服务的功能。如果仅仅只是想执行耗时任务,在普通服务中开启子线程是为更好的解决方法。
跨进程通信传输的数据只有基础数据类型和Lish,Map等。而且无法传输引用,只能传输数据。所以只能把对象的属性传递过去而无法使用其中的方法。

参考资料

郭霖:《第一行代码》
Android Service完全解析,关于服务你所需知道的一切(上)
Android Service完全解析,关于服务你所需知道的一切(下)

猜你喜欢

转载自blog.csdn.net/weixin_43766753/article/details/102576940