作为 Android 四大组件之一的 Service,使用场景虽然没有 Activity 的 广泛,但是在特定的场景下,Service 却是不可或缺的,简单来说,Service 是一种可在后台执行长时间运行操作而不提供页面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍可继续运行。
启动服务
首先我们创建一个 MyService 继承 Service(抽象类),重写 onCreate() 方法、onStartCommand() 方法和 onDestroy() 方法,onBind() 方法在Service中是抽象方法,必须重写,如下所示
public class MyService extends Service {
private static final String TAG = "MyService";
@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 intent) {
Log.d(TAG, "onBind: ");
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
}
然后在 Activity 中通过 startService() 方法就可以启动服务了,具体如下:
startService(new Intent(this,MyService.class));
相应地,启动服务后应该有停止服务方法:
stopService(new Intent(this,MyService.class));
当我们启动一个 Service 的时候,如果此时这个服务还未启动的时候,就会调用 Service 中的 onCreate() 和 onStartCommand() 方法,如果此时这个服务已经启动的时候就只会调用 onStartCommand() 方法。其实 Service 中还有一个弃用的 onStart() 方法,onStartCommand() 内部调用了 onStart() 方法,我们做一个了解即可,日常开发中有需要的话重写 onStartCommand() 即可,onStartCommand() 方法的返回值是 int 类型,不同的返回值具有不同的效果,具体后面会进行说明,Service 中并没有像Activity那样有 onStop() 回调方法。
绑定服务
绑定服务一般适用于和 Activity 通信,这里需要注意的是 Service 是运行在主线程中,如果我们执行耗时操作,需要开启工作线程去执行。绑定服务和启动服务一样得先有个 Service:
public class MyService extends Service {
private static final String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: ");
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind: ");
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
public class MyBinder extends Binder {
MyService getService() {
return MyService.this;
}
}
}
这里有个 MyBinder 内部类,继承了 Binder ,然后在 onBind() 方法返回了MyBinder一个实例。
在 Activity 中绑定服务:
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: ");
//这里的IBinder涉及到Binder机制,有兴趣的同学可以去详细了解下
//可以简单地理解为是 Service中 onBinder 返回的实例
((MyService.MyBinder) service).getService().startDownloadApk();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected: ");
}
};
bindService(new Intent(this,MyService.class),mConnection,BIND_AUTO_CREATE);
可以看到,我们首先创建了一个 ServiceConnection 匿名内部类,在 onServiceConnected() 方法中我们可以得到 MyBinder 的实例。bindService() 方法接收三个参数,第一个参数就是我们创建的 Intent 对象,第二个参数就是前面创建的ServiceConnection 的实例,第三个参数是一个标志位,一般都是 BIND_AUTO_CREATE,表示在Activity和Service建立关联后自动创建Service,即 MyService中的 onCreate() 方法得到执行。当我们调用 bindService() 方法进行绑定服务时,如果服务此前没有绑定过,则会执行 onBind() 方法,如果已经绑定过,则不会继续执行 onBind() 方法,这点需同每次 startService() 方法启动服务时都会调用onStartCommand() 作区分。此外,通过 bindService() 的绑定的Service的生命周期和调用绑定方法的 Context 生命周期一样,即如果 context 如果是Activity,当Activity 销毁的时候 Service 也会销毁,即使没有调用 unBindService() 方法,但是不建议这么做,因为会系统会抛出异常,虽然这个异常被系统捕捉了。而当 Context 是 Application 时,我们没及时的解绑 Service,则 Service 的生命周期就和 Application 一样长,这在开发中值得我们注意,使用不当会造成资源浪费、内存泄漏,影响应用性能。
解绑服务就很简单啦,直接在Activity中调用 unBindService() 方法。
unbindService(mConnection);
mConnection 就是我们之前创建的 ServiceConnection 啦。
我们可以同时将多个客户端一般是Activity绑定到服务。但是,系统会缓存 IBinder 服务通信通道。换言之,只有在第一个客户端绑定服务时,系统才会调用服务的 onBind() 方法来生成 IBinder。然后,系统会将同一 Binder 传递至绑定到相同服务的所有其他客户端,无需再次调用 onBinder() 方法,当最后一个客户端与服务的绑定时,系统会销毁服务,除非 startService() 方法也启动了该服务。
销毁Service
如前面所说,如果我们只是通过 startService() 方法启动服务,调用 stopService() 方法即可销毁 Service,可以看到打印日志 Service 调用了 onDestroy() 方法,当然也可以调用 Service 的 stopSelf() 方法停止服务。
同理,如果我们只是通过绑定服务 bindService() 方法创建服务,调用 unBindService() 方法即可销毁 Service:
可以看到,在调用 onUnbind() 方法之后紧接着调用 onDestroy() 方法。
这两种销毁方式很好理解,但如果我们既通过 startService() 启动服务,又通过 bindService() 绑定服务,如何才能销毁服务呢?答案是 stopService() 和 unBindService() 都要调用,Service 才能真正被销毁。
前台服务
Service 默认是在后台运行的,相对来说系统优先级还是比较低的,当系统出现内存不足时,就有可能会回收掉正在后台运行的 Service,这时我们可以通过开启前台服务解决这个问题。当然使用前台服务不仅仅是为了提高 Service 优先级的原因,更多时候是让用户感知服务的存在,达到对用户友好的目的,比如通过服务播放音乐的播放器,状态栏的通知可能表示正在播放的歌曲,并且允许用户通过启动 Activity 与音乐播放器进行交互。启动前台服务也很简单,主要是通过调用 startForeground() 方法,此方法有两个参数:唯一标识通知的整型数和用于状态栏的 Notification。此通知必须拥有 PRIORITY_LOW 或更高的优先级,代码如下:
public class ForegroundService extends Service {
//Channel ID 必须保证唯一
private static final String CHANNEL_ID = "me.tandeneck.blogdemo.service.foreground";
private static final String CHANNEL_NAME = "foreground";
private static final int ONGOING_NOTIFICATION_ID = 0x1;
@Override
public void onCreate() {
super.onCreate();
//点击通知跳转的Activity
Intent notificationIntent = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("标题")
.setContentText("内容")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.setAutoCancel(true) // 用户触摸时,自动关闭
.setOngoing(false)//设置处于运行状态
.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
//向系统注册通知渠道,注册后不能改变重要性以及其他通知行为
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}
//提供给 startForeground() 的整型 ID 不得为 0。
startForeground(ONGOING_NOTIFICATION_ID, notification);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
stopForeground(true);
}
}
如果应用面向 Android 9(API 级别 28)或更高版本并使用前台服务,则其必须请求 FOREGROUND_SERVICE权限
点击开启前台服务按钮,服务就会以前台服务的模式启动了,并且在系统通知栏弹出一个通知栏图标,下拉可以看到通知的详细内容:
如果要从前台移除服务,可以调用 stopForeground() 方法,此方法采用布尔值,表示是否同时移除状态栏通知。此方法不会停止服务,注意同 stopService 方法进行区分,如果在服务仍运行于前台将其停止,则通知也会随之移除。
Activity 和 Service 通信
1. 通过Bidner的方式获取Service的引用,就是上述的绑定服务实现。关键步骤如下:
- 在服务中,创建可执行某种操作的 Binder 实例。
- 在 Binder中 返回当前的 Service 实例,该实例中包含 Activity 可调用的公共方法
- 获取 Activity 提供的交互方法,甚至获取 Activity 实例(做好相应的内存管理即可)
- 从 onBind() 回调方法返回此 Binder 实例
- 在 Activity 中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定方法。
2.观察者模式
Service 作为被观察的对象,Activity 作为观察者,但是最终也需要通过绑定服务的方式,在Activity中的 onServiceConnected() 方法中得到 Service 对象, 本质上还是通过 Bidner 通信(回调接口同理),不过可以通过第三方库 EventBus 高度解耦实现。
3. 系统广播实现
广播的实现也是一种观察者模式,由于是系统内置的,又是四大组件之一,所以单独拎出来,实现也比较简单,代码如下:
Service 实现:
public class MsgService extends Service {
private static final String TAG = "MsgService";
private volatile boolean mCancel= false;
private int mProgress = 0;
private LocalBroadcastManager mLocalBroadcastManager;
@Override
public void onCreate() {
super.onCreate();
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
//创建服务后开启下载任务
startDownload();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
public void startDownload() {
final Intent intent = new Intent(getString(R.string.receiver_action));
new Thread(new Runnable() {
@Override
public void run() {
while (!mCancel&& mProgress < 100) {
mProgress += 20;
intent.putExtra(ReceiverActivity.EXTRAS_PROGRESS, mProgress);
mLocalBroadcastManager.sendBroadcast(intent);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "run: " + mProgress);
}
if (mProgress >= 100) {
intent.putExtra(ReceiverActivity.EXTRAS_FINISH, true);
mLocalBroadcastManager.sendBroadcast(intent);
}
}
}).start();
}
@Override
public void onDestroy() {
super.onDestroy();
//停止服务后取消掉下载任务
mCancel= true;
}
}
Activity实现:
public class ReceiverActivity extends BaseActivity {
public static final String EXTRAS_PROGRESS = "progress";
public static final String EXTRAS_FINISH = "finish";
private MyReceiver mMyReceiver;
private IntentFilter mIntentFilter;
private TextView mProgressTv;
private Button mStartBtn, mStopBtn;
private LocalBroadcastManager mLocalBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_receiver);
mProgressTv = findViewById(R.id.progress_tv);
mStartBtn = findViewById(R.id.start_btn);
mStopBtn = findViewById(R.id.stop_btn);
mStartBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开启服务
startService(new Intent(ReceiverActivity.this, MsgService.class));
}
});
mStopBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//停止服务
stopService(new Intent(ReceiverActivity.this, MsgService.class));
}
});
//注册广播
mIntentFilter = new IntentFilter(getString(R.string.receiver_action));
mMyReceiver = new MyReceiver();
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
mLocalBroadcastManager.registerReceiver(mMyReceiver, mIntentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
//注销广播
mLocalBroadcastManager.unregisterReceiver(mMyReceiver);
}
public class MyReceiver extends BroadcastReceiver {
private static final String TAG = "MyReceiver";
@Override
public void onReceive(Context context, Intent intent) {
int progress = intent.getIntExtra(EXTRAS_PROGRESS, 0);
Log.d(TAG, "onReceive: "+progress);
mProgressTv.setText("当前进度:" + progress);
if (intent.getBooleanExtra(EXTRAS_FINISH, false)) {
mProgressTv.setText("下载完成");
stopService(new Intent(ReceiverActivity.this, MsgService.class));
}
}
}
}
Service 保活
前面已经提到 Serivce 由于没有界面,虽然是四大组件之一,但是优先级相比Activity还是较低的,那么有什么办法可以提高 Service 的优先级呢?具体说来主要有以下几种方法:
1. 在 onStartCommand() 方法中,返回 START_STICKY
前面提到 onStartCommand() 的返回值是 int 类型,主要有以下几种值:
- START_NOT_STICKY,当 Service 因为内存不足而被系统 kill 后,接下来即使系统内存足够可用的情况下,系统也不会尝试重新创建 Service,除非程序重新手动启动服务比如通过 startService() 方法。
- START_STICKY,当 Service 因为内存不足而被系统杀死后,接下来系统内存够用的情况下,系统将会尝试重新创建此 Service ,创建成功后将会回调 onStartCommand() 方法,当其中的 intent 将会为 null,PendingIntent 除外。
- START_REDELIVER_INTENT,与 START_STICKY 唯一不同的是,回到 onStartCommand() 方法时,其中的 intent 将会时非空,将是最后一次调用 startService() 方法中的 intent。
- START_STICKY_COMPATIBLITY,顾名思义,是 START_STICKY 的兼容版本,但是在系统杀死服务后不一定保证调用 onStartCommand() 方法。
2.提高 Service 的优先级
在 AndroidManfiest.xml 文件中对于 intent-filter 可以通过 android:priority = "1000" 这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同样适用于广播。
3.提升 Service 进程的优先级,关于 Android 进程优先级
当系统内存紧张时,Android 系统会按照进程优先级进行进程的回收,我们可以通过启动前台服务来提高 Service 的优先级,具体方法可以参照前面。
4.在 onDestroy() 方法里重启 Service
当 Service 销毁时,即执行 onDestroy() 方法,可以发送一个自定义广播,当受到广播时,重新启动 Service。
结束语
以上就是对 Service的简单的解析啦,好久没码字了,累洗啦。限于个人的能力,如有错误之处请不吝赐教,感恩。--->点击查看完整源码<---