概述
Service——android系统中的四大组件(Activity、 Service.、BroadcastReceiver,、ContentProvider) 之一, 它跟Activity的级别差不多,但它只能后台运行,并且不提供用户界面。
下面是API中对Service的描述:
服务是一种在后台运行的组件,用于执行长时间运行的操作或为远程进程执行作业。 服务不提供用户界面。 例如,当用户位于其他应用中时,服务可能在后台播放音乐或者通过网络获取数据,但不会阻断用户与 Activity 的交互。 诸如 Activity 等其他组件可以启动服务,让其运行或与其绑定以便与其进行交互。
图为Service的生命周期
从生命周期我们可以看到,Service有两种执行Service方法,一种是startService,一种是bindService。下面我们将分别进行讲解。
startService
实现
实现Service通常经过如下步骤:
- 创建一个类继承andorid.app.Service类,实现抽象方法,重写onStart和onDestory方法
- 在AndroidMainfest.xml中配置Service
下面开始实现
准备工作:两个Button,绑定onClick事件,这个之前重复了n+1次了,就不再提供代码了。
按照步骤,我们建立一个Service
打开配置清单文件,实际上IDE已经自动为我们注册了Service
接下来我们按照步骤实现抽象方法,重写Service里的两个方法,
需要注意在onStartCommand实现服务的核心业务
package com.example.a4_3service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
//服务被创建
@Override
public void onCreate() {
super.onCreate();
System.out.println("MyService Create.");
}
//在该方法中实现服务的核心业务
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
for (int i=0;i<50;i++){
System.out.println("onStartCommand:"+i);
}
return super.onStartCommand(intent, flags, startId);
}
//销毁服务
@Override
public void onDestroy() {
super.onDestroy();
System.out.println("MyService Destroy");
}
}
接下来实现之前写的两个button的onClick事件
我们已经学习了Intent,显然service的启动(切换)也是通过intent这个“中间人”来“间接”达成的。
package com.example.a4_3service;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//启动一个服务
public void start(View view){
Intent intent=new Intent(this,MyService.class);
startService(intent);
}
//停止一个服务
public void stop(View view){
Intent intent=new Intent(this,MyService.class);
stopService(intent);
}
}
点击启动服务
点击停止服务(看最后一行....)
需要注意的是,如果没有停止服务,服务处于启动状态,虽然退出了应用,服务依旧会在后台运行。
如下图:我们清空了所有正在运行的应用,打开设置观察服务运行情况,可以看到我们的4_3Service依旧在运行
所以接下来,我们来进行服务自动终止的实现。
服务自动终止的实现
实际上非常简单,之前提到了onStartCommand()里实现核心业务,所以我们修改这个部分,只需要使用stopSelf即可实现终止服务。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
for (int i=0;i<50;i++){
System.out.println("onStartCommand:"+i);
if(i==30){
this.stopSelf();//终止自己(终止服务)
}
}
return super.onStartCommand(intent, flags, startId);
}
让服务“慢”下来
线程休眠
之前接触过线程的概念,我们修改核心业务代码
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
for (int i=0;i<50;i++){
System.out.println("onStartCommand:"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==30){
this.stopSelf();//终止自己(终止服务)
}
}
return super.onStartCommand(intent, flags, startId);
}
然后会发现服务确实慢下来了,但是可以看到实际上整个Activity陷入“假死”的状态,在等待线程的完成
可以修改System.out语句,观察结果,可以看到当前的进程为main,证实了上面的结论。
即默认情况下服务与主线程在同一个线程中执行
System.out.println("onStartCommand:"+i+"-"+Thread.currentThread().getName());
显然这种方法还需要一定的改进,如果“慢”的服务可以在子线程中运行,是不是就解决了这个问题。
改进
//在该方法中实现服务的核心业务
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
/* for (int i=0;i<50;i++){
System.out.println("onStartCommand:"+i+"-"+Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==30){
this.stopSelf();//终止自己(终止服务)
}
}*/
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("onStartCommand:" + i + "-" + Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 30) {
MyService.this.stopSelf();//终止自己(终止服务)
}
}
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
可以看到,线程已经不再是main,而是Thread-4(不用在意-几...)
补充
前面提到了可以另起一个线程,如果想要让service在不同的进程怎么办呢?
可以通过修改配置清单文件实现,如下图所示。
android:process=":进程的名称"
onStartCommand()
onStartCommand使用时,返回的是一个(int)整型。
这个整型可以有以下四个返回值:
- START_ STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
- START_ NOT, STICKY :“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。
- START_ REDELIVER INTENT :重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
- START_ STICKY_ COMPATIBILITY :START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
附上一段API的描述,点击相关文字可以跳转到对应的API文档界面
请注意,
onStartCommand()
方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,IntentService
的默认实现将为您处理这种情况,不过您可以对其进行修改)。从onStartCommand()
返回的值必须是以下常量之一:如果系统在
onStartCommand()
返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。如果系统在
onStartCommand()
返回后终止服务,则会重建服务并调用onStartCommand()
,但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用onStartCommand()
。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。如果系统在
onStartCommand()
返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用onStartCommand()
。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
Intent Service
之前提到了,可以服务默认运行在主线程,而且服务不去主动停止会一直保持运行状态。
虽然相应的都提供了解决方案,但是每次都去做未免有些麻烦。
安卓中自带了一个Intent Service就是来解决上述问题的。
IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作, 启动 IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandlelntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。
实现
同样的,创建一个Service,提供intentService的选项,可以直接选那个。
ide依旧会帮我们注册好,如下:
与普通Service不同,exported属性在这里为false,exported属性表示是否支持其它应用调用当前组件,即intentService默认不支持其他应用调用该组件。
然后我们准备一个按钮和一个点击事件(因为intentService可以自动停止,所以不需要stop)
IntentService内部有一个工作线程来完成耗时的操作,我们只需实现onHandleIntent方法即可,完成工作后IntentService会自动停止服务当同时执行多个任务时,IntentService会以工作队列的方式,并依次执行。
实现代码:
package com.example.a4_3service;
import android.app.IntentService;
import android.content.Intent;
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
System.out.println(intent.getStringExtra("info"));
for(int i=0;i<50;i++) {
System.out.println("onHandleIntent-" + i + "-" + Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
启动一个intent服务,可以看到线程的名字并非main
切到服务管理器,发现虽然没有手动停止服务,但是服务在完成之后已经没有了。
bindService
概述
以上无论是intentService还是一开始最普通的Service,都是startService方法执行的,下面介绍bindService这种执行方法。
下面是API的描述:
绑定服务允许应用组件通过调用
bindService()
与其绑定,以便创建长期连接(通常不允许组件通过调用startService()
来启动它)。如需与 Activity 和其他应用组件中的服务进行交互,或者需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。
要创建绑定服务,必须实现
onBind()
回调方法以返回IBinder
,用于定义与服务通信的接口。然后,其他应用组件可以调用bindService()
来检索该接口,并开始对服务调用方法。服务只用于与其绑定的应用组件,因此如果没有组件绑定到服务,则系统会销毁服务(您不必按通过onStartCommand()
启动的服务那样来停止绑定服务)。要创建绑定服务,首先必须定义指定客户端如何与服务通信的接口。 服务与客户端之间的这个接口必须是
IBinder
的实现,并且服务必须从onBind()
回调方法返回它。一旦客户端收到IBinder
,即可开始通过该接口与服务进行交互。多个客户端可以同时绑定到服务。客户端完成与服务的交互后,会调用
unbindService()
取消绑定。一旦没有客户端绑定到该服务,系统就会销毁它。有多种方法实现绑定服务,其实现比启动服务更为复杂,因此绑定服务将在有关绑定服务的单独文档中专门讨论。
- 应用程序组件(客户端)通过调用bindService()方法绑定服务,然后系统会调用服务的onBind()回调方法,并返回一个跟服务端交互的IBinder对象
- 绑定服务是异步的,bindService()方法立即返回,并且不给客户端返回IBinder对象。
- 要接收IBinder对象,客户端必须创建一个ServiceConnection类的实例,并且把这个实例传递给bindService()方法。ServiceConnection对象包含了一个系统调用的传递IBinder对象的回调方法。
- 只有Activity、Service、content provider能绑定服务。
实现
通过绑定服务来实现功能有以下几个步骤:
实现一个ServiceConnection接口,并重写里面的onServiceConnected和onServiceDisconnected两个方法,其中,前者是在服务已经绑定成功后回调的方法,后者是在服务发生异常终止时调用的方法。
在客户端,通过bindService方法来异步地绑定一个服务对象,如果绑定成功,则会回调ServiceConnection接口方法中的onServiceConnected方法,并得到一个IBinder对象。
服务端通过创建一个*.aidl文件来定义一个可以被客户端调用的业务接口,同时,服务端还需要提供一个业务接口的实现类,并实现*.aidl中定义的所有方法,通常让这个实现类去继承Stub类。
注意:创建aidl文件时有几个注意点:
(1)定义的方法前面不能有修饰符,类似于接口的写法。
(2)支持的类型有:8大基本数据类型,CharSequence,String,List<String>,Map,以及自定义类型。
自定义类型需要做到以下几点:
实现Parcelable接口。
定义一个aidl文件来声明该类型。
如果要在其他的aidl文件中使用,则必须要使用import来引用。
通过Service组件来暴露业务接口。
通过Service的onBind方法来返回被绑定的业务对象。
客户端如果绑定成功,就可以像调用自己的方法一样去调用远程的业务对象方法。
使用技巧:
- started启动的服务会长期存在,只要不停
- bind启动的服务通常会在解绑时停止
- 先started,后bind
按照步骤,
- 我们需要三个按钮,三个对应的点击事件(绑定、解绑、调用业务方法)
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/start"
android:onClick="start"
android:text="启动一个服务"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stop"
android:text="停止一个服务"
android:onClick="stop"
app:layout_constraintTop_toBottomOf="@id/start"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/start2"
android:text="启动一个Intent服务"
android:onClick="start2"
app:layout_constraintTop_toBottomOf="@id/stop"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/startbind"
android:text="绑定一个服务"
android:onClick="startbind"
app:layout_constraintTop_toBottomOf="@id/start2"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stopbind"
android:text="解除绑定"
android:onClick="stopbind"
app:layout_constraintTop_toBottomOf="@id/startbind"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/call"
android:text="通过IPC调用业务方法"
android:onClick="call"
app:layout_constraintTop_toBottomOf="@id/stopbind"/>
</android.support.constraint.ConstraintLayout>
- 我们需要对应的描述业务接口的文件——AIDL (创建完毕后会自动生成一个文件名.Stub类)
package com.example.a4_3service;
interface IMyAidlInterface {
//提供给客户端需要绑定业务的方法
void setName(String name);//自定义方法
String desc();//输出一个字符串,描述desc
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
- 有接口,自然有实现类(去继承👆自动生成的那个Stub类)
package com.example.a4_3service;
import android.os.RemoteException;
//业务接口具体实现类
public class MyAidlImpl extends IMyAidlInterface.Stub{
private String name;
@Override
public void setName(String name) throws RemoteException {
this.name=name;
}
@Override
public String desc() throws RemoteException {
return "Name is"+name;
}
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
}
- 对应的bindService文件
package com.example.a4_3service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MybindService extends Service {
public MybindService() {
}
@Override
public IBinder onBind(Intent intent) {
//在这里返回具体的业务类(必须是Ibinder,即返回我们的AIDL)
return new MyAidlImpl();
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
- 最后完成点击事件
package com.example.a4_3service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface iMyAidlInterface;
private boolean mBound=false;//是否绑定成功
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//启动一个服务
public void start(View view){
Intent intent=new Intent(this,MyService.class);
intent.putExtra("info","测试传递数据");
startService(intent);
}
//停止一个服务
public void stop(View view){
Intent intent=new Intent(this,MyService.class);
stopService(intent);
}
//启动一个Intent服务
public void start2(View view){
Intent intent=new Intent(this,MyIntentService.class);
intent.putExtra("info","测试传递数据2");
startService(intent);
}
//绑定服务的连接回调接口
private ServiceConnection conn=new ServiceConnection() {
@Override
//这里的IBinder即public IBinder onBind(Intent intent)返回的Ibinder
public void onServiceConnected(ComponentName name, IBinder service) {
//绑定成功后回调的方法
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
mBound=true;
Toast.makeText(MainActivity.this, "绑定成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
//服务异常时调用
mBound=false;
}
};
//绑定一个服务
public void startbind(View v){
Intent intent=new Intent(this,MybindService.class);
//异步绑定,绑定成功后回调onServiceConnected
//BIND_AUTO_CREATE 绑定一个服务,如果这个服务没有启动,创建一个服务再绑定
bindService(intent,conn, Context.BIND_AUTO_CREATE);
}
//解除绑定服务
public void stopbind(View v){
//只有绑定成功的时候才可以解绑,如果绑定没成功解绑也无从说起
if (mBound){
unbindService(conn);
mBound=false;
Toast.makeText(this, "解除绑定成功", Toast.LENGTH_SHORT).show();
}
}
//通过IPC调用业务方法
public void call(View v){
if (iMyAidlInterface==null){
return;
}
try {
iMyAidlInterface.setName("测试名称");
Toast.makeText(this, iMyAidlInterface.desc(), Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
测试效果如下:
自定义类型AIDL
例如,我们想要在AIDL里自定一个Person类型
首先我们需要额外创建一个AIDL文件Person.aidl
里面啥都不需要 只需要一句parcelable Persons
package com.example.a4_3service;
parcelable Person;
然后需要准备一个Person类并implements Parcelable接口,实现其方法,还需要一个创建器
package com.example.a4_3service;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable {
String name;
String work;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", work='" + work + '\'' +
'}';
}
//创建器
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
Person p = new Person();
p.name = in.readString();
p.work = in.readString();
return p;
}
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(work);
}
}
然后IMyAidlInterface.aidi文件还需要导包
import com.example.a4_3service.Person;
最后在Impl里实现对应的方法,在MainActivity的call方法里补上调用业务
@Override
public Person getPerson() throws RemoteException {
Person p=new Person();
p.name="名字随便什么都好";
p.work="测试员";
return p;
}
效果如👈