Service 简介
Service
是 Android
四大组件之一,一般而言,Activity
用于提供一个可供交互的屏幕界面,以此完成一些任务。而Service
则与 Activity
不同,它主要用于执行一些不需要与用户进行交互且需要长时间运行的任务。值得注意是,Service虽然是处理后台任务的,但它仍然运行在主线程中,所以,如果需要执行耗时任务,还需要开启一个子线程,不然会出现 ANR(Application Not Responding)
现象。庆幸的是,Android
中存在一个 IntentService
类,它拥有自己的线程,是独立于 主线程的,所以我们需要使用Service
的时候可以直接继承自 IntentService
。
Service 的两种启动方法
- Context.startService()
如果我们需要程序在后台执行一项任务,即使当前用户并没有与程序发生交互,而且这项任务的执行并不需要用户的干预,即不需要与用户发生过多的交互,我们可以选择该方法。
当 Context.startService()
方法调用后,系统会首先调用 Service
的 onCreate()
方法(第一次运行调用该方法,之后就不再调用),然后调用 onStartCommand(Intent, int, int)
。该Service
会一直运行下去,直到 Context.stopService()
或者 StopSelf()
被调用。
onStartCommand(Intent, int, int)
方法会返回一个整形常量,该常量用于告诉系统如何处理 Service
的重启操作,其中三个返回值为
返回 START_STICKY
代表当前系统出于某些原因关闭Service
时,Service
会被重新启动。但是,当系统重启 Service 时, onStartCommand()
参数中的Intent
会被置为 null
。
返回START_NOT_STICKY
意味着 Service
不会在系统关闭它时重新启动,适合执行一次性的操作 。
返回 START_REDELIVER_INTENT
的效果和 START_STICKY
基本一样,但是 onStartCommand()
会接收到Service
被销毁之前接收到的最后一个 Intent
。
- Context.bindService()
对应于上一个方法,如果该后台任务需要与用户发生频繁的交互,可以采用该方法,当然这种方法比上一种方法复杂些。
当Context.bindService()
方法调用后,如果该 Service 还不存在,那么 首先调用onCreate()
方法,然后调用onBind(Intent)
方法。 该 Service
会一直运行只要Connection
是建立着的。
Service 的使用
1.如果采用的是Context.startService()
,简单用法如下:
public class MyService extends Service{
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
}
然后在Activity
中调用startService(Intent)
即可。
这种方法,我们可以在onCreate()
方法中初始化一些我们需要的变量,在 onStartCommand()
方法中执行任务,因为该方法 在每次启动服务时都会被调用,而onCreate()
只在服务创建时执行一次。
使用这种方法,当服务完成时,可以使用发送 Broadcast
的方法通知Activity
已经完成,然后停止该服务。
2.使用 Context.bindService()
,简单用法如下
public class MyLocalService extends Service {
private LocalBinder mLocalBinder = new LocalBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mLocalBinder;
}
public void doLongRunningOperation() {
//执行耗时任务
}
public class LocalBinder extends Binder {
public MyLocalService getService() {
return MyLocalService.this;
}
}
}
public class MainActivity extends AppCompatActivity implements ServiceConnection{
private MyLocalService mLocalService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
Intent intent = new Intent(this,MyLocalService.class);
bindService(intent,this,BIND_AUTO_CREATE);
}
@Override
protected void onPause() {
super.onPause();
if (mLocalService != null){
unbindService(this);
}
}
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mLocalService = ((MyLocalService.LocalBinder)iBinder).getService();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mLocalService = null;
}
}
如图示,当在Activity
中调用 bindService()
之后,接着就会调用 Service 中的onBind()
方法,该方法会返回一个IBinder
给onServiceConnected()
方法,然后就可以通过 IBinder
获得该 Service
对象,接着就可以像使用一个 普通 Java
对象使用它了。
请注意,以上两种方法在执行耗时任务时,都需要手动的去开启一个子线程,为了方便和高效,我们可以使用 IntentService
工具类。
IntentService 的使用
IntentService
在Service
中包装了一个处理后台线程的 Handler
,多个调用会被内部的Hnadler
放到队列中,所以能确保在同一时间只能有一个 Intent
被处理,基于IntentService
的Service
会一直处于启动状态,直到队列中没有要处理的任务。
public class MyIntentService extends IntentService{
public static String ACTION_DOWNLOAD = "com.example.myintentservice.download";
public MyIntentService(String name) {
super(name);
setIntentRedelivery(false);
}
@Override
protected void onHandleIntent(Intent intent) {
String action = intent.getAction();
if (ACTION_DOWNLOAD.equals(action)){
download();
}
}
private void download(){
//Download
}
}
这个时候,我们需要在清单文件中为 Service 添加相应的intent-filter
,然后以带有特定action
的Intent
调用 Context.startService()
方法。
Service 的通信
上面已经提到过很多次,如果使用Context.startService()
,和 Activity
交互是很不方便的,就好像Activity
提醒 Service
你可以运行了,然后Service
就开始自顾自的运行,直到运行结束,通过 Broadcast Receiver
发送一条广播,告诉Activity
我完成任务了,你可以调用 stopService()
了。如果仅仅是这样,是很不利于Service
和 Activity
之间进行大规模快速更新操作的。那么该怎么办呢?
Context.bindService
天生就是解决这个问题的,谁让咱在创建的时候还能返回一个IBinder
呢。 下面就来使用它完成一个下载任务
public class MyLocalService extends Service {
private LocalBinder mLocalBinder = new LocalBinder();
private Callback mCallback;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mLocalBinder;
}
public void doLongRunningOperation() {
new MyAsyncTask().execute();
}
public class LocalBinder extends Binder {
public MyLocalService getService() {
return MyLocalService.this;
}
}
public interface Callback{
public void onOperationProgress(int progress);
public void onOperationCompleted(boolean complete);
}
public void setCallback(Callback callback){
mCallback = callback;
}
private final class MyAsyncTask extends AsyncTask{
@Override
protected Boolean doInBackground(Void... voids) {
int progress = 0;
for (progress = 0;progress<=100;progress+=25) {
publishProgress(progress);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
if (mCallback != null){
mCallback.onOperationProgress(values[0]);
}
}
@Override
protected void onPostExecute(Boolean aBoolean) {
if (mCallback != null && aBoolean){
mCallback.onOperationCompleted(true);
}
}
}
}
public class MainActivity extends AppCompatActivity implements ServiceConnection,MyLocalService.Callback{
private MyLocalService mLocalService;
private Button mButton;
private ProgressDialog mDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.start);
mDialog = new ProgressDialog(this);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mDialog.setTitle("Download");
mDialog.show();
mLocalService.doLongRunningOperation();
}
});
}
@Override
protected void onResume() {
super.onResume();
Intent intent = new Intent(this,MyLocalService.class);
bindService(intent,this,BIND_AUTO_CREATE);
}
@Override
protected void onPause() {
super.onPause();
if (mLocalService != null){
mLocalService.setCallback(null);
unbindService(this);
}
}
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mLocalService = ((MyLocalService.LocalBinder)iBinder).getService();
mLocalService.setCallback(this);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mLocalService = null;
}
@Override
public void onOperationProgress(int progress) {
mDialog.setMessage(progress+"%");
}
@Override
public void onOperationCompleted(boolean complete) {
if (complete){
mDialog.dismiss();
Toast.makeText(this, "Download success", Toast.LENGTH_SHORT).show();
}
}
}