Sui

Android-Service详解

字数统计: 1.9k阅读时长: 8 min
2021/01/27 Share

Android-Service详解

Service是安卓的四大组件之一,在我们的日常开发中经常会使用到,例如执行一些后台操作,播放音乐、下载等等。

接下来我们就从一个简单的Service的使用开始,一步一步的深入介绍Service

Service简单使用

创建一个Activity,在Activity中设置两个按钮,用于启动、关闭Service,而在Service中只是打印一些生命周期的Log,并不去做什么事。

MainActivity

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
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private final static String TAG = MainActivity.class.getSimpleName();

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

Button startService = findViewById(R.id.btn_start_service);
Button stopService = findViewById(R.id.btn_stop_service);

startService.setOnClickListener(this);
stopService.setOnClickListener(this);
}

@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, MyService.class);
switch (v.getId()) {
case R.id.btn_start_service:
startService(intent);
break;
case R.id.btn_stop_service:
stopService(intent);
break;
default:
// fall through
}
}
}

activity_main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="2.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">

<Button
android:id="@+id/btn_start_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开启服务"/>

<Button
android:id="@+id/btn_stop_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关闭服务"/>

</LinearLayout>

MyService

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 final static String TAG = MyService.class.getSimpleName();

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onCreate() {
Log.e(TAG, "service onCreate");
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "service onStartCommand");
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
Log.e(TAG, "service onDestroy");
super.onDestroy();
}
}

最后别忘了在AndroidManifest中注册Service

使用一个Service的步骤

  • 创建一个类,继承Service
  • 在AndroidMainfest中注册Service
  • 在Activity中使用startService启动Service

注意我这里使用的是显示启动Service,如果要隐式启动Service,则需要在Intent中指定包名和action,步骤如下

1
2
3
4
intent = new Intent();
intent.setAction("com.sui.SERVICE");
intent.setPackage("com.sui.testapplication");
startService(intent);

不允许直接使用action去启动Service是从Android 5开始,即Android Lollipop

这时我们通过点击启动服务的按钮即可打开Service,看到打印的日志。

如果想要停止服务,则可以使用stopService()传入Intent即可,注意,开启和结束Service的Intent必须为同一个Intent

Service绑定

上面的开启服务方式和调用startService的Activity一点关系都没有,使用startService只是告诉Service一声你可以启动了。如果想要将Activity和Service进行关联,则需要使用Context.bindService(Intent intent, ServiceConnection conn, int flags)

  • 第一个参数的为传入的Intent
  • 第二个参数为connect的状态回调
  • 第三个参数为绑定关系

使用bindService

1
bindService(intent, serviceConnection, BIND_AUTO_CREATE);

intent和上面的一样,只不过这里我们需要去定义一下第二个参数

1
2
3
4
5
6
7
8
9
10
11
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {

}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

实现了一个匿名内部类,该内部类中有两个必须实现的函数,也是非常重要的函数,即onServiceConnectedonServiceDisconnected

  • onServiceConnected表示当Activity与Service建立绑定成功时候的回调函数
  • onServiceDisconnected表示当Activity与Service断开连接时候的回调函数

onServiceConnected方法有一个非常重要的参数IBinder service,这个参数是绑定成功后Service返回的数据,还记不记得我们在Service中必须重写的方法

1
2
3
public IBinder onBind(Intent intent) {
return null;
}

IBinder service参数就是接受来自onBind的返回信息

如果想要返回消息,则需要创建一个类,继承自Binder

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
// Service文件中
public static class MyBinder {
int addPlus(int x, int y) {
return x + y;
}
}

public IBinder onBind(Intent intent) {
return new MyBinder();
}

// Activity文件中
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MyService.MyBinder binder = (MyService.MuBinder)service;

int res = binder.addPlus(1, 1);
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

连接成功后即可在serviceConnection中的onServiceConnected中执行addPlus方法

这里需要注意一下:如果在Service中的onBind方法中没有返回任何信息,即null,那么回调方法onServiceConnectedonServiceDisconnected就不会执行,即使Service与Activity绑定成功了也不会执行

第三个参数flags,他有一个特殊的值,Context.BIND_AUTO_CREATE。这个值特殊的地方就在于当我们绑定Service时,如果Service还未开启,bindService就会开启Service,注意,这时会执行Service的全部与新建相关的生命周期函数

解绑服务只需使用unbindService即可,参数传入与bindService同一定义的ServiceConnectiong

Service的生命周期

Service的生命周期主要有onCreateonStartCommandonDestroy

当我们第一次启动Service的时候,会先执行onCreate -> onStartCommand

当我们一直在执行startService时,onCreate只会执行一次,而onStartCommand会每次都执行。

当我们使用bindService的时候,onCreateonStartCommand都不会执行(如果指定了BIND_AUTO_CREATE时且没有创建Service则会都执行)

最后我们使用stopService的时候,会执行onDestroy方法

如果我们在开启服务后,又使用bindService绑定Service,这时我们再关闭服务(stopService),可以看到onDestroy是不会执行的(即Service不会关闭),只有当前服务的所有绑定都解绑了(所有的绑定执行了unbindService),然后使用stopService才会销毁Service

跨进程Service

Service可以单独放在一个进程之中,只需要在AndroidMainfest文件中指定android:process即可

1
2
3
4
5
<application>
......
<service android:process=":remote" android:name=".MyService" />
......
</application>

可以在另一个进程的Activity中开启其他进程的Service,还是使用startService即可,关闭也可以使用stopService

如果使用bindService绑定其他进程的Service,这就涉及到唤醒Service了,需要使用隐式启动Service,显示启动无法获取到类的字节码信息(也就无法传入Intent)。

1
2
3
4
intent = new Intent();
intent.setAction("com.sui.SERVICE");
intent.setPackage("com.sui.testapplication");
bindService(intent, connect, BIND_AUTO_CREATE);

此时如果想要使用binder进行通信,可以使用Android的跨进程通信aidl,创建一个aidl文件

1
2
3
4
package com.sui.mutiprocessapplication;
interface IMyService {
int addPlus(int x, int y);
}

有以下几个注意点:

  • interface 前不要加public
  • 函数的返回值前也不要加访问限制修饰符

Android Studio中将项目重新rebuild即可(Build -> Rebuild Project),即可生成对应的接口文件

一切准备就绪了,下面我们可以在Service的onBind方法中返回实例化自定义aidl接口的类

1
2
3
4
5
6
7
8
9
10
11
12
public iBinder onBind(Intent intent) {
return myBinder;
}

private IMyService.Stub.myBinder = new IMyService.Stub() {

// 实现aidl接口中的自定义函数
@Override
public int addPlus(int x, int y) {
return x + y
}
}

在Activity中的ServiceConnect中调用addPlus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ServiceConnect connect = new ServiceConnection() {
@Override
public void onServiceConnection(ComponentName name, IBinder service) {
IMyService binder = IMyService.Stub.asInterface(service);

int res = binder.addPlus(1, 2);
}

@Override
public void onServiceDisconnected(ComponentName name) {


}
}

以上我们就完成了一次跨进程的Activity与Service之间的通信。

关于Service的一些问题

1.Service与Thread有什么区别?

在Service中不要进行一些耗时的操作,Service如果没有单独的开启一个进程,那么它是运行在主线程中的,即UI线程,执行耗时的操作可能会引发ANR,引入Service的目的是为了执行一些后台的操作,例如下载播放音乐等。如果在Service中执行耗时的操作,应该在其中开启一个线程。

关于Service的使用会在以后的操作过程中去慢慢更新,当前时间2021年1月27日

参考链接

CATALOG
  1. 1. Android-Service详解
  2. 2. Service简单使用
  3. 3. Service绑定
  4. 4. Service的生命周期
  5. 5. 跨进程Service
  6. 6. 关于Service的一些问题
  7. 7. 参考链接