在Android
系统中,知道为什么有些软件杀不死么?很大的原因就是Service
。那Service
是什么?
Service
是Android
四大组件之一,开发中会经常用到它,例如当检测到App
有新版本的时候,这时候我们一般会开启一个Service
去后台下载新包,这时候,无论用户是否清除我们的App
,都不会影响下载进度。
Service 生命周期 Service
和Activity
一样,都有各自的生命周期,不同的是,由于Service
有两种启动方式,其生命周期也不同。
startService 当应用组件通过startService
方法来启动Service
时,Service
则会处于启动状态,一旦服务启动,它就会在后台无限期的运行,生命周期独立于启动它的组件,即使启动它的组件已经销毁了也不受任何影响,由于启动的服务长期运行在后台,这会大量消耗手机的电量,因此,我们应该在任务执行完成之后调用stopSelf()
来停止服务,或者通过其他应用组件调用stopService
来停止服务。
startService
启动服务后,会执行如下生命周期:onCreate()
->onStart()(已经废弃)
->onStartCommand()
->onDestroy
。
onCreate(): 首次启动服务的时候,系统会调用这个方法,在onStartCommand
和onBind
方法之前,如果服务已经启动了,再次启动时,则不会调用此方法,因此可以在onCreate
方法中做一些初始化的操作。比如要执行耗时的操作,可以在这里创建线程。
onStartCommand(): 当通过startService()
方法来启动服务的时候,在onCreate
方法之后就会调用这个方法,此方法调用后,服务就启动了,将会在后台无限期的运行,直到通过stopService
或者stopSelf
方法来停止服务。
onDestroy: 当服务不再使用且将被销毁是,系统将调用此方法,服务应该事先此方法来清理所有资源,如线程等。
了解完这几个生命周期方法后,我们来写一个简单的Service
:
要使用Service
就要通过继承Service
类来实现,如下:
public class SimpleService extends Service {
public static final String TAG = "SimpleService" ;
@Nullable
@Override
public IBinder onBind (Intent intent) {
Log.i(TAG,"call onBind..." );
return null ;
}
@Override
public void onCreate () {
Log.i(TAG,"call onCreate..." );
}
@Override
public void onStart (Intent intent, int startId) {
Log.i(TAG,"call onStart..." );
}
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
Log.i(TAG,"call onStartCommand..." );
return super .onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy () {
Log.i(TAG,"call onDestroy..." );
}
}
切记,Service
类写好后,需要在AndroidManifest.xml
文件中注册:
<service
android:name =".SimpleService"
android:enabled ="true"
android:exported ="true" >
</service >
然后,我们在Activity
中放置两个按钮,一个启动Service
,一个停止Service
:
@Override
public void onClick (View v) {
switch (v.getId()){
case R.id.start_service:
Intent intent = new Intent(this ,SimpleService.class);
startService(intent);
break ;
case R.id.stop_service:
Intent service = new Intent(this ,SimpleService.class);
stopService(service);
break ;
}
}
可以看到如下打印日志:
小结:通过startService方法启动的服务,服务会无限期的在后台运行,直到通过stopService或者stopSelf来终止服务。服务独立于启动它的组件,也就是说,当组件启动服务后,组件和服务就再也没有关系了。就算启动它的组件被销毁了,服务照样在后台运行。通过这种方式启动的服务不好与组件之间通信。
bindService 除了startService
来启动服务之外,另外一种启动服务的方式就是通过bindService
方法了,也就是绑定服务,其实通过它的名字就容易理解,绑定即将启动组件和服务绑定在一起。前面讲的通过startService
方式启动的服务是与组件相独立的,即使启动服务的组件被销毁了,服务仍然在后台运行不受干扰。但是通过bindSerivce
方式绑定的服务就不一样了,它与绑定组件的生命周期是有关的。如下:
多个组件可以绑定到同一个服务上,如果只有一个组件绑定服务,当绑定的组件被销毁时,服务也就会停止了。如果是多个组件绑定到一个服务上,当绑定到该服务的所有组件都被销毁时,服务才会停止。
bindService
绑定服务和startService
的生命周期是不一样,bindServie
的生命周期如下:onCreate
->onBind
->onUnbind
->onDestroy
。其中重要的就是onBind
和onUnbind
方法。
onBind(): 其他组件想通过bindService
与服务绑定时,系统将会回调这个方法,在实现中,你必须返回一个IBinder
接口,供客户端与服务进行通信,必须实现此方法,这个方法是Service
的一个抽象方法,但是如果你不允许绑定的话,返回null
就可以了。
onUnbind(): 当所有与服务绑定的组件都解除绑定时,就会调用此方法。
了解了这2个方法后,我们来看一下怎么绑定一个服务。
1 首先,添加一个类 继承 Binder ,在Binder 类中添加其他组件要与服务交互的方法,并在onBind() 方法中返回IBinder 实例对象:
public class SimpleService extends Service {
public static final String TAG = "SimpleService" ;
@Nullable
@Override
public IBinder onBind (Intent intent) {
Log.i(TAG,"call onBind..." );
return new MyBinder();
}
@Override
public boolean onUnbind (Intent intent) {
Log.i(TAG,"call onUnbind..." );
return super .onUnbind(intent);
}
@Override
public void onCreate () {
Log.i(TAG,"call onCreate..." );
}
@Override
public void onStart (Intent intent, int startId) {
Log.i(TAG,"call onStart..." );
}
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
Log.i(TAG,"call onStartCommand..." );
return super .onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy () {
Log.i(TAG,"call onDestroy..." );
}
public class MyBinder extends Binder {
public String getStringInfo () {
return "调用了服务中的方法" ;
}
}
}
2, 绑定服务的时候,需要提供一个ServiceConnection 接口,在接口回调中获取Binder 对象,与服务进行通信。
public class ServiceDemoActivity extends AppCompatActivity {
private SimpleService.MyBinder mMyBinder;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected (ComponentName name, IBinder service) {
mMyBinder = (SimpleService.MyBinder) service;
String content = mMyBinder.getStringInfo();
Toast.makeText(ServiceSimpleActivity.this ,content,Toast.LENGTH_LONG).show();
}
@Override
public void onServiceDisconnected (ComponentName name) {
mMyBinder = null ;
}
};
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_service_demo);
}
public void bindServer (View view) {
Intent intent = new Intent(this ,SimpleService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
public void unBindServer (View view) {
unbindService(mConnection);
}
}
点击绑定按钮,即绑定服务,并且在onServiceConnected
中得到MyBinder
对象,就可以通过这个对象和服务通信了,生命周期方法调用如下:
可以看到,绑定服务的生命周期内依此调用了onCreate
,onBind
,onUnbind
和 onDestroy
方法,只有中间两个生命周期方法与startService
启动服务是不同的。一张图就能看清两种方式的生命周期的异同:
tips: Service 的生命周期方法不同与Activity ,不需要调用超类的生命周期方法,如:不用调用 super.onCreate()
注意,当使用startService方式启动Service的时候,可以通过手机的设置查看到启动的服务,而通过bindService方式启动的则不能。这很好理解,前者跟我们的应用程序的存活周期没有关系,有时候需要手动杀掉Service。
Service通信 BroadcastReceiver 通过前文我们知道,startService
方式启动的服务在后台,无限期地运行,并且与启动它的组件是独立的,启动Service
之后也就与启动它的组件没有任何关系了。因此它是不能与启动它的组件之间相互通信的。虽然Service
没有提供这种启动方式的通信方法,我们还是可以通过其他方式来解决的,这就用到了BroadcastReceiver
。
场景描述:通过startService
启动一个长期在后台运行的下载图片服务,然后在界面上点击下载按钮,通过intent
传递一个下载链接给Service
,在下载完成后,通过BroadcastReceiver
通过Activity
界面显示图片。看一下代码实现:
Service:
public class DownloadService extends Service {
public static final String IMAGE = "iamge_url" ;
public static final String RECEIVER_ACTION = "simpleservice" ;
private static final String TAG = "DownloadService" ;
public static final String ACTION_START_SERVICER = "startservice" ;
public static final String ACTION_DOWNLOAD = "startdownload" ;
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler (Looper looper) {
super (looper);
}
@Override
public void handleMessage (Message msg) {
String url = (String) msg.obj;
Bitmap bitmap = null ;
try {
bitmap = Picasso.with(getApplicationContext()).load(url).get();
Intent intent = new Intent();
intent.putExtra("bitmap" ,bitmap);
intent.setAction(RECEIVER_ACTION);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
} catch (IOException e) {
e.printStackTrace();
}
stopSelf();
}
}
@Nullable
@Override
public IBinder onBind (Intent intent) {
return null ;
}
@Override
public void onCreate () {
HandlerThread thread = new HandlerThread("ServiceHandlerThread" , Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
Log.i(TAG,"call onStartCommand..." );
if (intent.getAction().equals(ACTION_DOWNLOAD)){
handleCommand(intent);
}else if (intent.getAction().equals(ACTION_START_SERVICER)){
}
return START_STICKY;
}
private void handleCommand (Intent intent) {
String url = intent.getStringExtra(IMAGE);
Message message = mServiceHandler.obtainMessage();
message.obj = url;
mServiceHandler.sendMessage(message);
}
}
新建了一个DownloadService
,在里面启动了一个工作线程,在线程里下载图片,然后通过BroadcastReceiver
通知Activity
显示。
Activity
的代码很简单,注册BroadcastReceiver
,在onReceiver
中显示图片就好了,代码如下:
public class ServiceDemoActivity extends AppCompatActivity {
private ImageView mImageView;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive (Context context, Intent intent) {
Bitmap bitmap = intent.getParcelableExtra("bitmap" );
mImageView.setImageBitmap(bitmap);
}
};
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_service_demo);
mImageView = (ImageView) findViewById(R.id.imageView);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DownloadService.RECEIVER_ACTION);
LocalBroadcastManager.getInstance(this ).registerReceiver(mReceiver,intentFilter);
}
public void startDownload (View view) {
Intent intent = new Intent(this ,DownloadService.class);
intent.putExtra("url" ,"http://www.8kmm.com/UploadFiles/2012/8/201208140920132659.jpg" );
intent.setAction(DownloadService.ACTION_DOWNLOAD);
startService(intent);
}
}
LocaService 使用Binder 和 服务通信 既然通过startService
启动的服务与启动它的组件是独立的。相互通信比较麻烦,那么Google
也提供了两者之间的通信方法,那就是组件绑定服务,也就是上文将的通过bindService
将组件和服务绑定到一起。组件可以获取Service
通过onBind
返回的一个IBinder
接口,这样两者就可以通信了,这也是Service
应用类通信比较常用的方式。
下面就模拟一个用服务播放音乐的例子来讲一下组件通过Binder 接口和服务之间通信。 首先定义一个通信的接口 IPlayer:
public interface IPlayer {
public void play () ;
public void pause () ;
public void stop () ;
public int getProgress () ;
public int getDuration () ;
}
然后添加一个MusicService 类,继承Service 实现 Iplayer 接口:
public class MusicService extends Service implements IPlayer {
public static final String TAG = "MusicService" ;
private LocalService mBinder = new LocalService();
public class LocalService extends Binder {
public MusicService getService () {
return MusicService.this ;
}
}
@Nullable
@Override
public IBinder onBind (Intent intent) {
return mBinder;
}
@Override
public void play () {
Log.i(TAG,"music play..." );
}
@Override
public void pause () {
Log.i(TAG,"music pause..." );
}
@Override
public void stop () {
Log.i(TAG,"music stop..." );
}
@Override
public int getProgress () {
return 100 ;
}
@Override
public int getDuration () {
return 10240 ;
}
}
其中比较重要的就是内部类LocalService
,继承Binder
,里面提供一个getService
方法,返回MusicService
实例,组件通过IBinder
获取到Music
实例后,就可以和Service
之间相互通信啦!
Activity中代码如下:
public class ServiceDemoActivity extends AppCompatActivity {
private MusicService.LocalService mLocalService;
private MusicService mMusicService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected (ComponentName name, IBinder service) {
mLocalService = (MusicService.LocalService) service;
mMusicService = mLocalService.getService();
mMusicService.play();
mMusicService.pause();
mMusicService.stop();
int progress = mMusicService.getProgress();
Log.i(MusicService.TAG,"progress:" +progress);
int duration = mMusicService.getDuration();
Log.i(MusicService.TAG,"duration:" +duration);
}
@Override
public void onServiceDisconnected (ComponentName name) {
mMusicService = null ;
mLocalService = null ;
}
};
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_service_demo);
}
public void bindServer (View view) {
Intent intent = new Intent(this , MusicService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
}
获取到MusicService
后,就可以调用接口方法了,比如:播放音乐,暂停、停止、获取进度等等。
Service 总结 Service
有2种启动方式,startService
启动服务,服务启动起来后,在后台无限期运行,直到通过stopService
或者stopSelf
停止服务,服务与组件独立,通信比较困难(但还是有办法的,通过BroadcastReceiver
)。另一种方式就是bindService
即绑定服务,组件和服务绑定在一起,服务的生命后期受组件影响,如果绑定到服务的组件全部被销毁了,那么服务也就会停止了。绑定服务的方式通常用于组件和服务之间 需要相互通信。startService
这种 方式一般用于在后台执行任务,而不需要返回结果给组件。 这两种方式并非完全独立,也就是说,你可以绑定已经通过startService
启动起来的服务,可以通过在Intent
中添加Action
来标示要执行的动作。比如:通过Intent Action
标记要播放的音乐,调用startService
来启动音乐服务播放音乐,在界面需要显示播放进度的时候,可以通过binderService
来绑定服务,从而获取歌曲信息。这种情况下,Service
需要实现两种方式的生命周期。这种情况下,除非所有客户端都已经取消绑定,否则通过stopService
或者stopSelf
是不能停止服务的。
Service
是运行在主线程中的,因此不能执行耗时的活着密集型的任务,如果要执行耗时操作或者密集型计算任务,请在服务中开启工作线程,在线程中执行。或者使用下面一节将要讲的IntentService
。
IntentService IntentService
是Service
的子类,它使用工作线程逐一处理所有启动请求,果您不要求服务同时处理多个请求,这是最好的选择。 您只需实现onHandIntent
方法即可,该方法会接收每个启动请求的Intent
,使您能够执行后台工作。
IntentService
默认为我们开启了一个工作线程,在任务执行完毕后,自动停止服务,因此在我们大多数的工作中,使用IntentService
就够了,并且IntentService
比较简单,只要实现一个方法OnHandleIntent
,接下来看一下示例:
public class MyIntentService extends IntentService {
public static final String TAG ="MyIntentService" ;
public MyIntentService () {
super ("MyIntentService" );
}
@Override
protected void onHandleIntent (@Nullable Intent intent) {
boolean isMainThread = Thread.currentThread() == Looper.getMainLooper().getThread();
Log.i(TAG,"is main thread:" +isMainThread);
mockDownload();
}
* 模拟执行下载
*/
private void mockDownload () {
try {
Thread.sleep(5000 );
Log.i(TAG,"下载完成..." );
}catch (Exception e){
e.printStackTrace();
}
}
}
然后启动服务,看一下打印的日志,如下图:
IntentService 总结 IntentService
是Service
的子类,默认给我们开启了一个工作线程执行耗时任务,并且执行完任务后自 动停止服务。扩展IntentService
比较简单,提供一个构造方法和实现onHandleIntent
方法就可了,不用重写父类的其他方法。但是如果要绑定服务的话,还是要重写onBind
返回一个IBinder
的。使用Service
可以同时执行多个请求,而使用IntentService
只能同时执行一个请求。