Android AIDL 解析

AIDL简介

AIDL(Android Interface Definition Language) 是Android IPC(Inter-Process Communication)进程间通信的一种重要方式,使用Binder实现。我们知道在Android(Linux)系统中,每个进程都有独立的内存区域,其他进程无法直接访问,当我们的进程中需要跨进程调用方法、获取数据时,我们就可以使用AIDL定义客户端与服务均认可的编程接口,实现进程间的相互通信。

AIDL使用

下面我们先通过一个简单的例子来熟悉一下使用aidl实现进程间通信的流程。我们先main目录下创建aidl目录,并们创建一个.aidl文件,定义一些方法。


image.png
// ImageInterface.aidl
package com.chuan.infrastructure;

// Declare any non-default types here with import statements

interface ImageInterface {

    void addImage(String path);

    String getImage(int id);
    /**
     * 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);
}

然后重新rebuild一下工程,会发现AndroidStudio 会帮我们在build目录下生成与aidl同名的Java文件。


image.png

至此我们跨进程的接口就已经定义好了,需要跨进程的服务继承接口的静态内部类Stub即可。我们把创建好的Service配置在其他进程里面与主进程的activity去交互。

ImageService

package com.chuan.infrastructure.service;

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

import androidx.annotation.Nullable;

import com.chuan.infrastructure.ImageInterface;

import java.util.ArrayList;
import java.util.List;

public class ImageService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ImageBinder();
    }

    //继承自动生成的静态内部类,使当前类可以跨进程
    public static class ImageBinder extends ImageInterface.Stub{

        List<String> images = new ArrayList<>();

        @Override
        public void addImage(String path) {
            images.add(path);
        }

        @Override
        public String getImage(int id) {
            if (id>=0 && id < images.size()){
                return images.get(id);
            }
            return null;
        }
    }
}

在AndroidManifest.xml里面配置ImageService在独立进程里面。

 <service android:name=".service.ImageService"
            android:process=":images"
            android:exported="true"
            android:enabled="true"/>

然后再在activity里面绑定ImageService,此时activity已经和ImageService在不同进程。ImageService进程名为 -> 包名:images

ImageActivity

package com.chuan.infrastructure;

import androidx.appcompat.app.AppCompatActivity;

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.util.Log;
import android.view.View;

import com.chuan.infrastructure.service.ImageService;

public class ImageActivity extends AppCompatActivity {

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

    private ServiceConnection connection;
    private ImageInterface imageInterface;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image);
        connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                imageInterface = ImageInterface.Stub.asInterface(service);
                Log.d(TAG, "onServiceConnected: " + imageInterface.toString());
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
        Intent intent = new Intent(getApplication(), ImageService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }


    public void addImage(View view){
        try {
            imageInterface.addImage("https://www.image.com/image/a.jpg");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void getImage(View view){
        try {
            //获取远程保存的数据
            String url = imageInterface.getImage(0);
            Log.d(TAG, "getImage: " + url);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}

在绑定service成功后,service返回给我们了Sub的代理类对象,而且我们通过该对象成功滴与ImageService通信。


image.png

至此,我们的跨进程通信已经完成,已经可以实现简单的跨进程业务。但是大多数人还对AIDL没有一个清晰的认知,只是一脸懵逼的实现了跨进程通信。

AIDL解析

我们前面说过AIDL是接口定义语言,他的作用仅仅就是定义一个规范,然后由AndroidStudio帮我们生成具体跨进程规则的Java接口和类,基于Binder实现跨进程通信,那么我们就可以参考自动生成的代码自己写一套跨进程通信的接口,从而不用像最开始一样创建一个.aidl文件,再rebuild工程生成一个同名的Java文件。

下面我们对生成的Java接口文件做一个详解,具体的解析我都加在注释里面

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.chuan.infrastructure;

//ImageInterface 继承自 IInterface,
// IInterface其实就是AIDL提供的接口用了定义借口包含哪些能力
public interface ImageInterface extends android.os.IInterface
{
  //静态内部类继承Binder,实现了上面定义的接口ImageInterface
  // 说明该静态内部类具备接口定义的功能,同时继承的Binder类实现了IBinder接口,
  // IBinder接口定义了跨进程的能力,所以Stub子类的对象可以跨进程,而且具备ImageInterface接口定义的所有功能
  public static abstract class Stub extends android.os.Binder implements ImageInterface
  {
    private static final String DESCRIPTOR = "com.chuan.infrastructure.ImageInterface";

    public Stub()
    {
      //设置interface对象和描述,在后面queryLocalInterface获取
      this.attachInterface(this, DESCRIPTOR);
    }
    //该方法用来用来判断是否在同一进程,
    // 同一进程返回ImageInterface对象,不在同一进程返回Stub的代理类对象
    public static ImageInterface asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      //具体判断逻辑,在stub的构造方法中调用了this.attachInterface(this, DESCRIPTOR);
      //设置在了Server进程,Client进程查询拿到null,所以返回代理对象Proxy
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof ImageInterface))) {
        return ((ImageInterface)iin);
      }
      return new Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }

    //跨进程调用具体执行方法,跨进程通过proxy类调用
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_addImage:
        {
          data.enforceInterface(descriptor);
          String _arg0;
          _arg0 = data.readString();
          //调用stub具体实现类的方法
          this.addImage(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_getImage:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          String _result = this.getImage(_arg0);
          reply.writeNoException();
          reply.writeString(_result);
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements ImageInterface
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      @Override public void addImage(String path) 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.writeString(path);
          //跨进程调用,调用所代理类的onTransact,
          boolean _status = mRemote.transact(Stub.TRANSACTION_addImage, _data, _reply, 0);
          //该判断用以支持同进程调用
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().addImage(path);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public String getImage(int id) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        String _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(id);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getImage, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getImage(id);
          }
          _reply.readException();
          _result = _reply.readString();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static ImageInterface sDefaultImpl;
    }
    static final int TRANSACTION_addImage = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getImage = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(ImageInterface 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 (Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static ImageInterface getDefaultImpl() {
      return Proxy.sDefaultImpl;
    }
  }
  void addImage(String path) throws android.os.RemoteException;
  String getImage(int id) throws android.os.RemoteException;
}

到此我们的AIDL已基本熟悉流程,我们可以不用创建.aidl文件,而是自己继承android.os.IInterface、android.os.Binder 去实现跨进程通信了。下面是我写的一个简单的跨进程计算求和的接口,去掉了支持同进程求和,代码精简了不少,不到80代码,没有钱捕获异常,简单测试使用,供大家参考。

package com.chuan.infrastructure;

import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public interface CalcInterface extends IInterface {

    abstract class CalcStub extends Binder implements CalcInterface{

        private static final int TRANSACTION_SUM = 1;
        private static final String DESCRIPTOR = CalcInterface.class.getName();

        public CalcStub(){
            this.attachInterface(this, DESCRIPTOR);
        }

        @Override
        public IBinder asBinder() {
            return this;
        }

        public static CalcInterface asInterface(IBinder binder){
            if (binder == null) return null;
            IInterface owner = binder.queryLocalInterface(DESCRIPTOR);
            if (owner != null && owner instanceof CalcInterface){
                return (CalcInterface) owner;
            }else {
                return new Proxy(binder);
            }
        }

        @Override
        protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
            switch (code){
                case TRANSACTION_SUM:{
                    data.enforceInterface(DESCRIPTOR);
                    int a = data.readInt();
                    int b = data.readInt();
                    int sum = this.sum(a, b);
                    reply.writeNoException();
                    reply.writeInt(sum);
                    return true;
                } default:{
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy extends CalcStub{

            private IBinder remote;
            Proxy(IBinder stub){
                remote = stub;
            }

            @Override
            public int sum(int a, int b) throws RemoteException {
                Parcel data = Parcel.obtain();
                Parcel replay = Parcel.obtain();
                data.writeInterfaceToken(DESCRIPTOR);
                data.writeInt(a);
                data.writeInt(b);
                remote.transact(TRANSACTION_SUM, data, replay, 0);
                replay.readException();
                int result = replay.readInt();
                data.recycle();
                replay.recycle();
                return result;
            }

            @Override
            public IBinder asBinder() {
                return remote;
            }
        }
    }

    int sum(int a, int b) throws android.os.RemoteException;
}

一下是测试结果截图:

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容