绑定型服务必须依附于应用的生命周期,一般是在最后退出的Activity的onDestroy方法中解绑。
看一个仅绑定的示例。
注意此时服务的生命周期发生变化了:
/**
* 附属的,绑定型服务
* @author cuiweiyou.com
*/
public class BindService extends Service {
@Override
public void onCreate() {
Log.e("ard", "服务create");
super.onCreate();
}
/** 执行绑定的方法
* Activity就靠这个方法和Service联络了
*/
@Override
public IBinder onBind(Intent intent) {
String data = intent.getStringExtra("data");
Log.e("ard", "服务绑定,启动," + data);
NorthBinder binder = new NorthBinder();
return binder;
}
/**
* 再次绑定
*/
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
String data = intent.getStringExtra("data");
Log.e("ard", "再次绑定服务," + data);
}
/**
* 解除绑定
*/
@Override
public boolean onUnbind(Intent intent) {
String data = intent.getStringExtra("data");
Log.e("ard", "解除绑定," + data);
//return true; // 在仅bindService方式,返回值无所谓
return super.onUnbind(intent); // false。结合startService时须区别
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("ard", "服务销毁");
}
/**
* 绑定器,由Service通过onBind方法return给Activity
* Binder完全实现了IBinder接口,没有必须重写的方法
* 根据须要自定义功能即可
* @author cuiweiyou.com
*/
public class NorthBinder extends Binder {
public void doNothing(){
Log.e("ard", "Binder被调用了");
}
}
}
这里面新加入了一个元素,自定义Binder对象。它就是Service和Activity通讯的载体,如同Activity和子线程通讯时的Handler。只不过此情此景显得很有光芒。在这个光芒君里可以定义各种需要的功能,并在onBind方法中将光芒君对象返回即可。Activity会迎接到这个大号人物。
无论是独立服务还是附属服务都须要在AndroidManifest.xml里注册。
android:name ------------- 服务类名
android:label -------------- 服务的自定义标签,如果此项不设置,那么默认显示的服务名则为类名
android:icon -------------- 服务的图标
android:permission ------- 申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务。android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。如果是android:process="remote",没有“:”冒号的,则创建全局进程,不同的应用程序共享该进程。
android:process ---------- 表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字
android:enabled ---------- 如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false
android:exported --------- 表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false
此时对服务进行绑定的Activity须要实现一个ServiceConnection接口,继而实现2个功能,连接服务、断开服务。当然启动服务的方法也不一样了。
/**
* 和Service进行通信的Activity
* 实现ServiceConnection接口
*/
public class MainActivity extends Activity implements ServiceConnection {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(MainActivity.this, BindService.class);
i.putExtra("data", "顺便捎带点什么");
// 绑定服务,此例唯一启动服务的方法(服务意图,回调对象,启动模式)
bindService(i, MainActivity.this, Context.BIND_AUTO_CREATE);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 此时应用销毁
unbindService(MainActivity.this); // 服务先解绑,再销毁
// 绑定的服务必须在应用退出时解绑
// 否则:ServiceConnectionLeaked: Activity has leaked ServiceConnection that was originally bound here
// 且不可重复解绑
}
/**
* ServiceConnection接口定义的与Service通讯连接成功的回调
* @Parameters name:Service的名称,bindService绑定的自定义服务
* @Parameters service:Service的onBind方法return来的Binder对象
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
BindService.NorthBinder binder = (NorthBinder) service;
Log.e("ard", "服务连接了," + name.getClassName());
binder.doNothing(); // 服务中的绑定器的功能
}
/**
* 与Service连接断开的回调
* 正常情况下是不被调用的,它的调用时机是当Service服务被异外销毁时。如内存不足
* @Parameters name:服务名称
*/
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("ard", "意外连接断开 ");
}
}
其中,绑定服务的启动模式有以下几种:
看一下此时启动服务,而后又退出应用的日志:
Service的生命周期里还有个特殊的onRebind方法,出现的情况也比较特殊。首先由startService创建启动了服务,而后bindService进行了绑定;而后解绑服务又再次绑定服务的时候执行,从而跳过onBind方法。
这个情况必须有onUnbind方法的配合:
/**
* 解除绑定
*/
@Override
public boolean onUnbind(Intent intent) {
Log.e("ard", "解除绑定");
return true; // 再次绑定服务时执行onRebind方法
//return super.onUnbind(intent); // false。结合startService时须区别
}
onUnbind方法必须返回true。
首先用startService方法创建启动服务,然后用bindService方法绑定服务,退出应用,解绑服务,再次运行应用,再次绑定服务。看一下日志,“再次绑定服务”由onRebind方法打印。
如果仅须启动一个后台服务长期进行某项任务(播放器)那么使用 startService 便可以了。
如果服务只是公开一个远程接口供客服端远程调用执行方法。可以只用bindService,在第一次bindService的时候才会创建服务的实例运行它,节约资源,尤其是Remote Service。
如果须要与正在运行的Service取得联系,那么有两种方法:一种是使用Broadcast;另外就是使用bindService绑定服务。广播的缺点:若交流较为频繁,容易造成性能上的问题,并且BroadcastReceiver里onReceive中代码的执行时间不能超过5s(也许执行到一半,后面的代码便不会执行)。服务则没有这些问题。
在Activity中更新Service的某些运行状态,需同时使用 startService 和 bindService。
下图是2种服务的生命周期演示:
首先按照服务的使用场景可分2种:
l 本地服务(Local Service):
用于实现应用程序自己的一些耗时任务,比如查询升级信息、播放器,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好。startService()启动,stopService()结束。在服务自身内部可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。无论调用了多少次startService(),都只需调用一次stopService()来停止。
l 远程服务(Remote Sercie):
用于系统内应用程序之间访问。如天气预报服务、内容提供者,其他应用程序不需要再写这样的服务,调用已有的即可。一个应用可以作为服务端定义接口并把接口暴露出来,以便其他应用作为客户端进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。bindService()方法建立连接启动,unbindService()关闭连接。多个客户端可以绑定至同一个服务。
startService创建后持续后台运行,不依附于应用的生命;应用退出时无须stop服务,多用于本地服务。
bindService创建后在后台运行,但依附于应用的生命;应用退出时必须解绑,否则会抛出异常,常用于远程服务。
Thread:Thread 是程序执行的最小单元,它是分配CPU资源的基本单位。可以用Thread来执行一些异步的操作。
Service:Service是android的一种机制,当它运行的时候如果是Local Service,那么对应的Service是运行在主进程的main线程上的。如:onCreate,onStart这些函数在被系统调用的时候都是在主进程的main线程上运行的。如果是Remote Service,那么对应的Service则是运行在独立进程的main线程上。
Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。当 Activity 被 finish 之后,你不再持有该 Thread 的引用,因此无法手动结束线程。另一方面,无法在不同的Activity中对同一Thread进行控制。
因此在Service里面创建、运行并控制Thread,这样便解决了这2个问题。任何Activity都可以控制同一Service,而系统也只会创建一个对应Service的实例。
可以把Service理解为一种消息服务,而你可以在任何有Context的地方调用Context.startService、Context.stopService,Context.bindService、Context.unbindService,来控制它。可以在Service里注册BroadcastReceiver,在其他地方通过发送 broadcast来控制它。
Android系统提供了一个函数ActivityManager.getRunningServices,可以列出当前正在运行的后台服务线程
private boolean isServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
for (android.app.ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (service.service.getClassName().equals("目标服务类名")) {
return true;
}
}
return false;
}
第一种方法就不演示了,后面4种见下一节。
l 启动服务时通过Intent捎带数据
流程:UI —> Service
操作:使用Intent进行数据传递,通过服务中的onStartCommand或onBind方法进行接受,和Activity间传递方式一样。
l Handler+Message
l 使用Broadcast(广播)进行信息的双向传递
流程:UI <——> Service
操作:在服务里注册广播收音机,在Activity发广播。通过广播来进行2者间通信
注意:在服务退出的时候记得解除广播。
l AIDL