我们这一章来浅显的讲一下Service的用法。老样子,只探究用法不探究源码~
我们菜鸟的目标是只解其一不解其二!嘻嘻
Service的个人理解
Service如同它的名字一样就应该是自己偷偷摸摸在后台运行的那种。我们平时手机明明应用都关光了但还是非常耗电的原因十有八九都是因为后台有许许多多的服务在运行。后台的下载任务,QQ微信等实时接受到消息等都应该是由服务实现的。(当然QQ这种的我还没有细细研究过,只是一种推测啦~)所以说Service对于一个app来说是非常重要的一块。
那么我们来看看谷歌官方是怎么解释一个Service的吧:
What is a Service?
Most confusion about the Service class actually revolves around what it is not:
(大多数关于Service类究竟是什么的困惑通常是关于它不是什么:)
A Service is not a separate process. The Service object itself does not imply it is running in its own process; unless otherwise specified, it runs in the same process as the application it is part of.
(一个Service不是一个单独的进程。Service本身并没有表明自己是运行在自己的进程里除非是特殊指定的,它运行在它属于的应用进程里面)
A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors).
(一个Service不是一个线程。Service不是一种不在主线程做工作的方法(不能用来避免ANR异常))
Thus a Service itself is actually very simple, providing two main features:
(所以说Service本身实际上是非常简单的,Service有两个主要的特点:)
A facility for the application to tell the system about something it wants to be doing in the background (even when the user is not directly interacting with the application). This corresponds to calls to Context.startService(), which ask the system to schedule work for the service, to be run until the service or someone else explicitly stop it.
(应用的一个来告诉系统我要做什么的功能(即使当手机用户没有在跟我这应用进行直接的交互),这个由调用Context.startService()实现——请求系统为Service安排工作,并且一直工作知道Service自己或者其他什么东西非常明确的停止Service)
A facility for an application to expose some of its functionality to other applications. This corresponds to calls to Context.bindService(), which allows a long-standing connection to be made to the service in order to interact with it.
(应用的一个来暴露自身的一些功能给其他的应用。这个由调用Context.bindService()实现——允许建立一个用服务的长期存在的连接,以便与其(应用)交互)
When a Service component is actually created, for either of these reasons, all that the system actually does is instantiate the component and call its onCreate() and any other appropriate callbacks on the main thread. It is up to the Service to implement these with the appropriate behavior, such as creating a secondary thread in which it does its work.
(当一个Service的某些部件被创建,无论是出于什么原因,系统做的所有的事情是实例化这个部件并且调用它的onCreate()方法和主线程的其他的合适的回调方法。这个取决于Service实现了哪些适当的方法,比如在它要工作的地方建立一个子线程)
Note that because Service itself is so simple, you can make your interaction with it as simple or complicated as you want: from treating it as a local Java object that you make direct method calls on , to providing a full remoteable interface using AIDL.
(注意到由于Service本身是如此的简单,你可以自定义你想要指定的交互设计无论是简单或者是复杂:通过把它当做是你能调用直接方法的本地Java对象,来提供可使用AIDL的可被远程调用的接口)。
有很多地方限于我英语水平翻译的不够好,大家见谅哈。对于上面的一大段话,我们只需要知道:Service本身不是一个独立的进程也不是一个独立的线程。它是依附于应用工作的。然后是通过Context.startService()来创建Service,通过Context.bindService()来把服务与Activity结合起来(即我们所说的绑定)
Service的基本用法
我们先来通过代码研究一下Service的生命周期。
开启和关闭Service
Client.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:onClick="open"
android:text="开启服务"
android:textAllCaps="false" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:onClick="close"
android:text="关闭服务"
android:textAllCaps="false" />
</LinearLayout>
代码很简单,定义了两个按钮分别用来实现开始服务和关闭服务。
接着我们自己新建一个MyService类继承Service。
MyService.java
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d("111","bindService");
return null;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d("111","unBindService");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
Log.d("111", "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("111", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("111", "onDestroy");
}
@Override
public void onStart(Intent intent, int startId) {
Log.d("111","onStart");
super.onStart(intent, startId);
}
}
我们这里几乎把所有带on字样的且形似生命周期回调的方法都写上去了。注:onStart方法现在已经过时,已被onStartCommand代替所以大家自己在用的时候可以不用重写onStart方法
然后在Manifiest里面加上我们写的Service
<service android:name=".MyService" />
这是google api的截图:
然后在我们的Activity完成两个按钮的方法。
public void open(View view) {//开启服务
startService(new Intent(this, MyService.class));
}
public void close(View view) {//关闭服务
stopService(new Intent(this, MyService.class));
}
大家看一下这两个调用。startService和stopService可以看出里面的参数是一个Intent(context,Service类)。这个死记硬背一下就行啦~。然后运行一下我们的程序,可以观察到如下情况:
- 按开启服务之后按关闭服务:依次出现onCreate->onStartCommand->onStart->onDestroy
- 按两下开启服务之后按关闭服务:依次出现onCreate->onStartCommand->onStart->onStartCommand->onStart->onDestroy
按开启服务之后按两下关闭服务:依次出现onCreate->onStartCommand->onStart->onDestroy
那么我们可以得出如下结论:
第一次调用startserviceA,会执行A的create和start,如果A没有关闭的情况下再次调用startserviceA,则只会调用A的start方法。
- 调用stopServiceA之后(即服务A已被关闭的情况下,再次调用stopServiceA不起任何作用)。
- startService和stopService没有涉及到任何bind有关字样的方法,也就是说开启服务后,需要我们手动绑定Service和Activity。
- 无论执行了多少次startserviceA,执行一遍stopServiceA就可以结束掉ServiceA。
此外再说一点,结束Service除了在Activity调用Context.stopService()之外,也可以在Service里面调用stopSelf()来使本Service停止。
接下来我们实验Service究竟是不是在UI线程里面工作的。在onCreate或者onStartCommand方法里使当前线程睡眠几秒。
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
然后运行一下会发现,按下按钮之后app会停止响应2秒钟,之后Log界面弹出了onCreate和onStartCommand。这也就说明了Service确实是在UI线程里面工作的。
绑定Service
我们现在来看怎么通过Service达成与Activity交互。
举例:在xml里面新建一个TextView并且text是0,然后我们在Service修改它的值
在MyService.java建立一个内部类并且继承Binder,里面写一个方法。
class MyBinder extends Binder {
public int change() {
return 1;
}
}
然后修改我们的onBind()方法,返回我们建立的MyBinder对象
private MyBinder binder = new MyBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d("111", "bindService");
return binder;
}
在Activity里面定义一个ServiceConnection对象,用于连接Service和Activity。
MyService.MyBinder binder;
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binder = (MyService.MyBinder) service;//
test.setText(binder.change()+"");//调用自定义MyBinder类的方法,通过返回值修改界面的值
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
然后我们完成绑定服务和解绑服务按钮的实现:
public void bind(View view) {//绑定服务
bindService(new Intent(this,MyService.class),connection,0);
}
public void unBind(View view) {//解绑服务
unbindService(connection);
}
我们不必去深究ServiceConnection这个类(其实是一个接口)具体做了什么事情。只需要记住有这个东西就行。大家可以看到在bindService的时候第二个参数就是我们的ServiceConnection的对象。0表明在绑定Service操作中系统不会自动做其他事情。这里可以填一些其他常数,最常用的是BIND_AUTO_CREATE:在绑定Service的时候如果该Service没有被创建则自动创建一个。但要注意虽然这样也可以创建一个新的Service,它的onStartCommand()还是只能由startService()启动。
我们现在来看看加入绑定和解绑操作之后的生命周期走向(bindService的flag参数为0时):
- 直接按解绑服务:出异常
- 开始服务->绑定服务->解绑服务->关闭服务:这个是最正常的流程,onCreate->onStartCommand->onBind->onUnBind->onDestroy
- 开始服务->绑定服务->关闭服务:onCreate->onStartCommand->onBind->onUnBind->onDestroy 注:此时可以再按一下解绑服务,按第二下时出现异常
- 绑定服务->解绑服务:没有输出任何东西,但再按一下解绑服务则抛出异常
由此可以得出几个结论:
- 解绑服务之前必须执行一下绑定服务。
- 可以多次执行绑定服务,但有效的只有第一次
- 关闭服务的时候会自动执行一次解绑服务
前台Service的简单使用
所谓的前台XX,指的就是能与用户直接交互的东西,所以前台的XX的优先级往往很高(用户当然不希望自己正在点的东西突然因为手机内存不足而被杀死啦~)。我们呢在这里使用Notification(通知)实现一个前台服务来模仿天气服务吧~
在MyService的onCreate()方法下面加上这么几句话
Notification.Builder builder = new Notification.Builder(this);//通知构建器
builder.setSmallIcon(R.mipmap.ic_launcher);//设置通知栏图标
builder.setContentText("天气正在运行中。。。");//设置通知栏主信息
builder.setContentTitle("My天气");//设置通知栏标题
builder.setTicker("天气开始运行!");//设置出现通知时手机上面一闪而过的提示信息
Notification notification = builder.build();//构建出一个通知对象
startForeground(1,notification);//把服务从后台以通知的形式运行在前台。1是通知的id号
相应的注释我都写在代码上了,相信大家也能看懂。然后我们按下开启服务的时候会有一个通知出现,按下关闭服务时,通知会从系统状态栏消失。
Service里面开线程处理耗时工作
其实Service的主要工作就应该是独立的处理一些工作:像一些下载啦,或者向服务器发送心跳数据啦等等。所以Service的工作肯定不能占用UI线程。下面让我们来看看两种简单的方法把Service的工作从UI线程里独立出来。
简单的new Thread
这个就很简单了,想必大家平时也一直会自己开线程做一些事情,那么最常用的就是new Thread。
比如在onStartCommand()里面加上新开的线程:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("111", "onStartCommand");
new Thread(new Runnable() {
@Override
public void run() {
stopSelf();//结束服务
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
**注:**stopSelf()之后如果线程后面还有代码可以执行,则会继续执行线程。所以说stopSelf()只会结束Service而不会结束当前线程!!!
使用IntentService
第二种方法就是官方提供给我们的IntentService。新建一个MyIntentService.java
public class MyIntentService extends IntentService {
public MyIntentService()//必须要有一个无参构造方法
{
super("MyIntentService");//必须要有一
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("111","123");
}
}
IntentService和Service用法上差不多,所以我就不详细讲了。onHandleIntent()是在子线程里面运作的,我在里面写了当前线程睡眠5秒用于测试,还有就是在onHandleIntent执行完之后会自动调用onDestroy,我在里面写了个Log用于测试。
别忘了在Manifiest里面注册:
<service android:name=".MyIntentService"/>
调用的话就在Activity里面
startService(new Intent(this,MyIntentService.class));
运行的结果应该是在5秒之后Log界面显示“123”。
总结: Service作为四大组件之一,其重要程度不言而喻。那我们这次主要学了怎么启动和绑定一个Service,怎么把Service从后台转为前台,然后是如何使用IntentService。说起来其实也没多少内容,难点其实是我们自己在用的过程中对于自定义Service的设计——怎么样在Activity和Service之间分配工作才能比较条理清晰的实现我们的目标。