在之前,我已经详细讲解过了Android的四大组件之一的Activity
,那么这一次我将讲解一个与它比较相似的service
。
Service简介
Service翻译过来就是服务,是Activity的四大组件之一。相同于Activity一样,它有自己的生命周期,创建配置方式也很相似。不同点在于,Service长期运行于后台,用于执行长期运行但并不和用户交互的任务。
所以当某个程序组件需要在运行时需要与用户进行交互,需要提供某种界面时,这时就是用Activity;如果不需要与用户交互,只需要运行于后台,像后台下载东西或是后台播放音乐等等,此时就应该考虑使用Service。
Service的创建及配置
Service的创建配置过程和Activity几乎相似,但因为Service不需要与用户交互的界面,所以就省去了Activity三部曲的最后一步。
第一步定义一个继承Service的子类
第二步就是在AndroidMainifest进行声明注册,(四大组件的使用都需要在清单文件中进行配置。)只需要在<application></application>
中添加<service../>
子元素即可配置。
配置Service时也可以给它指定<intent-filter.../>
,用来说明该Service可以被哪些intent启动。
在配置过程中我们可能会经常接触到的一些属性
-
- name:指定该Service实现类类名
- permission:指定启动该Service所需的权限
- exported:指定该Service能否被其他app所启动
- process:指定该Service所处的进程
Service生命周期
因为Service是运行于后台的程序,它不需要与用户进行交互,所以也就没有Activity中的onResume、onPause这些回调。
并且启动Service的方式不同也会导致Service的生命周期有差异,我们下面来看两种不同的启动方式
通过startService来启动
先在TestService
类中重写好回调的方法,并在每个回调方法中打出对应的日志。
然后在MainActivity通过两个Button进行点击测试,一个用于开启Service,另一个用于关闭服务
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_start_service:
startService(new Intent(this, ServiceTest.class));
break;
case R.id.btn_stop_service:
stopService(new Intent(this,ServiceTest.class));
break;
}
}
点击开启服务时
点击停止服务时
在第一次点击开启服务时,会回调onCreate()
方法和onStartCommand()
方法,如果再次点击的话,就只会回调onStartCommand()
方法。onCreate()
只有在第一次开启时才会被回调,只有服务停止后再次点击开启服务后才会被回调。
通过startService启动服务所经历的生命周期:onCreate()--->onStartCommand()--->onDestroy()
通过绑定服务启动
如果通过startService()的方式来启动服务的话,服务与访问者之间是无法进行通信和交换数据的。
假设现在还是通过startService()来启动服务,在TestService中定义要给方法可以给外界去调用,然后调用这个方法。
//在TestService中定义一个方法
public void serviceMethod(){
Toast.makeText(this,"调用服务内部方法",Toast.LENGTH_SHORT).show();
}
//开启服务后,直接调用
case R.id.btn_communication:
ServiceTest serviceTest = new ServiceTest();
serviceTest.serviceMethod();
break;
此时如果以上面的方式调用这个方法程序就会直接崩掉
所以如果想让Service和访问者之间进行方法调用或交换数据,我们就要通过绑定服务的方式来启动Service。
case R.id.btn_bind_service:
//绑定服务
bindService(new Intent(this, ServiceTest.class),mConnection ,BIND_AUTO_CREATE);
break;
case R.id.btn_unbind_service:
//解绑服务
unbindService(mConnection);
break;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//当服务连接成功后回调
Log.d(TAG, "onServiceConnected....");
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
//断开连接后回调
Log.d(TAG, "onServiceDisconnected.....");
}
};
上面第一块代码中的bindService(Intent service, ServiceConnection conn, int flags)
就是用来绑定服务的,下面来解释下该方法的三个参数:
- service:通过Intent指定要启动的Service
- conn:ServiceConnection对象,看上面第二块代码,是用于监听访问者与Service之间的连接情况的。当链接成功时就会回调
onServiceConnected(ComponentName componentName, IBinder iBinder)
,断开后回调onServiceDisconnected(ComponentName componentName)
。所以必须重写这两个方法来进行对应的操作。 - flags:指定绑定时是否创建Service,上面的
BIND_AUTO_CREATE
就是自动创建
写完上面后还是不够,此时我们还是不能绑定服务,还差最后一步。注意到连接成功后回调的onServiceConnected(...)
方法中有个IBinder的对象,也就是说我们还需要提供一个IBinder对象。回到最开始我们创建一个继承Service类的子类的时候,我们是需要重写一个IBinder onBind(Intent intent)
方法的,这个方法所返回的IBinder对象就是会传入onServiceConnected(...)
中(当其返回null的时候是不会触发onServiceConnected(...)
的),这样就可以进行连接通信了。
public static class InnerBinder extends Binder{
...
}
//Service被绑定时回调
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind...");
return new InnerBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind....");
return super.onUnbind(intent);
}
点击绑定服务
点击解绑服务
此时我们再回到上面方法调用的问题,这一次我们不直接暴露要调用的方法,而是在InnerBinder中调用,在InnerBinder中暴露一个方法。
public class InnerBinder extends Binder{
public void doServiceMethod(){
serviceMethod();
}
}
private void serviceMethod(){
Toast.makeText(this,"调用服务内部方法",Toast.LENGTH_SHORT).show();
}
然后我们需要在onServiceConnected(...)
中去获取这个IBinder对象
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mBinder = (ServiceTest.InnerBinder) iBinder;
//当服务连接成功后回调
Log.d(TAG, "onServiceConnected....");
}
此时当我们绑定服务之后,我们就可以和Service进行通信,调用其内部方法
可以看到,通过bindService()启动服务的生命周期:onCreate()--->onBind()--->onUnbind()--->onDestroy()
由此我们可以得出两种启动方式的优缺点
启动方式 | 优点 | 缺点 |
---|---|---|
startService() | 服务可以长期运行在后台 | 无法进行通信 |
bindService() | 可以进行通信 | 不能保证服务可以长期运行于后台,解绑后服务停止运行。当我们绑定服务后,点击返回键,通过日志可以发现服务也会自己销毁。 |
通过分析二者的优缺点,其实我们可以发现二者之间是可以进行互补的,这就是接下来要讲的混合启动服务。
混合启动服务
步骤:
- 开启服务:startService(),确保服务可以长期在后台运行
- 绑定服务:onBind(),为了使服务能够进行通讯
- 调用服务内部的方法
- 解绑服务:unBindService()
- 停止服务:stopService()
特点:
- 开启服务,然后绑定服务,如果不取消绑定,就无法停止服务
2. 开启服务以后,多次绑定-解绑服务,服务不会被停止
AIDL 跨进程通信
android interface definition language 安卓接口定义语言
在Android中,每一个应用程序都运行在自己的进程中,它们一般都是相互独立的,无法直接进行跨进程通信。所以Android提供了AIDL,它就如同一个桥梁,为两个独立的应用程序建立联系。我们可以通过ALDL来制定一些规则,规定不同程序之间可以进行那些通信。
想要AIDL实现跨进程通讯,首先需要创建AIDL文件。我们先创建一个新项目作为服务端
打开ITest.aidl文件定义好接口
我们可以发现,AIDL接口语法与Java接口语法是十分相似的。创建完成后,我们要先make project,Android Studio会自动为该AIDL接口生成实现类
这个自动生成的实现类,我们可以看到里边有个Stub类继承了Binder,也就说明我们之后定义的Service类中的onBind()方法应该是返回这个的子类实例
然后定义一个Service实现类,除了onBind()返回的IBinder对象不同,其它操作和开发本地Service是一样的。
public class AIDLService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
TestBinder testBinder = new TestBinder();
return testBinder;
}
class TestBinder extends ITest.Stub{
@Override
public String getMsg() throws RemoteException {
return "跨进程调用成功";
}
}
}
在配置文件中进行配置
关于上面的配置,在之前Activity中是有讲过的。那么此时服务端就搭建好了,此时我们在新建一个项目当作客户端用来进行访问。
AIDL接口定义的是两个进程之间的通信接口,所以客户端也需要服务端定义的AIDL接口。那么我们需要先将服务端的AIDL文件复制过去。客户端这边主要是调用服务端的getMsg()方法,我们在布局文件中设置一个TextView和Button,点击Button后TextView会显示getMsg()返回的字符串。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试"
android:textSize="30sp"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="远程getMsg"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:onClick="getMsg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity中的代码:
public class MainActivity extends AppCompatActivity {
private ITest testService;
private static final String TAG = "MainActivity";
private TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindTestService();
mTv = findViewById(R.id.tv_test);
}
private void bindTestService() {
Intent intent = new Intent();
//启动服务端的Service
intent.setAction("com.example.aidltestservice.action.ACTION_TEST");
intent.setPackage("com.example.aidltestservice");
boolean b = bindService(intent, mConnection, BIND_AUTO_CREATE);
Log.d(TAG, "bindTestService: isBind---->" + b);
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d(TAG, "onServiceConnected...");
testService = ITest.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
testService = null;
}
};
public void getMsg(View view){
//Toast.makeText(this,"hhhh",Toast.LENGTH_SHORT).show();
try {
Log.d(TAG, "getMsg: ---->" + testService.getMsg());
mTv.setText(testService.getMsg());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
通过上面代码,其实可以看出跨进程绑定服务和本地绑定服务的步骤是几乎一样的,只是在onServiceConnected(...)
中,跨进程绑定Service并不是直接去获取onBind()所返回的对象,这里我们上面也说过,远程Service的onBind()方法返回的IBInder对象和本地绑定Service时返回的是不同的。远程的只是将IBinder对象的代理传给了客户端这边onServiceConnected(...)
的第二个参数。因此这里需要用Stub提供的方法ITest.Stub.asInterface(iBinder)
。
那么此时两边应用都启动后,我们在客户端点击按钮,就能与服务端进行跨进程通信。
总结
上面主要讲解了下Service的作用、如何配置创建Service、启动Service的不同方式及其对应的生命周期、最后拓展了下AIDL跨进程通信。AIDL这块平常使用的比较少,作为了解就好,我接触的也不多,所以也不能在这块做很详细的说明,感兴趣的同学可以自己去了解。