Service是Android的四大组件之一,是一个可以在后台执行长时间任务而不提供用户界面的基础组件。
这篇博客主要讲解以下几点内容,这些基本都是面试必问的。
- Service生命周期
- Service的两种启动方式,有什么区别?
- Service的回调函数onStartCommand返回值含义
- 提高Service的优先级,创建前台Service。
- Activity使用AIDL跨进程bind远程Service
Service生命周期
Service的两种启动方式
从上图中也可以看出来启动Service主要所有两种方式,
- 在Activity中可以直接startService(用Intent包裹的Service),对应的销毁service是stopService。
- 通过bindService来绑定一个Service,对应的销毁Service是unbindService。
startService&stopService
public class ServiceDemo extends Service {
private static final String TAG ="ServiceDemo";
@Override
public void onCreate() {
Log.d(TAG,"onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int temp = super.onStartCommand(intent, flags, startId);
Log.d(TAG,"onStartCommand and return "+temp);
return temp;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG,"onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.d(TAG,"onDestroy");
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind");
return null;
}
}
上面代码中,主要是Service的几个回调函数,onCreate、onStartCommand、onBind、onUnBind、onDestroy。
我们来看一下启动Service的代码
public class ServiceDemoActivty extends Activity {
private static final String TAG ="ServiceDemo";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_service_demo);
Intent intent= new Intent(this, ServiceDemo.class);
findViewById(R.id.button).setOnClickListener(v -> {
startService(intent);
});
findViewById(R.id.button2).setOnClickListener(v -> {
stopService(intent);
});
}
}
这个Activity里有两个按钮,一个是启动service,一个是停止service。
当我们点击启动service时,log如下
D/ServiceDemo: onCreate
D/ServiceDemo: onStartCommand and return 1
我们试着多点击几次startService,发现log中没有回调onCreate了,只回调了onStartCommand。
D/ServiceDemo: onCreate
D/ServiceDemo: onStartCommand and return 1
D/ServiceDemo: onStartCommand and return 1
D/ServiceDemo: onStartCommand and return 1
点击stopService之后
D/ServiceDemo: onCreate
D/ServiceDemo: onStartCommand and return 1
D/ServiceDemo: onStartCommand and return 1
D/ServiceDemo: onStartCommand and return 1
D/ServiceDemo: onDestroy
而当Service被销毁之后,再点击stopService时,就不会有任何回调了,也就没有任何意义了。
bindService&unbindService
public class ServiceDemo extends Service {
private static final String TAG ="ServiceDemo";
private MyBinder myBinder = new MyBinder();
@Override
public void onCreate() {
Log.d(TAG,"onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int temp = super.onStartCommand(intent, flags, startId);
Log.d(TAG,"onStartCommand and return "+temp);
return temp;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG,"onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.d(TAG,"onDestroy");
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind");
return myBinder;
}
class MyBinder extends Binder{
public void download(){
Log.d(TAG,"download咯。。。");
}
}
}
从上面的代码中,可以发现ServiceDemo中的onBind方法增加了一个返回值,返回了一个成员变量myBinder。而这个myBinder是通过ServiceDemo的内部类MyBinder创建的,MyBinder继承了Binder,并且有个公共的方法,这个方法有什么用来看下面的Activity代码就知道了。
public class ServiceDemoActivty extends Activity {
private ServiceDemo.MyBinder mBinder;
private static final String TAG ="ServiceDemo";
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG,"onServiceConnected");
mBinder = (ServiceDemo.MyBinder) service;
mBinder.download();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG,"onServiceDisconnected");
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_service_demo);
Intent intent= new Intent(this, ServiceDemo.class);
findViewById(R.id.button).setOnClickListener(v -> {
bindService(intent,mConnection,BIND_AUTO_CREATE);
});
findViewById(R.id.button2).setOnClickListener(v -> {
unbindService(mConnection);
});
}
}
这次是通过 bindService(intent,mConnection,BIND_AUTO_CREATE)来启动Service的。另外,如果多次调用bindService是没有任何反应的,只会调用一次onCreate和onBind。
从上面代码中可以看到,创建了一个匿名内部类,里边的两个方法是在Activity与Service建立连接和断开连接时调用的。在onServiceConnected中我们可以拿到Service传给我们的Binder了,然后就可以调用Service的方法了。
上面介绍的是bindService,如果Activity需要与Service解绑,那就调用unbindService就可以了。
注意:
- 在调用unbindService之前最好加一个判断服务是否绑定过了,如果没有bind或者已经unbind过了再调用这个方法就会出异常了。
- 正常unbind的话,上面ServiceConnection中的onServiceDisconnected是不会调用的,这个方法是在bind被异常终止时回调的,比如在内存不够时系统把Service给干掉了,这种情况会回调这个方法。
java.lang.IllegalArgumentException: Service not registered:
总结:startService和bindService有什么区别?
- 两种启动方式导致的service生命周期不一样。
- startService只是启动一个服务,然后就再也拿不到Service的任何回调了。而bindService在Activity与Service建立连接之后,Activity可以调用Service暴露出来的方法。
onStartCommand返回值含义
onStartCommand的返回值主要是用于描述系统应该如何在服务终止的情况下继续运行服务,主要有以下几种返回值:
START_NOT_STICKY,如果系统在onStartCommand之后把这个service kill了之后并且此时没有挂起的Intent(企图启动这个service的Intent),那么这个Service不会重新启动。
START_STICKY, 系统在onStartCommand之后kill了Service之后,系统会去重新创建这个service,但是不会传递最后一个intent,所以一定要记得判空。
START_REDELIVER_INTENT,系统在onStartCommand之后kill了Service,系统会重写创建Service,并且把最后一个intent以及挂起的intent依次传递进来。
创建前台Service
我们知道后台Service在系统内存不足时很容易被kill掉,我们可以将Service设置成前台Service提升优先级,前台Service一般不会被杀掉。
设置前台Service也是很简单,只要在onCreate或者onStartCommand里创建一个Notification,然后调用startForeground即可。
Notification.Builder builder = new Notification.Builder(this.getApplicationContext()); //获取一个Notification构造器
Intent nfIntent = new Intent(this, MainActivity.class);
builder.setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0))
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),R.mipmap.ic_launcher))
.setContentTitle("Service Title")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText("要显示的内容")
.setWhen(System.currentTimeMillis());
Notification notification = builder.build(); // 获取构建好的Notification
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
startForeground(110,notification);
远程Service
在Service的源码中,有这么一段注释
Note that services, like other application objects, run in the "main thread" of their hosting process. This means that, if your service is going to do any CPU intensive (such as MP3 playback) or blocking (such as
networking) operations, it should spawn its own thread in which to do that
work.
从源码中,可以看出来Service是运行在main thread中的,既然运行在main thread中那肯定是不能有耗时操作了,耗时操作只能在Service中创建子线程来处理耗时操作了,否则一定会出现ANR。
大家可以打印一下Activity中的线程id和Service中的线程id,然后比较一下,其实是一样的,因为都是主线程。这里就不再验证了。
上面说了,在Service里有耗时操作会导致ANR,那么如果把这个Service设置成远程Service还是会ANR吗? 那么怎么设置远程Service呢?
其实很简单,只要在AndroidManifest.xml中的Service添加android:process=":remote"就可以了。
设置成远程Service之后,就不会ANR了,为什么呢?主要是因为远程Service和Activity根本不在同一个进程里,所以远程Service阻塞的其实remote进程的主线程,那当然不会ANR了。
但是设置远程Service会有一个问题,就是我通过startService来启动一个远程Service没有问题,而通过bindService来绑定一个远程Service就有问题了。
FATAL EXCEPTION: main
Process: wanda.com.androiddemo, PID: 28494
java.lang.ClassCastException: android.os.BinderProxy cannot be cast to wanda.com.androiddemo.ServiceDemo$MyBinder
at wanda.com.androiddemo.ServiceDemoActivty$1.onServiceConnected(ServiceDemoActivty.java:26)
上面的运行时异常,主要是因为Activity和Service不在同一个进程了,那么就不能用传统的方式来绑定Service了。这就需要我们的AIDL了。
AIDL的简单使用
AIDL(Android Interface Definition Language)是Android接口定义语言的意思,它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。
下面通过一个例子来简单介绍下AIDL的使用方法
- 创建一个AIDL接口文件(IMyAidlInterface.aidl),用于描述Service暴露给客户端的方法。
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
int plus(int a,int b);
}
这个文件保存时,会在build/generated目录下生成一个IMyAidlInterface.java文件
- 在Service里创建一个Binder,实现AIDL接口中的方法,然后在onBind中返回这个binder。
private IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
@Override
public int plus(int a, int b) throws RemoteException {
return a+b;
}
@Override
public String toUpperCase(String str) throws RemoteException {
return str.toUpperCase();
}
};
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind");
return mBinder;
}
- 在客户端(Activity)中拿到binder,调用AIDL接口中的方法。
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG,"onServiceConnected");
IMyAidlInterface aidlInterface = IMyAidlInterface.Stub.asInterface(service);
try {
int result = aidlInterface.plus(996,998);
Log.d(TAG,result+"");
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG,"onServiceDisconnected");
}
};
上面3步其实只是实现了在一个应用内的跨进程通信,那么怎么实现多应用之间的跨进程通信呢?
其实也很简单,大体描述一下几步,
- 在Service端创建一个AIDL接口文件,描述需要暴露的方法。
- 在Service里创建一个Binder,实现AIDL接口中的方法,然后在onBind中返回这个binder。
- 在Service端的AndroidManifest中添加Service的action,这样客户端就可以隐式调用Service了。
- 将自动生成的AIDL java文件拷贝到需要与Service连接的应用中,记住,包名需要与Service端一致。
- 在客户端(Activity)中通过隐式调用bindService。然后在onServiceConnected中拿到binder,调用AIDL接口中的方法。
参考文章:
https://blog.csdn.net/guolin_blog/article/details/9797169