关于Service的一些难点
1、前言
Service作为安卓四大组件之一,在开发中也是经常遇到的,顾明思议我们称为后台服务。当我们的业务不需要前台页面的时候,我们可以使用Service来实现。
2、关于Service的启动方式以及生命周期
Activity的生命周期我们也许背的滚瓜烂熟了,但是Service的生命周期我们可能往往很容易忽略。和Activity不一样的是Service的启动分为两种:startService和bindService。下面我们分别介绍:
2.1、startService
我们通过startService的方式来启动一个service,然后观察生命周期的变化
1、创建MySevice类
package com.example.administrator.workmanager;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
private static final String TAG ="MyService" ;
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind: ..." );
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: ..." );
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: ..." );
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy: ..." );
super.onDestroy();
}
}
2、配置AndroidManifest.xml
<service android:name=".MyService"/>
3、startService和stopService
final Intent intent = new Intent(MyActivity.this,MyService.class);
ivOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startService(intent);
}
});
ivTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopService(intent);
}
});
点击startService启动一个service,我们看打印结果:
E/MyService: onCreate: ...
E/MyService: onStartCommand: ...
我们看到首次启动service的时候,首先会调用onCreate方法,然后在调用onStartCommand方法。那如果启动一个servcie之后再启动呢?我们在启动看看:
E/MyService: onStartCommand: ...
我们看到再次启动的时候,调用了onStartCommand方法,也就是说onCreate在第一次启动的时候会调用。接下来我们来看下通过stopService停止一个service。
stopService(intent);
E/MyService: onDestroy: ...
通过stopService停止一个service。则会调用onDestroy方法。这个时候当我们再次startService,由于之前的service已经被销毁了,则会重新启动一个service
E/MyService: onCreate: ...
E/MyService: onStartCommand: ...
那么就会重新调用onCreate和onStartCommand方法。
2.2、Activity的生命周期会影响service的生命周期吗?
我们看到我们是在Activity中通过startService的方式启动一个service,那么Activity的生命周期和service有关联吗?我们在做一个测试。
我们启动service之后,分别点击Activity进行跳转,回到原来的Activity,关闭Activity等等。发现即使Activity被关闭了调用onDestroy方法。service的生命周期也不会有影响,也就是说service依然是存活在后台中的。因此我们得出结论,通过startService方式启动一个service,那么service的生命周期和Activity的生命周期是没有关系的。他们之间真的没关系吗?答案不是绝对的,下面我们介绍通过bindService的方式启动一个service。
2.3、bindService
首先我们观察到在MyServcie类中是不是有一个onBind方法。在上面我们没有提到,那么其实这个方法和bindService有关的。
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind: ..." );
return null;
}
下面我们通过bindService的方式来启动service
//第一个参数:Intent意图
//第二个参数:是一个接口,通过这个接口接收服务开启或者停止的消息,并且这个参数不能为null
//第三个参数:开启服务时的操作,BIND_AUTO_CREATE代表自动创建service
final Intent intent = new Intent(MyActivity.this,MyService.class);
final MyConnection conn = new MyConnection();
ivOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bindService(intent,conn,BIND_AUTO_CREATE);
}
});
ivTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unbindService(conn);
}
});
private class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//只有当我们自己写的MyService的onBind方法返回值不为null时,才会被调用
Log.e("call","onServiceConnected");
}
@Override
public void onServiceDisconnected(ComponentName name) {
//这个方法只有出现异常时才会调用,服务器正常退出不会调用。
Log.e("call","onServiceDisconnected");
}
}
我们来看打印结果:分别是onCreate、onBind。
E/MyService: onCreate: ...
E/MyService: onBind: ...
当我们再次调用bindService的时候,不会有任何打印。然后我们来看下通过unbindService停止一个service。我们可以打印结果调用了onUnbind然后调用onDestroy方法
E/MyService: onUnbind: ...
E/MyService: onDestroy: ...
我们得出结论:通过bindService的方式启动一个service首先会调用onCreate方法,然后在调用onBind方法,这种启动方式我们一般叫做绑定服务,也就是service和activity 绑定。当我们调用bindService服务之后,我们在再次调用,不会调用任何生命周期方法。我们通过unbindService方式停止解绑一个服务,分别会调用onUnbind和onDestroy方法。那这种方式启动的service和activity有没有关联呢?我们通过bindService启动一个service之后,然后退出当前activity,我们来看下面的生命周期的打印结果:分别调用了Activity的onPause和onDestroy方法,然后在调用了service的onUnbind和onDestroy方法
E/MyService: Activity onPause: ....
E/MyService: Activity onDestroy: ....
E/MyService: onUnbind: ...
E/MyService: onDestroy: ...
我们得出结论就是:通过bindService方式启动一个service其实是和当前activity绑定的,当activity被销毁的时候,那么service也会相应的解绑和销毁。以上我们分析是在onBind返回值是null的情况下,那么如果onBind的返回值不为null呢?
2.4、onBind返回值不为null情况下service生命周期的变化。
我们对MyService和MyConnection进行修改。
package com.example.administrator.workmanager;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
private static final String TAG ="MyService" ;
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind: ..." );
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: ..." );
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: ..." );
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy: ..." );
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, "onUnbind: ..." );
return super.onUnbind(intent);
}
public class MyBinder extends Binder {
public void test(){
serviceTest();
}
}
private void serviceTest(){
Log.e(TAG, "serviceTest: 调用服务中的方法" );
}
}
private class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//只有当我们自己写的MyService的onBind方法返回值不为null时,才会被调用
Log.e(TAG,"onServiceConnected");
//调用服务中的方法
((MyService.MyBinder)service).test();
}
@Override
public void onServiceDisconnected(ComponentName name) {
//这个方法只有出现异常时才会调用,服务器正常退出不会调用。
Log.e(TAG,"onServiceDisconnected");
}
}
我们bindService启动然后看打印结果
E/MyService: onCreate: ...
E/MyService: onBind: ...
E/MyService: onServiceConnected
E/MyService: serviceTest: 调用服务中的方法
然后在通过unbindeService解绑
E/MyService: onUnbind: ...
E/MyService: onDestroy: ...
得出结论是:unbindeService解绑,生命周期的调用顺序还是一样的,但是bindService的时候,在MyConnection中会调用onServiceConnected方法。我们在onServiceConnected可以获取到IBinder对象,其实就是service中onBind返回的MyBiner对象,我们拿到IBinder对象,可以调用MyBiner中的方法,并可以在MyBiner中的方法中调用service的方法,这样也就完成了一次Activity和Service的通信,这种机制我们叫做binder机制。通过binder机制我们可以跨进程通信,假如这个service和我们的Activity不在同一个进程中,那么不就是完成了一次跨进程的通信吗?在启动一个安卓应用时,我们也许以为只是启动了一个应用进程,其实不是的,在内部还启动了很多个进程,我们来看一张图:
image.png从架构图上我们可以看到,安卓系统中主要包含了这几个进程:Init进程、Zygite进程、System Server进程、和应用进程。那么进程之间的通信主要就是以Binder机制为主。
3、小结
我们针对以上的知识点进行总结:
1、service的方式有startService和bindService
2、startService方法启动,生命周期:onCreate,onStartCommand.
停止用stopService方法,生命周期:onDestroy
3、bindService方法启动,生命周期: onCreate, onBind.
unbindService解绑,onUnbind,onDestroy。
Activity被销毁的时候,
Activity onPause
Activity onDestroy
Service onUnbind
Service onDestroy
4、onBind返回值不为null情况下,我们可以通过MyConnection中onServiceConnected方法获取onBind的返回值,调用服务service中的方法,实现Activity和Service的通信。
注意
以上提到的service都是在ui线程中执行的,我们之前的文章有提到,如果想在子线程中执行可以使用IntentService。
4、关于Android O(8.0)后台service限制
Android的每次平台更新,都一直在努力收紧应用权限。8.0也不例外,这次是限制应用后台启动service的权限。
对于所有应用可以使用的解决方法。
1). 平台区分,8.0以下,爱咋咋地,以前怎样还怎样。
2). 8.0以上,统一一个常驻service,转为前台服务。方法为:将startService改成startForegroundService,并在对应的service创建的时候,使用startForeground注册自己的notification。
3). 对,这个方法是带notification的,也就是说,如果想用常驻service,就得让用户知道。
5、startForegroundService的使用
我们知道startForegroundService启动的是作为一个前台服务,我们来看下具体的使用方式
首先创建一个服务,和上面一样
public class MusicPlayerService extends Service {
private static final String TAG = MusicPlayerService.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate()");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand()");
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind()");
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
然后创建Notification:
在Service的onStartCommand中添加如下代码:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand()");
// 在API11之后构建Notification的方式
Notification.Builder builder = new Notification.Builder
(this.getApplicationContext()); //获取一个Notification构造器
Intent nfIntent = new Intent(this, MainActivity.class);
builder.setContentIntent(PendingIntent.
getActivity(this, 0, nfIntent, 0)) // 设置PendingIntent
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),
R.mipmap.ic_large)) // 设置下拉列表中的图标(大图标)
.setContentTitle("下拉列表中的Title") // 设置下拉列表里的标题
.setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
.setContentText("要显示的内容") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
Notification notification = builder.build(); // 获取构建好的Notification
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
}
在完成Notification通知消息的构建后,在Service的onStartCommand中可以使用startForeground方法来让Android服务运行在前台。
// 参数一:唯一的通知标识;参数二:通知消息。
startForeground(110, notification);// 开始前台服务
如果需要停止前台服务,可以使用stopForeground来停止正在运行的前台服务。
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy()");
stopForeground(true);// 停止前台服务--参数:表示是否移除之前的通知
super.onDestroy();
}
到此为止,我们基本上就学会了如何使用前台服务了。
5.1、前台服务与普通服务的区别
- 前台Service的系统优先级更高、不易被回收;
- 前台Service会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。并且图标和标题我们可以在onStartCommand方法中自行设置。