1、Service概述
Service的主要作用是,让系统可以在后台干一些不与用户交互的操作,这些操作可能会比较耗时,比如去下载一些网络资源等;也可能是一项长期运行的工作,比如说监听电话来电、播放音乐等。初听起来,Service与线程Thread很像,但Service和Thread完全是两个不同的东西啊。
(1)Service不是运行在一个独立的进程中,它和我们的应用程序在同一个进程中;
(2)Service也不是一个线程,相反,Service是运行在主线程的,因此我们不能直接在Service中干上面那些耗时操作,因为它会很耗CPU,阻塞主线程,很容易出现ANR错误(Application
Not Responding),合适的做法是,在Service中开启一个Thread,进行上面的耗时操作。
如果我们要在Service中开启线程进行工作,我们也可以使用Service的一个子类IntentService,IntentService类中已经开启了线程,我们只需要实现一个方法即可,后面具体介绍。
2、Service用法
和Activity类似,我们要使用Service,只需要通过Intent发出请求即可。当然,在使用Service前,记得在AndroidManifest.xml中进行声明。启动方式有两种:
我们先在Activity中加入两组四个按钮,一组为startService的启动按钮和相应的停止按钮;一组为bindService的启动按钮和相应的停止按钮。如图:
启动方式一:startService()
在Activity中,我们如下使用:
public class MainActivity extends Activity implements OnClickListener{ private Button startServiceBtn,stopServiceBtn,bindServiceBtn,unBindServiceBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startServiceBtn = (Button) findViewById(R.id.startService); stopServiceBtn = (Button) findViewById(R.id.stopService); bindServiceBtn = (Button) findViewById(R.id.bindService); unBindServiceBtn = (Button) findViewById(R.id.unBindService); startServiceBtn.setOnClickListener(this); stopServiceBtn.setOnClickListener(this); bindServiceBtn.setOnClickListener(this); unBindServiceBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.startService://通过startService()的方式启动Service Intent intent = new Intent(this,ServiceTest.class); startService(intent); break; case R.id.stopService: stopService(new Intent(this,ServiceTest.class)); break; case R.id.bindService://通过bindService()的方式启动Service //TODO break; case R.id.unBindService: //TODO break; } } }
我们的Service如下:
public class ServiceTest extends Service { @Override public void onCreate() { super.onCreate(); Log.d("TAG", "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d("TAG", "onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent arg0) { Log.d("TAG", "onBind"); return null; } @Override public boolean onUnbind(Intent intent) { Log.d("TAG", "onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { Log.d("TAG", "onDestroy"); super.onDestroy(); } }
上面,点击“startService”按钮,我们就通过startService()将传进来的Intent中的Service启动了,启动时Log打印如下:
说明,我们第一次启动Service的时候,会执行onCreate()和onStartCommand()方法,如果我们这个时候,再次点击“startService”按钮,此时打印如下:
可以看到,如果一个Service已经运行了,再次启动这个Service,只会进入onStartCommand(),onCreate()方法只会在第一次启动的时候进行初始化。Service不像我们的Activity,Activity每次通过Intent启动时都会创建一个新的Activity(默认模式下),然后放入到栈中。而同一个Service,应用中只会存在一个实例,在第一次创建时通过onCreate初始化,运行后,再次启动只是进入onStartCommand。
点击“stopService”后,Service被销毁,进入onDestroy()方法。不管前面我们启动了多少次Service,只要在外部调用一次Context.stopService()或者在Service内部自己调用一次stopSelf(),Service就会被销毁。
上面这种启动方式,在启动完Service后,这个Service就开始在后台运行了,同时,也与启动它的Activity失去了联系,因为不能通过ServiceTest service =
new ServiceTest()的方式启动Service,因而我们的Activity中不能获取到ServiceTest的实例,也就不能够控制启动后的Service干Activity想干的事,这个Service只能干它内部定义好的功能,没有与启动它的Activity交互能力。
为了解决与启动Service的组件的通信能力,有一种解决方案就是通过广播的形式,我们在Activity中发出一些想用操作广播,在Service中注册该广播,Service接收到该广播信息后,完成相应的功能。但是这种方案不够优雅,频繁发送广播比较消耗性能,同时,由于广播接受者中的onReceive()中,不能执行长时间的工作,时间超过后,可能就直接跳出了方法。因此,这种方案不是首选。
启动方式二:bindService()
如上面所述,如果要求我们的Service能够和启动Service的组件进行通信,我们可以使用bindService的启动方式。
首先改造Service,Service和组件之间是通过管道IBinder接口进行通信的。
public class ServiceTest extends Service { private MyLocalBinder mBinder = new MyLocalBinder(); @Override public void onCreate() { super.onCreate(); Log.d("TAG", "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d("TAG", "onStartCommand"); return super.onStartCommand(intent, flags, startId); } class MyLocalBinder extends Binder{ public ServiceTest getServiceInstance(){ return ServiceTest.this; } //...这里也可以继续写方法对外提供 } @Override public IBinder onBind(Intent arg0) { Log.d("TAG", "onBind"); return mBinder; } //对外提供的访问方法 public void downLoad(){ System.out.println("下载操作方法..."); } @Override public boolean onUnbind(Intent intent) { Log.d("TAG", "onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { Log.d("TAG", "onDestroy"); super.onDestroy(); } }
继续改造我们的Activity,在ASctivity中获得连接通道,如下:
private ServiceTest mService; private boolean isConnected = false;//我们在使用bindService时,最好定义一个是否连接的标志,方便我们在组件中通信前的判断操作 ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder binder) { ServiceTest.MyLocalBinder localBinder = (MyLocalBinder)binder; //先获得管道 mService = localBinder.getServiceInstance(); //通过管道,拿到Service的实例 isConnected = true; mService.downLoad();//拿到Service实例后想干嘛干嘛 } //注意:这个方法当Service意外运行失败时调用,如系统杀死这个Service或者运行Service时遇到崩溃,调用unBinderService并不会调用该方法 @Override public void onServiceDisconnected(ComponentName arg0) { isConnected = false; } }; @Override public void onClick(View v) { switch (v.getId()) { case R.id.startService://通过startService()的方式启动Service Intent intent = new Intent(this,ServiceTest.class); startService(intent); break; case R.id.stopService: stopService(new Intent(this,ServiceTest.class)); break; case R.id.bindService://通过bindService()的方式启动Service Intent intent2 = new Intent(this,ServiceTest.class); bindService(intent2, connection, BIND_AUTO_CREATE); break; case R.id.unBindService: unbindService(connection); break; } } @Override protected void onDestroy() { super.onDestroy(); if(isConnected){ unbindService(connection); isConnected = false; } }
点击“bindService”按钮,我们就通过bindService()将传进来的Intent中的Service启动了,启动时Log打印如下:
我们通过bindService()方法第一次启动后,会进入Service的onCreate()和onBind()方法。如果我们另一个组件(如Activity),又对同一个Service发起了bindService()操作(也就是说在bindService()中传入了不同的ServiceConnection),此时只会进入onBind()方法。也就是说onCreate也只是在Service第一次创建时执行。
我们点击“unBindService”时,打印如下:
此时走的是onUnbind和onDestroy方法。
可以看到,不管通过哪种方式启动Service,同一个Service在整个应用程序中只会有一个实例存在,如果通过bindService启动,获得的IBinder实例也都是同一个。四大组件中,只有在Activity、Service、Content
Providers中通过bindService启动Service。
3、Service生命周期
从上面的打印日志可以看到,两种启动Service方式走的生命周期方法是不同的,官方的生命周期图很好地描述了两种启动方式下的回调:
两种方式都是只有在第一次启动没有运行的Service时,才会进入onCreate()方法。当Service启动后,后面多次继;启动该Service,只会进入onStartCommand()或者onBind()。
(1)当我们通过startService()方法启动Service时,不管前面我们启动了多少次Service,只要在外部调用一次stopService()或者在Service内部自己调用一次stopSelf(),Service就会被销毁;
(2)当我们通过bindService()启动Service时,前面我们多次启动Service后,当没有客户端连接后(即所有客户端发出了unBindService),这个Service将会被系统销毁;
(3)当这个Service既被startService启动,又被bindService启动时,即使当所有连接的客户端断开连接后,Service也不会被销毁,除非再调用一次stopService或内部使用stopSelf(),这个时候Service才会被销毁。或者说如果我们调用了stopService或内部使用stopSelf(),Service也不会被销毁,只有当所有的客户端断开连接后,Service才会被销毁。也就是说,在这种情况下,Service必须在既没有任何Activity关联又停止的情况下,Service才会被销毁。这种情况下的生命周期如下:
注意:为了让我们的Service不过多的浪费CPU资源、内存资源,我们需要在必要的时候进行解除绑定。
(1)当我们的Service只在Activity被用户可见的时候,才与Activity进行交互,那我们应该在Activity的onStart()中bindService,在onStop()中unBindService();
(2)如果我们希望Activity即使在后台时也能够与Service交互,那我们应该在onCreate()中bindService(),在onDestroy()中unBindService(),即Activity在整个生命周期中要与Service交互。
4、IntentService
正如我们第一部分所谈到的,Service和Thread完全是两个不同的东西,Service主要功能是可以在后台执行一些操作,这些操作不需要与用户交互。Service是直接运行在主线中中的,如果我们需要在Service中执行耗时操作,为了避免ANR错误,是需要在Service中开启线程来执行的。对于使用Service有开启线程需求的开发者来说,Android提供了IntentService给用户,IntentService内部已经帮我们开启了线程,我们只需要实现它定义的onHandleIntent()方法,在里面实现我们的功能即可。注意,IntentService不能处理多个线程的请求,但是可以处理多个启动Service的请求。
IntentService提供的功能:
(1)内部创建了一个工作线程,来处理每个启动Service的请求传来的Intent;
(2)内部有一个工作队列,来分别处理多个启动Service的请求,因此避免了多线程的问题;
(3)在所有的请求处理完后,自动停止服务,因此我们不必在Service内部自己写stopSelf();
(4)提供了默认onBind()的实现,直接返回null,意味着IntentService只能通过startService()的方式启动;
(5)提供了默认onStartCommand()的实现,它将我们的Intent放入到了工作队列中,然后执行我们具体实现的onHandleIntent()方法。
一个简单实例如下:
public class IntentServiceTest extends IntentService { public IntentServiceTest(){//提供一个默认的构造方法,调用父类构造方法,传入工作线程的名字 super("IntentServiceTest"); } @Override protected void onHandleIntent(Intent arg0) { long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } } }
使用IntentService主要是注意两点,一是定义一个默认构造方法,里面调用父类构造方法,并定义工作线程的名字;第二是实现onHandleIntent()方法,我们在这里面具体实现我们Service需要做的事情。可以看到,如果我们要使用开启线程的Service,IntentService提供了一种非常好的方案,让用户只需要关注与onHandleIntent接口的具体实现即可,同时用工作队列的形式支持多启动服务的访问。
5、让我们的Service到前台运行
我们的Service默认都是在后台默默运行的,用户基本察觉不到有Service在运行。此时,Service的优先级是比较低的,当系统资源不足的时候,很容易被销毁。因此,如果我们想让用户知道有Service在后台运行,如音乐播放器,或者想让Service一直保持运行状态,不容易被系统回收,此时,就可以考虑使用前台Service。前台Service必须要设置有一个Notification到手机的状态栏,类似360一样,因此前台Service能够与用户进行交互,它的优先级也就提高了,在内存不足的时候,不会优先去回收前台Service。
创建前台Service也很简单,就是设置一个Notification到状态栏,如下:
@Override public void onCreate() { super.onCreate(); Log.d("TAG", "onCreate"); Notification notification = new Notification(R.drawable.ic_launcher,"前台Service通知来了",System.currentTimeMillis()); Intent notificationIntent = new Intent(this,MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(this, "通知标题", "前台Service内容", pendingIntent); //设置到前台运行,第一个参数为通知notification的唯一ID startForeground(1, notification); }
运行效果如下:
如果,我们要移除掉这个前台Service,只需要调用stopService()即可。这个方法并不会停止Service,只是移除掉Notification。
6、如何保证Service不被杀死
(本部分内容来自:http://www.cnblogs.com/rossoneri/p/4530216.html)
这个倒是有点流氓软件的意思,但有些特定情况还是需要服务能保持开启不被杀死,当然这样做我还是在程序里添加了关闭服务的按钮,也就是开启了就杀不死,除非在软件里关闭。
服务不被杀死分3种来讨论
1.系统根据资源分配情况杀死服务
2.用户通过 settings
-> Apps
-> Running
-> Stop
方式杀死服务
3.用户通过 settings
-> Apps
-> Downloaded
-> Force
方式杀死服务
Stop
第一种情况:
用户不干预,完全靠系统来控制,办法有很多。比如 onStartCommand() 方法的返回值设为 START_STICKY
,服务就会在资源紧张的时候被杀掉,然后在资源足够的时候再恢复。当然也可设置为前台服务,使其有高的优先级,在资源紧张的时候也不会被杀掉。
第二种情况:
用户干预,主动杀掉运行中的服务。这个过程杀死服务会通过服务的生命周期,也就是会调用 onDestory() 方法,这时候一个方案就是在 onDestory() 中发送广播开启自己。这样杀死服务后会立即启动。如下:
@Overridepublicvoid onCreate() {
// TODO Auto-generated method stubsuper.onCreate();
mBR = new BroadcastReceiver() {
@Overridepublicvoid onReceive(Context context, Intent intent) {
// TODO Auto-generated method stubIntent a = new Intent(ServiceA.this, ServiceA.class);
startService(a);
}
};
mIF = new IntentFilter();
mIF.addAction("listener");
registerReceiver(mBR, mIF);
}
@Overridepublicvoid onDestroy() {
// TODO Auto-generated method stubsuper.onDestroy();
Intent intent = new Intent();
intent.setAction("listener");
sendBroadcast(intent);
unregisterReceiver(mBR);
}
当然,从理论上来讲这个方案是可行的,实验一下也可以。但有些情况下,发送的广播在消息队列中排的靠后,就有可能服务还没接收到广播就销毁了(这是我对实验结果的猜想,具体执行步骤暂时还不了解)。所以为了能让这个机制完美运行,可以开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。经过实验,这个方案可行,并且用360杀掉后几秒后服务也还是能自启的。到这里再说一句,如果不是某些功能需要的服务,不建议这么做,会降低用户体验。
也就是如下操作,启动服务时,我们开启两个Service,A和B,在A的onCreate中注册B的广播事件,在B的onCreate中注册A的广播事件,当A被销毁时,进入onDestroy()方法时,发送A的广播,B接收到广播之后再启动A;同理,如果B被销毁,发送广播给A,让A启动B。
第三种情况:
强制关闭就没有办法。这个好像是从包的level去关的,并不走完整的生命周期。所以在服务里加代码是无法被调用的。处理这个情况的唯一方法是屏蔽掉 force
和
stopuninstall
按钮,让其不可用。方法自己去找吧。当然有些手机自带的清理功能就是从这个地方清理的,比如华为的清理。所以第三种情况我也没有什么更好的办法了。