10.3 Service的基本用法
10.3.1 定义一个Service
package com.example.demo2.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
public class MyService extends Service {
// 这个方法是Service中唯一的抽象方法,所以必须在子类里实现
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
这里我们又重写了onCreate()、onStartCommand()和onDestroy()这3个方法,它们是每个Service中最常用到的3个方法了。
其中onCreate()方法会在Service创建的时候调用
onStartCommand()方法会在每次Service启动的时候调用
onDestroy()方法会在Service销毁的时候调用
在AndroidManifest.xml文件中进行注册才能生效
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
tools:targetApi="31">
<service
android:name=".service.MyService"
android:enabled="true"
android:exported="true" />
</application>
</manifest>
Exported 属性表示是否将这个Service暴露给外部其他程序访问,Enabled 属性表示是否启用这个Service。
10.3.2 启动和停止Service
启动和停止的方法当然你也不会陌生,主要是借助Intent来实现的。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startBtn = findViewById(R.id.btn1);
startBtn.setOnClickListener(v -> {
Intent intent = new Intent(this, MyService.class);
startService(intent);
});
Button stopBtn = findViewById(R.id.btn1);
stopBtn.setOnClickListener(v -> {
Intent intent = new Intent(this, MyService.class);
stopService(intent);
});
}
另外,Service也可以自我停止运行,只需要在Service内部调用stopSelf()方法即可。
启动Service后,可以在Settings→System→Advanced→Developeroptions→Running services中找到它
从Android 8.0系统开始,应用的后台功能被大幅削减。现在只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。
如果你真的非常需要长期在后台执行一些任务,可以使用前台Service或者WorkManager
10.3.3 Activity和Service进行通信
需要借助 onBind() 方法
比如说,目前我们希望在MyService里提供一个下载功能,然后在Activity中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。
package com.example.demo2.service;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
public class MyService extends Service {
private DownloadBinder mBinder = new DownloadBinder();
// 这个方法是Service中唯一的抽象方法,所以必须在子类里实现
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public static class DownloadBinder extends Binder {
public void startDownload() {
Log.d("MyService", "startDownload executed");
}
public int getProgress() {
Log.d("MyService", "getProgress executed");
return 0;
}
}
}
这里我们新建了一个DownloadBinder类,并让它继承自Binder,然后在它的内部提供了开始下载以及查看下载进度的方法。
当一个Activity和Service绑定了之后,就可以调用该Service里的Binder提供的方法了。
package com.example.demo2;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.widget.Button;
import com.example.demo2.service.MyService;
public class MainActivity2 extends AppCompatActivity {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
Button bindServiceBtn = findViewById(R.id.btn3);
bindServiceBtn.setOnClickListener(v -> {
Intent intent = new Intent(this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);// 绑定Service
});
Button unbindServiceBtn = findViewById(R.id.btn4);
unbindServiceBtn.setOnClickListener(v -> {
unbindService(connection);// 解绑Service
});
}
}
这里我们首先创建了一个ServiceConnection的匿名类实现,并在里面重写了onServiceConnected()方法和onServiceDisconnected()方法。
onServiceConnected()方法方法会在Activity与Service成功绑定的时候调用,而onServiceDisconnected()方法只有在Service的创建进程崩溃或者被杀掉的时候才会调用
在onServiceConnected()方法中,我们又通过向下转型得到了DownloadBinder的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。现在我们可以在Activity中根据具体的场景来调用DownloadBinder中的任何public方法,即实现了指挥Service干什么Service就去干什么的功能。
当然,现在Activity和Service其实还没进行绑定呢,这个功能是在“Bind Service”按钮的点击 事件里完成的。可以看到,这里我们仍然构建了一个Intent对象,然后调用bindService()方 法将MainActivity和MyService进行绑定。bindService()方法接收3个参数,第一个参数就 是刚刚构建出的Intent对象,第二个参数是前面创建出的ServiceConnection的实例,第三 个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在Activity和Service进行绑定后 自动创建Service。这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
10.4 Service的生命周期
一旦在项目的任何位置调用了Context的startService()方法,相应的Service就会启动,并回调onStartCommand()方法。 如果这个Service之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。
Service启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用,或者被系统回收。
注意,虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个Service只会存在一个实例。 所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,Service就会停止。
可以调用Context的bindService()来获取一个Service的持久连接,这时就会回调Service中的onBind()方法。 之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和Service进行通信了。 只要调用方和Service之间的连接没有断开,Service就会一直保持运行状态,直到被系统回收。
当调用了startService()方法后,再去调用stopService()方法。这时Service中的onDestroy()方法就会执行,表示Service已经销毁了。
类似地,当调用了bindService()方法后,再去调用unbindService()方法,onDestroy()方法也会执行
对一个Service既调用了startService()方法,又调用了bindService()方法的,在这种情况下该如何让Service销毁呢? 根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁。 所以,这种情况下要同时调用stopService()和 unbindService()方法,onDestroy()方法才会执行。
10.5 Service的更多技巧
10.5.1 使用前台Service
从Android 8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。
而如果你希望Service能够一直保持运行状态,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果
由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。另外,用户也可以通过下拉状态栏清楚地知道当前什么应用正在运行,因此也不存在某些恶意应用长期在后台偷偷占用手机资源的情况。
package com.example.demo2.service;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import com.example.demo2.MainActivity;
public class MyService2 extends Service {
public MyService2() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("my_service", "前台Service通知",
NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
}
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
Notification notification = new NotificationCompat.Builder(this, "my_service2")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setContentIntent(pi)
.build();
startForeground(1, notification);
}
}
这里调用了startForeground()方法。这个方法接收两个参数:第一个参数是通知的id,类似于notify()方法的第一个参数;第二个参数则是构建的Notification对象。调用startForeground()方法后就会让MyService变成一个前台Service,并在系统状态栏显示出来。
另外,从Android 9.0系统开始,使用前台Service必须在AndroidManifest.xml文件中进行权限声明才行
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
10.5.2 使用IntentService
Service中的代码都是默认运行在主线程当中的,如果直接在Service里处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。
所以这个时候就需要用到Android多线程编程的技术了,我们应该在Service的每个具体的方法里开启一个子线程,然后在这里处理那些耗时的逻辑。因此,一个比较标准的Service就可以写成如下形式:
class MyService : Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
thread {
// 处理具体的逻辑
stopSelf()
}
return super.onStartCommand(intent, flags, startId)
}
}