windows process activation service 通信_Android四大组件——Service篇

113a809af813c979be8aa5645e8a78a6.png

04344b5829c5fa9c71988ffe0746bd4e.png
android四大组件

在之前,我已经详细讲解过了Android的四大组件之一的Activity,那么这一次我将讲解一个与它比较相似的service

Service简介

Service翻译过来就是服务,是Activity的四大组件之一。相同于Activity一样,它有自己的生命周期,创建配置方式也很相似。不同点在于,Service长期运行于后台,用于执行长期运行但并不和用户交互的任务。

所以当某个程序组件需要在运行时需要与用户进行交互,需要提供某种界面时,这时就是用Activity;如果不需要与用户交互,只需要运行于后台,像后台下载东西或是后台播放音乐等等,此时就应该考虑使用Service。

Service的创建及配置

Service的创建配置过程和Activity几乎相似,但因为Service不需要与用户交互的界面,所以就省去了Activity三部曲的最后一步。

第一步定义一个继承Service的子类

第二步就是在AndroidMainifest进行声明注册,(四大组件的使用都需要在清单文件中进行配置。)只需要在<application></application>中添加<service../>子元素即可配置。

2d84d49f83e972d015660eb649fe33c1.png

配置Service时也可以给它指定<intent-filter.../>,用来说明该Service可以被哪些intent启动。
在配置过程中我们可能会经常接触到的一些属性

    1. name:指定该Service实现类类名
    2. permission:指定启动该Service所需的权限
    3. exported:指定该Service能否被其他app所启动
    4. process:指定该Service所处的进程

Service生命周期

因为Service是运行于后台的程序,它不需要与用户进行交互,所以也就没有Activity中的onResume、onPause这些回调。

并且启动Service的方式不同也会导致Service的生命周期有差异,我们下面来看两种不同的启动方式

通过startService来启动

先在TestService类中重写好回调的方法,并在每个回调方法中打出对应的日志。

bb7b5ad7280b20b54b0c37be5cb01d6a.png

然后在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;
     }
 }

点击开启服务时

958038201f06bd154fe9ea0de3aaa746.png

点击停止服务时

4c0aa809a6f72a735be7280cb53b7078.png

在第一次点击开启服务时,会回调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);
 }

点击绑定服务

5462fa2dd3f0c3c188260151a5ac8917.png

点击解绑服务

8d10492cbba3ba0c993f28631585a3ad.png

此时我们再回到上面方法调用的问题,这一次我们不直接暴露要调用的方法,而是在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进行通信,调用其内部方法

c92b675f4411b2f330fa4611c35628ab.png

07708421ec2e1b0b0c2d92f6cb15eb25.png

可以看到,通过bindService()启动服务的生命周期:onCreate()--->onBind()--->onUnbind()--->onDestroy()

由此我们可以得出两种启动方式的优缺点

启动方式优点缺点
startService()服务可以长期运行在后台无法进行通信
bindService()可以进行通信不能保证服务可以长期运行于后台,解绑后服务停止运行。当我们绑定服务后,点击返回键,通过日志可以发现服务也会自己销毁。

通过分析二者的优缺点,其实我们可以发现二者之间是可以进行互补的,这就是接下来要讲的混合启动服务。

混合启动服务

步骤:

  1. 开启服务:startService(),确保服务可以长期在后台运行
  2. 绑定服务:onBind(),为了使服务能够进行通讯
  3. 调用服务内部的方法
  4. 解绑服务:unBindService()
  5. 停止服务:stopService()

特点:

  1. 开启服务,然后绑定服务,如果不取消绑定,就无法停止服务

2. 开启服务以后,多次绑定-解绑服务,服务不会被停止

AIDL 跨进程通信

android interface definition language 安卓接口定义语言

在Android中,每一个应用程序都运行在自己的进程中,它们一般都是相互独立的,无法直接进行跨进程通信。所以Android提供了AIDL,它就如同一个桥梁,为两个独立的应用程序建立联系。我们可以通过ALDL来制定一些规则,规定不同程序之间可以进行那些通信。

想要AIDL实现跨进程通讯,首先需要创建AIDL文件。我们先创建一个新项目作为服务端

e86b2adc5133a773824766bd1e042494.png

打开ITest.aidl文件定义好接口

e6ef4a034c9273821b74365dbe22ca4c.png

我们可以发现,AIDL接口语法与Java接口语法是十分相似的。创建完成后,我们要先make project,Android Studio会自动为该AIDL接口生成实现类

9e6aaf957a94cc5f0aa2fe608aad7aeb.png

这个自动生成的实现类,我们可以看到里边有个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 "跨进程调用成功";
         }
     }
 }

在配置文件中进行配置

fe010129a9602a5c0664b1f64dc8de96.png

关于上面的配置,在之前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)

那么此时两边应用都启动后,我们在客户端点击按钮,就能与服务端进行跨进程通信。

ed35f86dd02aa536149e2dec75ce140e.png
点击前

33361c7ef6eb86456369f3f0fbed6835.png
点击后

总结

上面主要讲解了下Service的作用、如何配置创建Service、启动Service的不同方式及其对应的生命周期、最后拓展了下AIDL跨进程通信。AIDL这块平常使用的比较少,作为了解就好,我接触的也不多,所以也不能在这块做很详细的说明,感兴趣的同学可以自己去了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值