Android 服务

服务
  • 服务是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。
  • 服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程,当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行
  • 服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况
创建一个服务
创建

默认代码
1
2
3
4
5
6
7
8
9
10
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");
}
}

这个服务类是继承于Service类的,其中必须实现抽象方法onBind()
服务中最常见的三个重写方法:

  • onCreate():创建时调用
  • onStartCommand():启动时调用
  • onDestroy():销毁时调用
注册服务(按照上图创建会自动注册)
1
2
3
4
5
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
  • Exported表示是否允许其他程序访问此服务
  • Enabled表示是否启用此服务
启动和停止服务
布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/start"
android:text="启动服务"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stop"
android:text="关闭服务"/>

</LinearLayout>
活动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private Button start;
private Button stop;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

start = findViewById(R.id.start);
stop = findViewById(R.id.stop);
start.setOnClickListener(this);
stop.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start:
Intent intent_start = new Intent(MainActivity.this, MyService.class);
startService(intent_start); //开启服务
break;
case R.id.stop:
Intent intent_stop = new Intent(MainActivity.this, MyService.class);
stopService(intent_stop); //停止服务
break;
}
}
}
修改服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyService extends Service {
private static final String TAG = "MyService";
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(TAG, "onCreate: 创建服务");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: 启动服务");
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: 销毁服务");
}
}
服务和活动通信

通信需要在服务中借助onBind()方法,一般需要创建一个专门的Binder对象管理。

模拟下载功能:

服务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyService extends Service {
private static final String TAG = "MyService";
private DownloadBinder mBinder = new DownloadBinder();

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
......
class DownloadBinder extends Binder {
public void startDownload(){
Log.d(TAG, "startDownload: 开始下载");
}

public int getProgress(){
Log.d(TAG, "getProgress: 获取进度");
return 0;
}
}
}
布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

......
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bind_service"
android:text="绑定服务"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/unbind_service"
android:text="解绑服务"/>


</LinearLayout>
活动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
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 button_start = findViewById(R.id.start);
Button button_stop = findViewById(R.id.stop);
button_start.setOnClickListener(this);
button_stop.setOnClickListener(this);

Button button_bind = findViewById(R.id.bind_service);
Button button_unbind = findViewById(R.id.unbind_service);
button_bind.setOnClickListener(this);
button_unbind.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start:
Intent intent_start = new Intent(this, MyService.class);
startService(intent_start);
break;
case R.id.stop:
Intent intent_stop = new Intent(this, MyService.class);
stopService(intent_stop);
break;
case R.id.bind_service:
Intent intent_bind = new Intent(this, MyService.class);
bindService(intent_bind, connection, BIND_AUTO_CREATE); // 绑定服务
break;
case R.id.unbind_service:
unbindService(connection); // 解绑服务
break;
default:
break;
}
}
}
分析
  • 创建了ServiceConnected匿名类,重写了里面的onServiceConnected()onServiceDisconnected()方法,这两个方法会在绑定成功和连接断开时调用
  • onServiceConnected()Service向下转型为自定义的DownloadBinder实例,然后用这个实例就能调用在DownloadBinder中的任何方法【必须先绑定】

  • bindService()可以将活动和服务绑定,第一个参数是Intent对象【和服务联系起来】,第二个参数是创建好的ServiceConnection实例【指定要绑定的服务】,第三个参数是一个标志位,传入BIND_AUTO_CREATE表示绑定后自动创建服务【onCreate()执行,onStartCommand()不执行【即创建而不启动】】

  • 解绑unBindService(),参数就是绑定好的ServiceConnection实例
  • 一个服务可以与不同的活动绑定,绑定后会获取到相同的实例
服务的声明周期
  • 调用Context.startService()服务就会启动并回调onStartCommand()方法,若服务还未创建过,就会先执行onCreate()
  • 服务启动后一直处于运行状态,直到Context.stopService()stopSelf()被调用
  • 每个服务都只会存在一个实例,不管调用多少次startService(),都只会存在一个实例,关闭只需一次
  • 通过Context.bindService()获取服务的持久连接,就会回调服务的onBind()方法,onBind()返回一个IBinder对象实例,就能实现自由通信了
  • 调用Context.stopService()服务就会被销毁并回调onDestroy()方法
  • 类似的,被绑定的服务执行unbindService()也会被销毁,而且回调onDestroy()
  • 如果一个服务既执行了startService(),也执行了bindService(),那么,销毁这个服务就必须都执行stopService()unbindService()才能被销毁,onDestroy()才能被执行
前台服务
  • 前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果(像天气类app就要用到前台服务)。
  • 前台服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收
Demo code
  • 添加权限
1
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
  • 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: 创建服务");

Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channelId")
.setContentTitle("这是标题")
.setContentText("这是内容")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi);
// 大于Android 8.0的版本适配
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = null;
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationChannel = new NotificationChannel("channelId", "name", NotificationManager.IMPORTANCE_HIGH);// 注意此处channelId要和前面一样
notificationManager.createNotificationChannel(notificationChannel);
}
startForeground(1, builder.build());
}
  • 运行

使用IntentService
  • 主线程处理耗时任务很容易出现ANR(Application Not Responding),所以推荐在每个服务的具体方法里开启子线程,即以下形式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

}
  • 但是有时总是忘记开多线程或者自动停止。为了解决这个问题,Android提供了IntentService类解决这个问题。
Demo
  • 新建一个MyIntentService类继承自IntentService:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService(){
super("MyIntentService"); // 调用父类的有参构造函数
}

@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 打印当前线程的id
Log.d(TAG, "当前线程id是 "+Thread.currentThread().getId());
}

@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: 服务已销毁");
}
}
  • 注册活动
1
<service android:name=".MyIntentService"/>
  • 布局中添加一个按钮
1
2
3
4
5
6
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/intent_service"
android:text="启动 IntentService"
/>
  • 点击事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
......

Button button_IntentService = findViewById(R.id.intent_service);
button_IntentService.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()) {
......
case R.id.intent_service:
Log.d("MainActivity", "主线程id是 "+Thread.currentThread().getId());
Intent intentService = new Intent(this, MyIntentService.class);
startService(intentService);
break;
......
}
}
  • 运行

    可以看到MyIntentService这个服务是在子线程中运行的

0%