基于AIDL编程实现Android远程Service服务

前言

Service 作为 Android 四大组件之一,应用非常广泛。

远程服务与本地服务最大的区别是:远程 Service 与调用者不在同一个进程里(即远程 Service 是运行在另外一个进程);而本地服务则是与调用者运行在同一个进程里。二者区别的详细区别如下图:

在这里插入图片描述

本地Service

先来看看 Android 普通 Service 的使用。

在这里插入图片描述

异步消息处理机制

郭霖解释得很清晰:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

服务与活动的通信

直接看看《Android第一行代码》里面的示例:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

【圈重点】service 服务的 onBind() 方法通过绑定自定义的 Binder 继承类对象,结合服务与活动绑定后将自动调用执行的 onServiceConnected() 函数,可定义活动与服务的交互动作。

远程Service

Android 系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。为了使其他的应用程序也可以访问本应用程序提供的服务,Android 系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于 RPC 的解决方案一样,Android 使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。为了克服 Linux 中 IPC (Inter-Process Communication,即跨进程通信)各种方式的缺点,在 Android 中引入了 Binder 机制,而 AIDL 即是 Binder 机制实现 Android IPC 时使用的工具。

【More】关于 Android 跨进程通信:Android跨进程通信Binder机制与AIDL实例

Binder与AIDL基础

Binder 与 AIDL 的作用范围:

  1. Binder:如果是在一个应用里实现远程调用,使用Binder即可,没必要使用AIDL(比如上面演示的本地Service代码);
  2. AIDL:如果涉及到在多个应用程序之间使用IPC通信,并且在服务又有多线程业务处理,这时可以使用AIDL。

Binder 跨进程通信机制 模型 基于 Client - Server 模式:
在这里插入图片描述

Client-ServiceManager-Server时序图如下:

在这里插入图片描述

Binder 从代码的角度来说,是 Android 系统源码中的一个类,它实现了 IBinder 接口;从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式;从 Android Framework 角度来讲,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager 等等)和相应 ManagerService 的桥梁;从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以和服务端进行通信,这里的服务包括普通服务和基于 AIDL 的服务

AIDL Service服务端

在多进程通信中,存在两个进程角色(以最简单的为例):服务器端和客户端。以下是两个进程角色的具体使用步骤:

服务器端(Service)

  1. 新建定义AIDL文件,并声明该服务需要向客户端提供的接口;
  2. 在 Service 子类中定义内部类 A 实现 AIDL 中定义的接口方法,并定义 Service 生命周期的方法(onCreat()、onBind()等),重点是在 onBind() 方法中 return 返回 A 类的对象;
  3. 在 AndroidMainfest.xml 中注册服务且声明为远程服务。

客户端(Client)

  1. 拷贝服务端的 AIDL 文件到 aidl 目录下;
  2. 通过Intent指定服务端的服务名称和所在包,绑定远程Service;
  3. 使用 Stub.asInterface 接口获取服务器的 Binder,根据需要调用服务提供的接口方法。

接下来,通过一个具体实例来介绍远程 Service 的使用。主要实现的是通过 AIDL 自定义的远程服务,给客户端 APP 提供登录接口和图书价格查询的接口。

(1)新建一个带了空 MainActivity 的 Android 项目 com.bwshen.aidlservice,先来看下最终的项目结构(需要编写的文件有 aidl 文件 IMyBookManager.aidl 、图书类 book.java、服务类 AIDLService.java):

在这里插入图片描述

(2)先来创建 aidl 接口文件 IMyBookManager.aidl ,在 app 上点击右键->新建->AIDL->AIDL文件(将自动创建 aidl 文件夹),定义两个服务接口分别为用于登录的 login() 和用于图书查询的 queryByName(),具体代码如下:

// IMyBookManager.aidl
package com.bwshen.aidlservice;

// Declare any non-default types here with import statements
import com.bwshen.aidlservice.Book;

//需要说明我们引入的book 是个序列化的类。
parcelable Book;

interface IMyBookManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);

    String login(String userName, String pwd);

    Book queryByName(String bookName);
}

(3)创建图书类 book.java 文件,由于需要通过 AIDL 接口返回给调用者,所以这个类需要进行序列化,故需要实现 Parcelable 接口:

package com.bwshen.aidlservice;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    private int price;
    private String name = "";

    public Book(){}

    protected Book(Parcel in) {
        price = in.readInt();
        name = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "\n图书名称:" + name + "\n图书价格:" + price;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(price);
        dest.writeString(name);
    }
}

(4)此时对项目进行编译 Build->Make Project,系统便会帮我们自动生成步骤 1 的图示所标出来的 com/bwshen/aidlservice/IMyBookManager.java 文件,该文件的内容及作用此处暂时先忽略,本文文末会单独重点分析。

(5)创建 AIDLService 类,并使用内部类 MyBookManager 继承 IMyBookManager.Stub、实现 IMyBookManager.aidl 对外提供的接口,并通过 service 的 onbind() 函数返回 MyBookManager 类的对象,如下:

package com.bwshen.aidlservice.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import com.bwshen.aidlservice.Book;
import com.bwshen.aidlservice.IMyBookManager;

public class AIDLService extends Service {
    MyBookManager bookManager = new MyBookManager();

    public AIDLService() {
    }

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

    public class MyBookManager extends IMyBookManager.Stub {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public String login(String userName, String pwd) throws RemoteException {
            if(userName.equalsIgnoreCase("admin") && pwd.equalsIgnoreCase("123456")){
                return "success";
            }
            return "error";
        }

        @Override
        public Book queryByName(String bookName) throws RemoteException {
            Book book = new Book();
            book.setName(bookName);
            book.setPrice(100);
            return book;
        }
    }
}

(6)最后在 AndroidManifest.xml 中将服务配置为远程服务:

<service
   android:name=".service.AIDLService"
   android:enabled="true"
   android:exported="true"
   android:process=":remote">
   <intent-filter>
       <action android:name="com.bwshen.aidlservice.bookService"/>
       <category android:name="android.intent.category.DEFAULT" />
   </intent-filter>
</service>

至此,提供远程 Service 服务的 APP 准备完毕。可以修改下主活动的页面,展示如下:

在这里插入图片描述

AIDL Service客户端

搭建完远程 Service 服务端 APP,下面来搭建一个连接远程 Service 并调用服务接口实现登录、图书价格查询功能的客户端 APP。

(1)同样新建一个空 Activity 的 Android 项目 com.bwshen.aidlclient,先来看看最终项目完整的结构:

在这里插入图片描述

(2)由上可知整个项目中需要自行编写的三个文件(MainActivity.java、IMyBookManager.aidl、Book.java)有两个直接从服务端 APP 完整拷贝过来即可,而 IMyBookManager.java 文件则由编译项目时自动生成、无需理会,故这里主要看下 MainActivity 的代码(需注意下面代码会导入 import com.bwshen.aidlservice.IMyBookManager,故需要拷贝完 IMyBookManager.aidl 文件后先编译下项目):

package com.bwshen.aidlclient;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.bwshen.aidlservice.Book;
import com.bwshen.aidlservice.IMyBookManager;

public class MainActivity extends AppCompatActivity {

    private Button btnConnection;
    private EditText etName;
    private EditText etPwd;
    private Button btnLogin;
    private TextView tvBookInfo;
    private Button btnQuery;
    private EditText etBookName;
    private boolean islogin=false;
    /**
     * 该类的导入,需要先编译项目,将aidl文件进行编译后映射,才能成功识别并导出com.bwshen.aidlservice.IMyBookManager
     */
    private IMyBookManager bookManager = null;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookManager = IMyBookManager.Stub.asInterface(service);
            Toast.makeText(MainActivity.this, "服务连接成功!", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bookManager = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        etBookName = findViewById(R.id.etBookName);
        etName = findViewById(R.id.etName);
        etPwd = findViewById(R.id.etPwd);
        btnLogin = findViewById(R.id.btnLogin);
        btnConnection = findViewById(R.id.btnConnection);
        btnQuery = findViewById(R.id.btnQuery);
        tvBookInfo = findViewById(R.id.tvBookInfo);
        tvBookInfo.setMovementMethod(ScrollingMovementMethod.getInstance());
        connect();
    }

    private void connect(){
        btnConnection.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(bookManager != null){
                    Toast.makeText(MainActivity.this, "服务连接成功!", Toast.LENGTH_SHORT).show();
                }else{
                    Intent intent = new Intent();
                    intent.setAction("com.bwshen.aidlservice.bookService");
                    intent.setPackage("com.bwshen.aidlservice");
                    bindService(intent, conn, Context.BIND_AUTO_CREATE);
                }
            }
        });

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(bookManager == null)
                {
                    Toast.makeText(MainActivity.this, "还没有绑定服务!", Toast.LENGTH_SHORT).show();
                    return;
                }
                if(etName.getText().toString().isEmpty() || etPwd.getText().toString().isEmpty()){
                    Toast.makeText(MainActivity.this, "用户名或密码不能为空!", Toast.LENGTH_SHORT).show();
                    return;
                }
                try {
                    String resStr = bookManager.login(etName.getText().toString(), etPwd.getText().toString());
                    if(resStr.compareToIgnoreCase("success") == 0){
                        islogin=true;
                        Toast.makeText(MainActivity.this, "登录成功!", Toast.LENGTH_SHORT).show();
                    } else{
                        Toast.makeText(MainActivity.this, "登录失败!", Toast.LENGTH_SHORT).show();
                        throw new RemoteException("登录失败");
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                    Toast.makeText(MainActivity.this, "登录失败!", Toast.LENGTH_SHORT).show();
                }
            }
        });

        btnQuery.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(bookManager == null || !islogin)
                {
                    Toast.makeText(MainActivity.this, "请先绑定服务并登录!", Toast.LENGTH_LONG).show();
                    return;
                }
                if(etBookName.getText().toString().isEmpty()){
                    Toast.makeText(MainActivity.this, "请输入您要查找的图书!", Toast.LENGTH_LONG).show();
                    return;
                }
                try {
                    Book book = bookManager.queryByName(etBookName.getText().toString());
                    if(book != null){
                        tvBookInfo.append(book.toString());
                    } else{
                        throw new RemoteException("查询失败!");
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                    tvBookInfo.append("查询失败!");
                }
            }
        });
    }
}

上述代码可以看到调用远程 Service 的几个关键流程节点:

  1. 通过 Intent 指定服务端的服务名称和所在包,bindService() 绑定远程 Service;
  2. 重写 onServiceConnected() 函数,使用 Stub.asInterface 接口获取远程服务的 Binder;
  3. 根据需要调用远程服务提供的接口方法 login()、queryByName()。

最后附上 activity_main.xml:

<?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">

    <Button
        android:id="@+id/btnConnection"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="140dp"
        android:layout_marginTop="52dp"
        android:text="连接服务测试"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="140dp" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="56dp"
        android:layout_marginLeft="56dp"
        android:layout_marginTop="156dp"
        android:text="账户:"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginLeft="4dp"
        android:layout_marginTop="52dp"
        android:text="密码:"
        app:layout_constraintStart_toStartOf="@+id/textView2"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <EditText
        android:id="@+id/etName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="用户名"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toBottomOf="@+id/textView2"
        app:layout_constraintStart_toEndOf="@+id/textView2"
        app:layout_constraintTop_toTopOf="@+id/textView2" />

    <EditText
        android:id="@+id/etPwd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="密码"
        android:inputType="textPassword"
        app:layout_constraintBottom_toBottomOf="@+id/textView3"
        app:layout_constraintStart_toStartOf="@+id/etName"
        app:layout_constraintTop_toTopOf="@+id/textView3" />

    <Button
        android:id="@+id/btnLogin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="160dp"
        android:layout_marginLeft="160dp"
        android:layout_marginTop="40dp"
        android:text="登录"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView3" />

    <TextView
        android:id="@+id/tvBookInfo"
        android:scrollbarStyle="outsideInset"
        android:layout_width="303dp"
        android:layout_height="141dp"
        android:layout_marginTop="36dp"
        android:gravity="start|top"
        android:text="查询到的图书信息:"
        android:textSize="16dp"
        android:maxLines="5"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.555"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etBookName" />

    <TextView
        android:id="@+id/textView5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="60dp"
        android:text="书名:"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnLogin"
        android:layout_marginLeft="16dp" />

    <Button
        android:id="@+id/btnQuery"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginLeft="20dp"
        android:text="查询"
        app:layout_constraintBottom_toBottomOf="@+id/textView5"
        app:layout_constraintStart_toEndOf="@+id/etBookName"
        app:layout_constraintTop_toTopOf="@+id/textView5"
        app:layout_constraintVertical_bias="0.517" />

    <EditText
        android:id="@+id/etBookName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginLeft="4dp"
        android:ems="10"
        android:hint="输入要查询的书名"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toBottomOf="@+id/textView5"
        app:layout_constraintStart_toEndOf="@+id/textView5"
        app:layout_constraintTop_toTopOf="@+id/textView5"
        app:layout_constraintVertical_bias="0.538" />
</androidx.constraintlayout.widget.ConstraintLayout>

下面来看下客户端 APP 的效果图:

在这里插入图片描述

Binder工作原理解析

AIDL 即 Android Interface Definition Language(安卓接口定义语言),当我们创建了这个接口后,编译项目时系统会自动生成其对应的 Binder 类:

  • 它继承了 IInterface, 内部有一个静态抽象类 Stub 和 Stub 内部的 Proxy 类,其中 Stub 继承了 Binder 类,所以 AIDL 中的 Stub 即为一个 Binder 对象;
  • 在服务端实现该接口后,支持在客户端远程调用(RPC);
  • 一个 IPC 请求发起时,首先会调用 Proxy 去连接 Binder 驱动,然后 Binder 驱动再去连接 Stub;
  • 要实现跨进程通信,两个进程必须要有相同的 AIDL 接口。
  • AIDL 的作用说白了就是用来自动生成 Binder 相关接口代码的,而不需要开发者手动编写

Binder 提供了两个与 Binder 驱动通信的重要接口方法,你可以通过它实现自定义的 RPC 协议:

  1. Transact():客户端调用,用于发送调用请求;
  2. onTransact():服务端响应,用于接收调用请求。

在这里插入图片描述

我们来回顾下前面的程序编写,编译程序时由 IMyBookManager.aidl 自动生成的 IMyBookManager.java 文件:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.bwshen.aidlservice;
public interface IMyBookManager extends android.os.IInterface{
  /** Default implementation for IMyBookManager. */
  public static class Default implements com.bwshen.aidlservice.IMyBookManager
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
    {
    }
    @Override public java.lang.String login(java.lang.String userName, java.lang.String pwd) throws android.os.RemoteException
    {
      return null;
    }
    @Override public com.bwshen.aidlservice.Book queryByName(java.lang.String bookName) throws android.os.RemoteException
    {
      return null;
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.bwshen.aidlservice.IMyBookManager
  {
    private static final java.lang.String DESCRIPTOR = "com.bwshen.aidlservice.IMyBookManager";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.bwshen.aidlservice.IMyBookManager interface,
     * generating a proxy if needed.
     */
    public static com.bwshen.aidlservice.IMyBookManager asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.bwshen.aidlservice.IMyBookManager))) {
        return ((com.bwshen.aidlservice.IMyBookManager)iin);
      }
      return new com.bwshen.aidlservice.IMyBookManager.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_basicTypes:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          long _arg1;
          _arg1 = data.readLong();
          boolean _arg2;
          _arg2 = (0!=data.readInt());
          float _arg3;
          _arg3 = data.readFloat();
          double _arg4;
          _arg4 = data.readDouble();
          java.lang.String _arg5;
          _arg5 = data.readString();
          this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_login:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          java.lang.String _arg1;
          _arg1 = data.readString();
          java.lang.String _result = this.login(_arg0, _arg1);
          reply.writeNoException();
          reply.writeString(_result);
          return true;
        }
        case TRANSACTION_queryByName:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          com.bwshen.aidlservice.Book _result = this.queryByName(_arg0);
          reply.writeNoException();
          if ((_result!=null)) {
            reply.writeInt(1);
            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
          }
          else {
            reply.writeInt(0);
          }
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.bwshen.aidlservice.IMyBookManager
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      /**
           * Demonstrates some basic types that you can use as parameters
           * and return values in AIDL.
           */
      @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(anInt);
          _data.writeLong(aLong);
          _data.writeInt(((aBoolean)?(1):(0)));
          _data.writeFloat(aFloat);
          _data.writeDouble(aDouble);
          _data.writeString(aString);
          boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public java.lang.String login(java.lang.String userName, java.lang.String pwd) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.lang.String _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(userName);
          _data.writeString(pwd);
          boolean _status = mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().login(userName, pwd);
          }
          _reply.readException();
          _result = _reply.readString();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      @Override public com.bwshen.aidlservice.Book queryByName(java.lang.String bookName) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        com.bwshen.aidlservice.Book _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(bookName);
          boolean _status = mRemote.transact(Stub.TRANSACTION_queryByName, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().queryByName(bookName);
          }
          _reply.readException();
          if ((0!=_reply.readInt())) {
            _result = com.bwshen.aidlservice.Book.CREATOR.createFromParcel(_reply);
          }
          else {
            _result = null;
          }
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.bwshen.aidlservice.IMyBookManager sDefaultImpl;
    }
    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    static final int TRANSACTION_queryByName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    public static boolean setDefaultImpl(com.bwshen.aidlservice.IMyBookManager impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.bwshen.aidlservice.IMyBookManager getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
  public java.lang.String login(java.lang.String userName, java.lang.String pwd) throws android.os.RemoteException;
  public com.bwshen.aidlservice.Book queryByName(java.lang.String bookName) throws android.os.RemoteException;
}

以上就是 AIDL 帮我们自动生成的 Binder 类,接下来我们要利用这个类来分析 Binder 的工作原理。

首先可以看到系统为我们生成了一个 IMyBookManager 接口,它继承了 IInterface 这个接口(所以这里要注意下,所有可以在 Binder 中传输的接口,都需要继承 IInterface 接口),接下来分析下该类的工作机制。仔细看,可以发现,该类主要分成三个部分:

  • 定义自身方法( login 方法和 queryByName 方法);
  • 内部静态类 Stub,该类继承 Binder,同时实现 IMyBookManager 接口;
  • Stub 的内部代理类 Proxy,也实现 IMyBookManager 接口;

第一部分我们不用管它,主要看 Stub 类和 Proxy 类。

  1. 在 Stub 中,声明了 asInterface 方法,该方法用于将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象,该方法通过调用 Binder 的 queryLocalInterface 方法,判断客户端和服务端是否处于同一进程,如果客户端、服务端处于同一进程,那么此方法直接返回服务端的 Stub,否则,返回 Stub 的代理对象 Proxy。
  2. 接下来是 Stub 的代理类 Stub.Proxy,由上面的分析可知,Stub.Proxy 是运行在客户端的(由 asInterface 方法返回给客户端的对象),Stub.Proxy 对象创建后,持有服务端的 Binder 对象,用于客户端请求时调用服务端方法进行远程调用。

客户端在向服务端发起请求时,调用 Stub.Proxy 的相应方法,Stub.Proxy 方法的流程如下:

  • 首先创建该方法所需要的输入型 Parcel 对象 _data、输出型对象 _reply 和返回值对象(如果有);
  • 然后把该方法的参数信息写入 _data 中(如果有);
  • 接着调用 transact 方法进行 RPC(远程过程调用)请求,同时当前线程挂起;
  • 然后在 transact 方法通过服务端的 Binder 对象调用服务端的 onTransact 方法,即 Stub 中的 onTransact 方法;
  • onTransact 方法返回后,当前线程继续执行,并从 _reply 中取出 RPC 过程返回的结果;
  • 最后返回 _reply 中的数据(如果客户端请求方法需要返回值)。

以上,就是系统生成的 IMyBookManager 的工作过程,需要注意下,服务端的 onTransact 方法是运行在 Binder 线程池中的。由于 IMyBookManager .Stub 类继承 Binder,所以上述分析即 Binder 的工作机制,简单总结下:

  • 在客户端和服务端连接时(一般通过 bindService 方法),服务端通过 asInterface 方法给客户端返回一个 IInterface 接口类型的对象(如果客户端和服务端在同一个进程,则返回服务端的 Binder 对象,否则返回服务端 Binder 对象的代理);
  • 客户端通过该对象向服务端进行请求,如果客户端和服务端不在同一个进程,则通过该对象所代理的 Binder 对象进行 RPC 请求,否则,直接通过 Binder 对象调用服务端相应方法。

或者参考下图理解下:

在这里插入图片描述

总结

最后总结下一完整的 AIDL 远程 Service 服务的搭建和通信步骤:

  1. 服务端创建 IMyBookManager.aidl 文件,系统编译生成相应的继承 IInterface 的接口类 IMyBookManager.java,定义暴露给客户端调用的接口;
  2. 服务端创建一个 Service 类,定义内部类 MyBookManager 继承 .aidl 文件中的 IMyBookManager.Stub 子类并实现对外提供服务的接口的具体逻辑,并在 Service 类的 onbind 方法中 return 内部类 MyBookManager 的对象;
  3. 在 AndroidMainfest.xml 中注册服务且声明为远程服务;
  4. 创建客户端,onbind() 函数绑定服务端的 Service(指定包名、隐式Action);
  5. 客户端重写 ServiceConnection 的 onServiceConnected 方法(绑定服务时会自动调用),使用 Stub.asInterface 接口将服务端返回的 Binder 对象转成 AIDL 接口所属的 IInterface 类型;
  6. 客户端借助服务端返回的 Binder 代理对象,直接调用 AIDL 中提供的接口方法,实现和服务端的远程通信。

本文参考文章:

  1. Android AIDL浅析及异步使用
  2. Android AIDL 学习 (服务端)
  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tr0e

分享不易,望多鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值