Service为Android四大组件之一
- 目录
- 什么是Service?
- 服务的基本用法
- Android-8.0的行为变更
什么是Service?
- 服务是Android中实现程序后台运行的方案,他非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。
- 服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了其他应用程序,服务仍然能够保持正常运行。
- 服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序被杀掉时,所有依赖于该进程的服务也会停止运行(当然,设置为多进程状况除外)
- 不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认在运行在主线程当中的。也就是说,我们需要在服务内部手动创建子线程,并在里面执行具体的任务,或者就有可能出现主线程被阻塞的情况出现。
- 服务的超时时间是20s。
Android中的多线程编程
① 使用Thread类
② 使用Runnable类
③ 使用Handler类
④ 使用AsyncTask类
服务的基本用法
一、定义一个服务
新建一个Service,命名为MyService,
Exported属性表示是否允许除了当前程序之外的其他程序访问这个服务
Enabled属性表示是否启动这个服务
将两个属性都勾选,点击Finish完成创建,现在观察MyService中的代码:
package com.sl.demo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
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");
}
}
可以看见MyService继承自Service类的,说明这是一个服务;现在看里面只有一个构造方法和一个onBind()方法,这个onBind()方法时Service中的唯一的一个抽象方法,必须在子类实现,现在我们先忽略这个方法,后面再讲解这个方法;
服务里面的逻辑应该在哪里写呢?这就需要我们重写Service中另外一些方法了,如下:
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();
}
@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() : 此方法会在程序销毁时调用
通常情况下,如果希望服务一启动就立刻去执行某个动作,就可以将逻辑写在onStartCommand()方法中。而当服务销毁时,我们又应该在onDestroy()方法中去回收那些不再使用的资源。
注意:每一个服务都需要在AndroidManifest.xml中进行注册才能生效。这也是Android四大组件共有的特点,当通过Android studio通过new 创建服务时,Android studio已帮我们在Androidmanifest .xml中将这一步完成了
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sl.demo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
</application>
</manifest>
这样一个服务就定义好了。
二、启动和停止服务
现在先在Service中的几个方法中加入打印日志:
public class MyService extends Service {
public MyService() {
Log.d("MyService","MyService() executed");
}
@Override
public IBinder onBind(Intent intent) {
Log.d("MyService","onBind executed");
// 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("MyService","onDestroy executed");
super.onDestroy();
}
}
在MainActivity中添加两个按钮,并绑定点击事件,一个用于启动服务,一个用于停止服务,布局就不贴了,现在看MainActivity中代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button start_Service ,stop_service;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start_Service = findViewById(R.id.start_Service);
stop_service = findViewById(R.id.stop_service);
start_Service.setOnClickListener(this); //添加点击事件
stop_service.setOnClickListener(this); //添加点击事件
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_Service: //启动服务
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);
break;
case R.id.stop_service: //停止服务
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);
break;
}
}
可以看见,启动和停止都是通过Intent来实现的;startService(Intent)和stopService(Intent)方法都是定义在Context类中,这里完全时由活动来开控制服务的启动和停止的。
服务自身也是可以让自己停止下来的,只需要在需要的位置调用stopSelf()方法就能让该服务停止下来;
现在我们来运行程序,查看服务的运行日志:
点击开始服务按钮:
03-13 11:50:20.692 1744-1744/com.sl.demo D/MyService: MyService() executed
03-13 11:50:20.692 1744-1744/com.sl.demo D/MyService: onCreate executed
03-13 11:50:20.692 1744-1744/com.sl.demo D/MyService: onStartCommand executed
看结果说明这个服务启动了,onCreate和onStartCommand方法都执行了,我们也可以在手机中查看该服务是否启动:设置——》开发者选项——》正在运行的服务,能再列表中看到该应用,点击后可以看见详情:
不要纠结我这个项目名为Notification,因为我是在一个已有的项目上进行测试滴,很明显的看见Myservice是启动的
在这里在验证一下onCreate和onStartCommand方法,我们多次对启动服务按钮点击,查看:
03-13 12:05:53.742 1744-1744/com.sl.demo D/MyService: MyService() executed
03-13 12:05:53.742 1744-1744/com.sl.demo D/MyService: onCreate executed
03-13 12:05:53.742 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:54.899 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:55.697 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:56.331 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:56.863 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:57.397 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:57.965 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:58.662 1744-1744/com.sl.demo D/MyService: onStartCommand executed
从这里可以看见,onCreate只运行了一次,而每次启动服务时,onStartCommand都会运行。
点击停止服务按钮:
03-13 11:50:25.915 1744-1744/com.sl.demo D/MyService: onDestroy executed
再进入设置里面查看的时候,已经没有这个服务了。
至此就完成了服务的启动和停止
三、活动与服务通信
前面只是通过活动来启动和停止服务,但是活动并不知道服务做了什么,以及完成得如何。如果想让活动和服务的关系更紧密一些,就可以借助我们刚才忽略的onBind()方法了.
例,我们需要在MyService里main提供一个下载功能,然后在活动中可以决定何时下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的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 ;
}
}
这里主要做了一下三个工作:
① 新建了一个DownLoadBinder继承Binder类,然后在它的内容提供了开始下载和查看进度的模拟方法
② 在MyService中创建了DownLoadBinder实例
③ 在onBind()方法中返回这个实例
至此完成了MyService的修改;
现在在对MainActivity进行修改,将两个按钮修改为绑定服务和解绑服务,当一个活动和服务绑定了后,就可以调用该服务里的Binder提供的方法了,修改MainActivity如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button bind_Service ,unBind_service;
private MyService.DownLoadBinder downLoadBinder ;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected executed");
downLoadBinder = (MyService.DownLoadBinder)service ;
downLoadBinder.startDownLoad();
downLoadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected executed");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bind_Service = findViewById(R.id.bind_Service);
unBind_service = findViewById(R.id.unBind_service);
bind_Service.setOnClickListener(this); //添加点击事件
unBind_service.setOnClickListener(this); //添加点击事件
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bind_Service: //绑定服务
Intent bindIntent = new Intent(this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE);
break;
case R.id.unBind_service: //解绑服务
unbindService(connection);
break;
}
}
}
在MainActivity中主要实现了
① 创建了一个ServiceConnection类,在里面重写了onServiceConnected()方法和onServiceDisconnected()方法,这两个方法分别会在活动和服务成功绑定和解除绑定的时候调用。在onServiceConnected()方法中,我们通过向下转型得到了DownLoadBinder的实例,通过这个实例活动和服务就变得非常紧密了,在活动中可以根据具体的场景来调用DownLoadBinder中的任何public方法了。
② 在绑定服务的按钮点击事件中,构建Intent兑现,调用bindService()方法,将MainActivity和MyService进行绑定;
③ 在解除绑定服务中按钮点击事件中,直接调用unbindService()方法即可
点击绑定服务查看日志:
03-21 14:15:55.918 30849-30849/com.sl.demo D/MyService: onCreate executed
03-21 14:15:55.927 30849-30849/com.sl.demo D/MainActivity: onServiceConnected executed
03-21 14:15:55.927 30849-30849/com.sl.demo D/MyService: startDownLoad executed
03-21 14:15:55.927 30849-30849/com.sl.demo D/MyService: getProgress executed
点击解绑服务查看日志:
03-21 14:17:18.371 31155-31155/com.sl.demo D/MyService: onDestroy executed
发现onServiceDisconnected()方法时没有被调用的,在连接正常关闭的情况下是不会被调用的, 该方法只在Service 被破坏了或者被杀死的时候调用. 例如, 系统资源不足, 要关闭一些Services, 刚好连接绑定的 Service 是被关闭者之一, 这个时候onServiceDisconnected() 就会被调用
注:任何一个服务在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的活动绑定,而且绑定完成后他们都可以获取到相同的DownLoadBinder实例
四、服务的生命周期
1)启动Service服务
单次:startService() —> onCreate() —> onStartCommand()
多次:startService() —> onCreate() —> onStartCommand() —> onStartCommand()
2)停止Service服务
stopService() —> onDestroy()
3)绑定Service服务
bindService() —> onCreate() —> onBind()
4)解绑Service服务
unbindService() —> onUnbind() —> onDestroy()
5)启动绑定Service服务
startService() —> onCreate() —> onStartCommand() —> bindService() —> onBind()
6)解绑停止Service服务
unbindService() —> onUnbind() —> stopService() —> onDestroy()
7)解绑绑定Service服务
unbindService() —> onUnbind(ture) —> bindService() —> onRebind()
五、前台服务
服务几乎都是在后台运行的,但是服务的优先级还是比较低,当系统出现内存不足的时候,就有可能会回收正在后天运行的服务。如果希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑前台服务;
前台服务后后台服务最大的区别就在于,他会一直有一个正在运行的图片在系统状态栏显示,下拉状态栏可以看到更加详细的信息,非常类似于通知的效果。
修改MyService的onCreate()方法如下:
@Override
public void onCreate() {
Log.d("MyService","onCreate executed");
super.onCreate();
int channelId = 1 ;
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder ;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ //Android 8.0适配
NotificationChannel channel = manager.getNotificationChannel(String.valueOf(channelId));
if (channel == null){
channel = new NotificationChannel(String.valueOf(channelId),
"channel_name",
NotificationManager.IMPORTANCE_HIGH);
manager.createNotificationChannel(channel);
}
builder = new NotificationCompat.Builder(this,String.valueOf(channelId));
}else{
builder = new NotificationCompat.Builder(this);
}
Notification notification = builder
.setContentTitle("this is content title")
.setContentText("this is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pi)
.build();
startForeground(1,notification);
}
创建一个Notification对象,调用startForeground()方法就会让MyService变为一个前台服务,并在系统栏显示出现,现在调用StartService()或BindService()方法,MyService就会以前台服务的模式启动了
六、使用IntentService
服务中的代码默认都是运行在主线程当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR的情况(Application Not Responding)
这种情况我们可以在具体实现的方法中开启一个子线程,让后在里面处理耗时逻辑,但是这种服务一旦启动起来就会一直处于运行状态,所以,如果想要让一个服务在执行完毕后自动停止,就必须在执行完耗时操作后调用stopSelf();
IntentService的目的是为了简单的创建一个异步的、会自动停止的服务。他是继承Service的一个抽象类,下面看看它的用法。
新建一个MyIntentService类集成子IntentService:
public class MyIntentService extends IntentService {
public MyIntentService(){
super("MyIntentService"); //调用父类有参构造
}
/** 实现父类的这个抽象方法,此方法为异步线程,也为主要Service的主要运行方法 */
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.d("MyIntentService", "onHandleIntent: Theard id is " + Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}
}
修改主界面布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="@+id/bind_Service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="启动服务"
android:onClick="startIntentService"/>
</LinearLayout>
MainActivity代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startIntentService(View view){
Log.d(TAG, "startIntentService: Theard id is " + Thread.currentThread().getId());
Intent intent = new Intent(this,MyIntentService.class);
startService(intent);
}
}
当然,不能忘记在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"
package="com.sl.demo">
<application
android:name=".app.DemoApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:label">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyIntentService" />
</application>
</manifest>
运行项目,查看log日志:
03-21 17:28:12.300 23843-23843/com.sl.demo D/MainActivity: startIntentService: Theard id is 2
03-21 17:28:12.316 23843-24604/com.sl.demo D/MyIntentService: onHandleIntent: Theard id is 5820
03-21 17:28:12.318 23843-23843/com.sl.demo D/MyIntentService: onDestroy executed
可以看见,MyIntentService和MainActivity所在的线程id不一样,而且MyIntentService的onDestory()方法在运行完毕后也自动停止了。
Android 8.0的行为变更
Android 8.0为提高电池续航时间,引入了 后台执行限制,当您的应用进入 已缓存 的状态时,如果没有活动的组件,系统将解除应用具有的所有唤醒锁。
此外,为提高设备性能,系统会限制未在前台运行的应用的某些行为。具体而言:
- 现在,在后台运行的应用对后台服务的访问受到限制。
- 应用无法使用其清单注册大部分隐式广播(即,并非专门针对此应用的广播)。
默认情况下,这些限制仅适用于针对 O 的应用。不过,用户可以从 Settings 屏幕为任意应用启用这些限制,即使应用并不是以 O 为目标平台。
Android 8.0 还对特定函数做出了以下变更:
- 如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用
startService()
函数,则该函数将引发一个IllegalStateException
。 - 新的
Context.startForegroundService()
函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用Context.startForegroundService()
。不过,应用必须在创建服务后的五秒内调用该服务的startForeground()
函数。
针对Service的行为变更定义Service如下:
public class MyService extends Service {
public static final String ACTION = "abcdefg";
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();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel("serviceId", "serviceName", NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(getApplicationContext(), "serviceId").
setSmallIcon(R.drawable.ic_launcher_foreground).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background)).
setContentText("服务运行中")
.setContentTitle("测试服务")
.setWhen(System.currentTimeMillis()).build();
startForeground(1, notification);
}
}
private int time = 0 ;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
time ++ ;
Log.d(MyService.this.getClass().getName(), "计时:" + time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
public static void startService(Context context) {
Intent intent = new Intent(context, MyService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
}