1.Service是什么?
Service是一种在后台运行的组件,它不依赖于任何用户界面,用于执行长时间运行的操作或为远程进程执行作业。是Android中实现程序后台运行的解决方案,因此非常适用于那些不需要和用户进行交互而且还要求长期运行的任务。例如,当用户位于其他应用中时,服务可能位于后台播放音乐或者通过网络获取数据,但不会阻断用户和当前Activity的交互。Service默认不会运行在子线程中,也不运行在一个独立的线程中,而是运行在主线程(UI线程中)。因此,不要在Service中去执行一些耗时操作以免产生ANR,除非在Service中创建了子线程来完成耗时操作。(PS:文章中所涉及的图片均来自Android开发者网站)
2.Service的运行方式
Service可以通过启动和绑定两种方法开启
—启动
当Activity(应用组件)通过调用startService()启动Service时,这时Service就处于“启动”状态。一旦Service被启动,Service就可以在后台无限期的运行,即使启动Service的组件被销毁了也不受影响,这时的Service仅仅做的是单一的操作并且不会将结果返回给调用方,当操作完成后Service就会自动停止运行
—绑定
当Activity(应用组件)通过调用bindService()绑定Service时,这时Service就处于“绑定”状态。绑定Service提供了一个借口,允许组件通过接口与Service进行交互、发送请求、获取结果,甚至可以利用进程间通信(IPC)跨进程去执行这些操作。多个组件可以同时绑定到同一个Service,当全部取消绑定后,该Service就会被销毁
3.Service的生命周期
同Activity一样Service也有自己相应的生命周期回调方法,可以通过这些回调方法去监控Service的状态变化并去执行相应的工作
由上图可知Service的生命周期从创建到销毁有两条路径,分别为startService和bindService。在生命周期内主要是与onCreate()、onStarCommand()、onBind()和onDestory()这四个回调方法,下来就一一介绍这几种回调方法
- onCreate():当Service第一次被创建时调用此方法,该方法在整个生命周期内只调用一次
- onStartCommand():当组件使用startService()方法去启动Service时会调用此方法,如果组件多次使用startService()方法去启动该Service时,Service不会创建新的对象而是继续复用前面创建的对象,但是会继续调用onStartCommand()方法
- onBinder():该方法是Service都必须要实现的方法,如果启动服务该方法返回一个null即可,如果绑定服务该方法需要返回一个IBinder对象,组件就是通过该对象与Service进行通信
- onUnbind():当组件与Service断开绑定时会调用该方法
- onDestory():当Service被关闭时会调用该方法,该方法在整个生命周期内只调用一次
—startService(启动的服务)
当Service被一个组件通过startService()的方法启动后(一般用于后台上传文件或者下载文件等,不跟其它组件通信),这个Service就会开始独立运行,这时必须被自己调用stopSelf()方法或者其它组件使用stopService()的方法停止服务,一旦Service被停止下来系统就会销毁此Service。
—bindService(绑定的服务)
当Service被一个组件通过bindService()的方法绑定后(允许其它组件跟它进行通信),客户就会通过IBinder接口和Service进行交互,客户可以通过unbindService()方法解除绑定,多个客户可以绑定同一个Service,当所有的客户都解除绑定后系统会自动销毁Service,Service没有必要自己停止服务。
上图是Service典型的回调方法,尽管上图是把启动和绑定分开画的,但是这里一定要注意,不管Service是如何被创建的,它都允许客户去进行绑定,所以一个通过startService()方法启动的Service仍然可以被绑定。
4.Service的后台服务
Service在后台的服务主要分为不可交互的后台服务、可交互的后台服务和混合交互的后台服务,Service的可交互性主要取决于组件启动Service的方式不同。
—不可交互的后台服务(startService启动Service)
不可交互的后台服务即是组件通过startService()的方法去启动的Service,这时Service的生命周期很简单,分别为onCreate()、onStarCommand()和onDestory()这三种状态。当组件使用startService()的方法去启动的Service时,首次创建Service会调用onCreate()方法,接下来调用onStarCommand()方法,服务一旦被开启后就需要通过使用stopService()或者stopSelf()停止服务,这时就会调用onDestory()方法。需要注意的是onCreate()方法只是在第一次创建Service时被调用,而onStarCommand()方法则在每次启动Service时都会被调用。
package com.example.administrator.serviceactivity;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
/**
* Created by ChuPeng on 2016/12/13.
*/
public class BindService extends Service
{
//代理类
class MyBinder extends Binder
{
public void showFirst()
{
Log.d("BindService", "First");
}
public void showSecond()
{
Log.d("BindService", "Second");
}
}
//创建服务时调用
public void onCreate()
{
Log.d("BindService", "onCreate");
super.onCreate();
}
//绑定服务时调用
public IBinder onBind(Intent intent)
{
Log.d("BindService", "onBind");
return new MyBinder();
}
//解除绑定服务时调用
public boolean onUnbind(Intent intent)
{
Log.d("onUnbind", "onUnbind");
return super.onUnbind(intent);
}
//销毁服务时调用
public void onDestroy()
{
Log.d("onDestroy", "onUnbind");
super.onDestroy();
}
}
在创建了一个服务以后我们还需要在Manifests文件里进行声明,在声明的过程中只有android:name属性是必须要有的,其他的属性都可以没有。但是有的时候适当的配置可以让我们的开发进行地更加顺利,所以了解一下注册一个service可以声明哪些属性也是很有必要的。
<service
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
</service>
- android:enabled : 如果为true,则这个service可以被系统实例化,如果为false,则不行。默认为true
- android:exported : 如果为true,则其他应用的组件也可以调用这个service并且可以与它进行互动,如果为 false,则只有与service同一个应用或者相同user ID的应用可以开启或绑定此service。它的默 认值取决于service是否有intent filters。如果一个filter都没有,就意味着只有指定了service的 准确的类名才能调用,也就是说这个service只能应用内部使用——其他的应用不知道它的类 名。这种情况下exported的默认值就为false。反之,只要有了一个filter,就意味着service是 考虑到外界使用的情况的,这时exported的默认值就为true
- android:icon : 一个象征着这个service的icon
- android:isolatedProcess : 如果设置为true,这个service将运行在一个从系统中其他部分分离出来的特殊进程 中,我们只能通过Service API来与它进行交流。默认为false
- android:label : 显示给用户的这个service的名字。如果不设置,将会默认使用<application>的label属性
- android:name : 这个service的路径名,例如“com.lypeer.demo.MyService”。这个属性是唯一一个必须填的属性
- android:permission : 其他组件必须具有所填的权限才能启动这个service
- android:process : service运行的进程的name。默认启动的service是运行在主进程中的
由上面的代码可以看出创建一个Service非常简单,只需要继承Service类,并且实现onBind()方法。在Activity中可以使用startService()去启动一个服务,使用stopService()去停止一个服务。当服务被启动的时候可以通过手机看出来此服务正在运行。
当服务被停止时可以通过日志看出Service的生命周期
这是你会发现明明关闭Service了,子线程的耗时操作仍然存在,这里关闭子线程还需要将后台运行的程序清除
—可交互的后台服务(bindService启动Service)
可交互的后台服务即指前台的界面可以绑定服务,并且可以调用服务的方法。和不可交互的后台服务不同在于,这里需要返回给前端界面一个代理对象,界面只要拿到这个代理对象才能控制后台的服务,也就实现了后台服务与前端界面的交互。
package com.example.administrator.serviceactivity;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
/**
* Created by ChuPeng on 2016/12/13.
*/
public class BindService extends Service
{
//代理类
class MyBinder extends Binder
{
public void showFirst()
{
Log.d("BindService", "First");
}
public void showSecond()
{
Log.d("BindService", "Second");
}
}
//创建服务时调用
public void onCreate()
{
Log.d("BindService", "onCreate");
super.onCreate();
}
//绑定服务时调用
public IBinder onBind(Intent intent)
{
Log.d("BindService", "onBind");
return new MyBinder();
}
//解除绑定服务时调用
public boolean onUnbind(Intent intent)
{
Log.d("onUnbind", "onUnbind");
return super.onUnbind(intent);
}
//销毁服务时调用
public void onDestroy()
{
Log.d("onDestroy", "onUnbind");
super.onDestroy();
}
}
这里通过Bind和Unbind这两个按钮去绑定和解绑服务
package com.example.administrator.serviceactivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity
{
private Button Start;
private Button Stop;
private Button Bind;
private Button Unbind;
private Intent StartIntent;
private Intent BindIntent;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Start = (Button) findViewById(R.id.StartService);
Stop = (Button) findViewById(R.id.StopService);
Bind = (Button) findViewById(R.id.BindService);
Unbind = (Button) findViewById(R.id.UnbindService);
StartIntent = new Intent(MainActivity.this, StartService.class);
BindIntent = new Intent(MainActivity.this, BindService.class);
Start.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
startService(StartIntent);
}
});
Stop.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
stopService(StartIntent);
}
});
Bind.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
bindService(BindIntent, connection, BIND_AUTO_CREATE);
}
});
Unbind.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
unbindService(connection);
}
});
}
ServiceConnection connection = new ServiceConnection()
{
//连接到服务
public void onServiceConnected(ComponentName name, IBinder service)
{
Log.d("BindService", "onServiceConnected");
//拿到后台服务代理对象
BindService.MyBinder myBinder = (BindService.MyBinder) service;
//调用后台的方法
myBinder.showFirst();
myBinder.showSecond();
}
//断开服务
public void onServiceDisconnected(ComponentName name)
{
Log.d("BindService", "onServiceDisconnected");
}
};
}
运行程序后点击Bind Service按钮过一段时间再点击Unbind Service按钮,打印的日志如下
其中打印出的First和Second就是在界面上调用后台服务中的方法
—混合交互的后台服务
混合交互的后台服务就是将以上的两个启动服务的方法共同使用,这样即可单独运行后台服务,又能在界面上调用后台服务提供的方法,其完整的生命周期为onCreate->onStartCommand->onBind->onUnBind->onDestroy
5.前台服务
由于后台服务的优先级不高,当系统处于内存不足的情况下就有可能回收后台服务,这时候前台服务就应运而生,前台服务被认为是用户主动意识到的一种服务,因此即使在系统内存不足的情况下也不会考虑回收此类服务。前台服务必须为状态栏提供通知,放在“正在进行”标题下方,这意味着除非服务停止或从前台移除,否则不能清除通知。例如,应该将通过服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。
创建一个前台服务很简单,相比较后台服务来说就是在Service的基础上创建一个Notification,然后使用startForeground()方法启动前台服务。
package com.example.administrator.serviceactivity;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
/**
* Created by ChuPeng on 2016/12/14.
*/
public class ForegroundService extends Service
{
public IBinder onBind(Intent intent)
{
return null;
}
public void onCreate()
{
super.onCreate();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public int onStartCommand(Intent intent, int flags, int startId)
{
//创建点击跳转的Intent
Intent foregroundIntent = new Intent(ForegroundService.this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, foregroundIntent, 0);
//构建通知
Notification.Builder builder = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("2016年12月14日")
.setContentText("今天空气质量:非常不利于健康")
.setContentIntent(pendingIntent);//设置跳转的Intent到通知中
//构建通知
Notification notification = builder.build();
startForeground(1, notification);
return super.onStartCommand(intent, flags, startId);
}
}
运行以后点击启动按钮我们可以看到手机通知栏新增了一条通知,并且点击这条通知可以跳转到程序的主界面
4.Service与Thread的区别
第一次接触到Service的都会有这样的疑问,Service与Thread有什么样的关系,这两者很像。其实二者之间完全没有关系,不少的人认为二者很像的原因就是Service后台的概念。Thread大家想必都很了解,通常用于开启一个子线程用于去执行一些比较耗时的逻辑,防止程序ANR的发生,而Service有这后台的概念,我们总觉得可以在后台处理一些耗时的逻辑,这样就容易将二者混淆。
其实Service中的逻辑是运行在主线程中的,如果在Service中执行一些耗时任务很有可能会造成程序的ANR。Service和Thread是两个完全不同的概念,Service是指运行在系统后台的控件它的运行完全不需要UI界面,即使是Activity被销毁或者程序被关闭,只要该程序的进程还存在Service就可以正常的运行。而Thread是指在主线程中创建一个子线程,在一般情况下子线程的运行是依赖于主线程的,如果主线程被结束掉子线程也将不存在。如果需要在Service处理耗时任务需要开启线程,但是为什么不直接在Activity开启线程去处理耗时任务,为什么要在Service中开启线程?
这是因为Activity很难直接对Thread进行控制,如果Activity被销毁以后就没有办法可以再对子线程建立联系,而且在一个Activity中创建子线程另一个Activity也无法对其进行操作。但是Service就不同了,即使Activity被销毁之后只要与Service建立新的联系就可以对其进行控制,并且一个Service可以关联多个Activity。
以上Demo的源代码地址:点击打开链接