一起来聊聊Android基础之Service
标签(空格分隔): Android面试知识 Android开发
参考资料:
google开发文档
拓展阅读:
Android 多线程之IntentService 完全详解
Service扯闲篇
- Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。
- Service是四大组件之一
- Service 服务不会自动创建线程,如果没有为Service服务添加异步操作,那Service服务将运行于主线程当中。,所以不能进行耗时操作。
摘录Android 综合揭秘 —— 全面剖释 Service 服务
Android 支持 Service 服务的原因主要目的有两个,一是简化后台任务的实现,二是实现在同一台设备当中跨进程的远程信息通信。
Service 服务主要分为 Local Service 本地服务与 Remote Service 远程服务两种,本地服务只支持同一进程内的应用程序进行访问,远程服务可通过AIDL(Android Interface Definition Language)技术支持跨进程访问。
远程服务牵扯到Binder机制,扯起来必然需要一篇长文,所以放到单独的文章中来写。
服务基本上分为两种形式:
1.启动
当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。
2.绑定
当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
创建启动Service
启动服务由另一个组件通过调用 startService() 启动,这会导致调用服务的 onStartCommand() 方法。
服务启动后,可以由服务本身调用stopSelf()方法或其它组件调用stopService()来终止服务。
从传统上讲,您可以扩展两个类来创建启动服务:
1.Service
2.IntentService
IntentService 执行以下操作:
- 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
- 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。onHandleIntent为异步方法,可以执行耗时操作
- 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。
- 提供 onBind() 的默认实现(返回 null)。
- 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。
如果您不要求服务同时处理多个请求(多线程),这是最好的选择。 您只需实现 onHandleIntent() 方法和构造函数即可。
想要深入了解IntentService可以阅读源码,它的源码比较简短。主要思路是在onCreate()方法中建立了独立的工作线程(HandlerThread),这是由于onCreate()方法只会在新建服务时被调用一次,可见这样的目的是为了让系统在单个线程中执行多个异步任务。
当系统调用Context.startService()方法时,系统将通过onStartCommand()方法调用onStart()方法使用异步方式,调用ServiceHandler.handleMessage(msg)进行处理,而handleMessage(msg)正是调用了虚拟方法onHandleIntent(intent),然后以stopSelf()结束服务。所以用户只需要在继承类中重写onHandleIntent(intent)方法,便可以以异步方法执行IntentService。
显示启动服务
Service在清单文件中的声明
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
Intent intent = new Intent(this, HelloService.class);
startService(intent);
startService() 方法将立即返回,且 Android 系统调用服务的 onStartCommand() 方法。如果服务尚未运行,则系统会先调用 onCreate(),然后再调用 onStartCommand()。
多个服务启动请求会导致多次对服务的 onStartCommand() 进行相应的调用。但是,要停止服务,只需一个服务停止请求(使用 stopSelf() 或 stopService())即可。
隐式启动服务
Android5.0之后不允许隐式启动Service,如果需要隐式启动Service,可以加上包名。
final Intent serviceIntent=new Intent(); serviceIntent.setAction("com.android.Service");
serviceIntent.setPackage(getPackageName());//设置应用的包名
startService(serviceIntent);
创建绑定Service
绑定服务允许应用组件通过调用 bindService() 与其绑定,以便创建长期连接。
要创建绑定服务,必须实现 onBind() 回调方法以返回 IBinder,用于定义与服务通信的接口。然后,其他应用组件可以调用 bindService() 来检索该接口,并开始对服务调用方法。
多个客户端可以同时绑定到服务。客户端完成与服务的交互后,会调用 unbindService() 取消绑定。一旦没有客户端绑定到该服务,系统就会销毁它。
示例来自google文档:
public class LocalService extends Service {
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
@Override
public void onCreate() {}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("LocalService", "Received start id " + startId + ": " + intent);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new LocalBinder();
}
public static class Binding extends Activity {
// Don't attempt to unbind from the service unless the client has received some
// information about the service's state.
private boolean mShouldUnbind;
// To invoke the bound service, first make sure that this value
// is not null.
private LocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((LocalService.LocalBinder)service).getService();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
}
};
void doBindService() {
// Attempts to establish a connection with the service. We use an
// explicit class name because we want a specific service
// implementation that we know will be running in our own process
// (and thus won't be supporting component replacement by other
// applications).
if (bindService(new Intent(Binding.this, LocalService.class),
mConnection, Context.BIND_AUTO_CREATE)) {
mShouldUnbind = true;
}
}
void doUnbindService() {
if (mShouldUnbind) {
// Release information about the service's state.
unbindService(mConnection);
mShouldUnbind = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
前台服务
前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,放在“正在进行”标题下方,这意味着除非服务停止或从前台移除,否则不能清除通知。
要请求让服务运行于前台,请调用 startForeground()。此方法采用两个参数:唯一标识通知的整型数和状态栏的 Notification。例如:
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
服务Service与线程Thread的区别
首先,这两者没有任何联系~~~
Service是Android中一个可以长期在后台运行的组件,它是运行在主线程上的。
Thread是CPU资源分配的最小单位,主线程(UI线程)也是Thread的一种,Thread通常还用来作为子线程执行一些耗时任务。
管理服务生命周期
以下框架服务展示了每种生命周期方法:
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
- 当调用startService()方法启动服务时,会依次回调onCreate()方法和onStartCommand()方法。但即使系统多次调用 startService(),onCreate() 方法只会在第一次调用时被触发,而onStartCommand()方法会多次调用。同理 onDestory () 方法也只会在服务完结时被触发
- 当调用bindService()方法绑定服务时,会依次回调onCreate()方法和onBind ()方法。成功获取Service的句柄后,系统就会通过用户自定义的serviceConnection对象onServiceConnected(ComponentName name, IBinder service)方法,对 Service 对象作出处理。最后当系统调用Context.unbindService()结束服务时,就会调用onDestory()方法。
- 即使多次调用 bindService(),系统也只会在第一次绑定时调用 onBind() 和 onServiceConnected方()法一次。而多次调用startService(),onStartCommand()方法会多次调用,这也是二者的区别。
Android 5.0以上的隐式启动服务
Android5.0之后,出于安全性考虑,Android不允许隐式启动服务,否则会报错。
解决方案有以下两种:
1.将隐式启动转换成显示启动
public Intent getExplicitIntent(Context context,Intent implicitIntent){
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfos = pm.queryIntentServices(implicitIntent, 0);
if (resolveInfos == null || resolveInfos.size()!= 1) {
return null;
}
Intent explicitIntent = null;
ResolveInfo info = resolveInfos.get(0);
String packageName = info.serviceInfo.packageName;
String className = info.serviceInfo.name;
ComponentName component = new ComponentName(packageName,className);
explicitIntent = new Intent(implicitIntent);
explicitIntent.setComponent(component);
return explicitIntent;
}
2.启动服务时加上包名
Intent intent = new Intent("com.android.service");
intent.setAction("your action");//Service能够匹配的Action
intent.setPackage("com.android.service");//应用的包名
context.bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
其实这两种方案的解决思路是一样的,都是明确了待启动服务的信息,从而转换成显示启动。
保证服务不被杀死的实现思路
1.因内存资源不足Service被杀
首先,我们来了解一下onStartCommand()方法的返回值。
从 onStartCommand() 返回的值必须是以下常量之一:
- START_NOT_STICKY
如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 - START_STICKY
如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。 - START_REDELIVER_INTENT
如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
所以,可以在onStartCommand中返回START_STICKY,并且将该Service作为前台服务来启动,从而避免Service因为内存资源不足而被杀。
2.用户通过 settings -> Apps -> Running -> Stop 方式杀死Service
这种过程会执行Service的生命周期,也就是onDestory方法会被调用,这时便可以在 onDestory() 中发送广播重新启动。这样杀死服务后会立即启动。这种方案是行得通的,但为程序更健全,我们可开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。
3.利用系统广播拉活
4.利用Native进程拉活
5.0之后已经失效
5.利用JobScheduler机制拉活
6.利用账号同步机制拉活