2、Service
2.1 Service的基本用法
定义Service
Service是Android系统中的四大组件之一,它是一种长生命周期的,没有可视化界面,运行于后台的一种服务程序。
onBind()是Service类中唯一的抽象方法,必须在子类里实现。我们会在后面的小节中使用到onBind()方法。
这里我们重写了OnCreate()、OnStartCommand()和OnDestroy()这3个方法,它们是每个Service最常用的3个方法了。
public class MyService extends Service {
public MyService() { }
@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");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService", "onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d("onDestroy", "onStartCommand executed");
super.onDestroy();
}
}
在AndroidManifest.xml中注册
每一个Service都需要在AndroidManifest.xml中文件中进行注册才能生效。这是Android四大组件共有的特点。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
</application>
</manifest>
启动和停止Service
创建MainActivity,并修改OnCreate的代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startServiceBtn = findViewById(R.id.button1);
Button stopServiceBtn = findViewById(R.id.button2);
startServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, MyService.class);
startService(intent);
}
});
stopServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, MyService.class);
stopService(intent);
}
});
}
}
startService()和stopService()方法都是定义在Context类中的,所以我们在Activity里可以直接调用这个方法。
另外,Service也可以自我停止运行,只需要我们在Service内部调用stopSelf()方法即可。
从Android 8.0系统开始,应用的后台功能被大幅削减。现在只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。之所以做这样的改动,是为了防止许多恶意的应用程序长期在后台占用手机资源,从而导致手机变得越来越卡。当然,如果需要长期在后台执行一些任务,可以使用前台Service或者WorkManager。
Activity和Service进行通信
我们希望在MyService里提供一个下载功能,然后在Activity中可以决定何时开始下载,以及随时查看下载进度。
实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。修改MyService中的代码:
public class MyService extends Service {
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
public void startDownload() {
Log.d("MyService", "startDownload executed");
}
public int getProgress() {
Log.d("MyService", "getProgress executed");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
......
}
当Activity和Service绑定了之后,就可以调用该Service里的Binder提供的方法了。修改MainActivity中的代码:
public class MainActivity 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_main);
Button bindServiceBtn = findViewById(R.id.button1);
Button stopServiceBtn = findViewById(R.id.button2);
bindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
});
stopServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
unbindService(connection);
}
});
}
}
onServiceConnected()方法方法会在Activity与Service成功绑定的时候调用,而onServiceDisconnected()方法只有在Service的创建进程崩溃或者被杀掉的时候才会调用,这个方法不太常用。
Activity和Service绑定:这里我们仍然构建了一个Intent对象,然后调用bindService()方法将MainActivity和MyService进行绑定。bindService()方法接收3个参数,第一个参数就是刚刚构建出的Intent对象,第二个参数是前面创建出的ServiceConnection的实例,第三个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建Service。这会使得MyService中的onCreate()方法得到执行,但 onStartCommand()方法不会执行。
Activity和Service解绑:调用一下unbindService()方法就可以了。
任何一个Service在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的Activity进行绑定,而且在绑定完成后,它们都可以获取相同的DownloadBinder实例。
2.2 Service的生命周期
2.2.1.png一旦在项目的任何位置调用了Context的startService()方法,相应的Service就会启动,并回调onStartCommand()方法。如果这个Service之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。Service启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用,或者被系统回收。注意,虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个Service只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,Service就会停止。
另外,还可以调用Context的bindService()来获取一个Service的持久连接,这时就会回调Service中的onBind()方法。类似地,如果这个Service之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和Service进行通信了。只要调用方和Service之间的连接没有断开,Service就会一直保持运行状态,直到被系统回收。
当调用了startService()方法后,再去调用stopService()方法。这时Service中的onDestroy()方法就会执行,表示Service已经销毁了。类似地,当调用了bindService()方法后,再去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个Service既调用了startService()方法,又调用了bindService()方法的,根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。
2.3 使用前台Service
前台Service概念
从Android 8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。
而如果你希望Service能够一直保持运行状态,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。
由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。
定义前台Service
PendingIntent和Intent有些类似,都可以去指明某一个“意图”,都可以用于启动活动、启动服务以及发送广播等。不同的是,Intent更加倾向于去立即执行某个动作,而PendingIntent更加倾向于在某个合适的时机去执行某个动作。所以,也可以把PendingIntent简单地理解为延迟执行的Intent。
PendingIntent的用法同样很简单,它主要提供了几个静态方法用于获取PendingIntent的实例,可以根据需求来选择是使用getActivity()方法、getBroadcast()方法、还是getService() 方法。这几个方法所接收的参数都是相同的,第一个参数依旧是Context,不用多做解释。 第二个参数一般用不到,通常都是传入0即可。第三个参数是一个Intent对象,我们可以通过这个对象构建出PendingIntent的 “意图”。第四个参数用于确定PendingIntent的行为,FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这四种值可选,每种值的含义你可以查看文档,通常情况下这个参数传入0就可以了。
public class MyService extends Service {
@Override
public void onCreate() {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
String channelId = "my_service";
String channelName = "前台Service通知";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
}
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.large_icon))
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
}
......
}
可以看到,这里先是使用Intent表达出我们想要启动MainActivity的“意图”,然后将构建好的Intent对象传入PendingIntent的getActivity()方法里,以得到PendingIntent的实力,接着在NotificationCompat.Builder中调用setContentIntent()方法,把它作为参数传入即可。这样子点击一下该通知时,就会打开MainActivity的界面了。
这部分代码和创建通知Notification的方法类似,只不过这次在构建Notification对象后并没有使用NotificationManager将通知显示出来,而是调用了StartForeground()方法。
startForeground()方法接收两个参数:第一个参数是通知的id;第二个参数则是构建的Notification对象。调用startForeground()方法后就会让MyService变成一个前台Service,并在系统状态栏显示出来。
在AndroidManifest.xml中声明权限
另外,从Android 9.0系统开始,使用前台Service必须在AndroidManifest.xml中进行权限声明:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication" >
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
......
</manifest>
现在点击“Start Service”或者“Bind Service”按钮,MyService就会以前台Service的模式启动了,并且在系统状态栏会显示一个通知图标,下拉状态栏可以看到通知的详细内容:
2.3.2.png2.3 使用IntentService
Service处理耗时逻辑
Service中的代码都是默认运行在主线程当中的,如果直接在Service里处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。
所以这个时候就需要用到Android多线程编程的技术了,我们应该在每个Service的每个具体方法里开启一个子线程,然后在这里处理那些耗时的逻辑:
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
......
}
但是,这种Service一旦启动,就会一直处于运行状态,必须调用stopService()或stopSelf()方法,或者被系统回收,Service才会停止:
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
......
}
定义IntentService
总会有一些程序员忘记开启线程,或者忘记调用stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的Service,Android专门提供了一个IntentService类:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 打印当前线程的id
Log.d("MyIntentService", "Thread id is" + Thread.currentThread().getName());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy");
}
}
不要忘记,Service都是需要再AndroidManifest.xml里注册的。
onHandleIntent()方法可以处理一些耗时的逻辑,而不用担心ANR的问题,因为这个方法已经是在子线程中运行的了。另外,这个Service在onHandleIntent()运行结束后会自动停止。