Android中的IPC机制(一)

1,950 阅读23分钟

很久没有写文章了,心里可能有千种理由,但是说到底还是惰性造成的。学习本身就是一场修行,在没有人督促的情况下,更容易产生惰性。所以,要时刻提醒自己要更努力一些。

前言

由于IPC机制在Android中属于比较重要的机制,加之也比较复杂。所以,我会分两篇文章进行记录。这篇文章将会从IPC机制概念、进程和线程之间的区别、IPC机制来、常见IPC机制和AIDL实践来讲解IPC机制,剩下的知识点将会在下一篇文章进行记录。

IPC机制概念

IPC的全称:Inter-Process Communication,翻译过来就是“进程间通信”,是指两个进城之间的相互通信。讲到这里肯定会有人问什么是进程?进程与线程之间的区别和联系是什么?让我们来看一下。

线程:
根据操作系统的描述,线程是是CPU调度的最小单元,同时线程是一种有限的系统资源。它是进程的一个执行流,每个线程都有自己的堆栈和局部变量。

进程:
通常情况下是指一个执行单元,在PC或者移动设备上指一个程序或者一个应用。

两者之间的区别:
1、进程是资源分配的最小单位,线程是程序执行的最小单位。
2、线程之间通信可以通过共享局部变量、静态变量等数据。进程之间通信需要通过IPC机制。

当然两者之间的区别肯定不止上面两条,大家可以自行查阅。

Android如何开启多进程

讲到如何在Android中开启多进程,大家可能有点懵:“两个进程不就是两个应用吗?”。没错,这里只是开启多进程的一种形式,但是我们我们如果想在一个应用中开启多进程该如何操作呢?最简单的我们可以通过给四大组件设置不同的android:process值来实现在一个应用中的多进程。下面我们将举一个例子。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.a00123.ipcdemo">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".FirstActivity"
            android:process=":process"> //1
        </activity>
        <activity
            android:name=".SecondActivity"
            android:process="com.a00123.ipcdemo.process"> //2
        </activity>
    </application>
</manifest>

我们在Manifest文件中创建了三个Activity,分别是:MainActivityFirstActivitySecondActivity,其中我们为FirstActivitySecondActivity分别设置了android:process属性(代码注释1和注释2处),在MainActivity中分别设置了两个按钮,用于打开另外两个Activity

 @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn1:
                Intent intent = new Intent(MainActivity.this, FirstActivity.class);
                startActivity(intent);
                break;
            case R.id.btn2:
                Intent intent2 = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent2);
                break;
        }
    }

让我们来运行一下,看看会有什么结果。在这里再多说一句题外话,这里我用的是adb命令进行查看的进程名称,至于各位小伙伴的电脑请参照具体方法查看。

由此我们可以看出如果没有设置android:process属性的话,默认的进程名就是当前应用的包名。使用“:”来设置的话,会自动在前面加上对应的包名。最后一个就是我们设置的android:process="com.a00123.ipcdemo.process"属性值。这也其实也很好理解,设置进程名就相当于我们电脑中文件中的绝对路径和相对路径。其中以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它运行在同一个进程中,而不以“:”开头的进程则属于全局进程,其他应用通过ShareUID方式可以和它运行在同一个进程中。
Android在运行一个程序时,会为其分配一个唯一的UID,具有相同的UID的两个应用才能共享数据。如果两个应用想通过ShareUID的方式运行在同一进程需要这两个程序拥有相同的ShareUID和相同的签名才可以。只有这种情况下,两个程序之间才能相互获取对方的私有数据。
既然这么容易的就能开启多进程,那我们是不是就能说进程间的通信难度也不过如此嘛。如果这样想,只能说我们太年轻。

我们还是用上面的代码,再举一个例子。我们创建一个类,类中有一个静态变量,按照我们正常的思维理解,静态资源应该是被共享的,只要有一处改变,其他地方也会随之改变,但是在多进程中是这样吗?我们运行一下用数据说话。

public class IPCManager {
    public static int managerId = 1;
}

正如上面这几张图所示,我们运行起来之后有三个进程。但是,只有在默认进程中静态变量的值是改变的,其他两进程中的静态变量竟然还是原来的值,这事怎么回事?

多进程带来的问题

从上面我们也可以看出,当程序中存在多进程时,容易出现问题。出现上面问题的具体原因是这三个Activity分别运行在三个进程中,上面我们也说过,Android系统会给每一个应用程序分配一个独立的虚拟机,不同的虚拟机在内存地址的分配上是不同的。当访问一个静态变量时,不同的进程之间相互没有影响,所以这就是为什么在MainActivity中修改静态变量的值时其他Activity没发生变化的原因。
当运行在不同进程中的四大组件,它们想要通过共享内存的方式来共享数据都会产生失败。所以说,进程间共享数据时,会出现以下问题:

1、静态成员和单例模式失效
2、线程同步机制失效
3、SharePreference的可靠性下降
4、Application会被重复创建

分别解释一下 第一个问题在上面中的例子中已经有所说明;第二个问题和第一个问题相同,进程间运行在不同的内存上,不管有没有锁对象都没法保证线程同步,因为锁作用的对象不是同一个;第三个问题,SharePreference文件是不支持两个进程同时写数据,因为有可能造成数据的丢失,所以可靠性会降低;第四个问题,我们说过Android创建新的进程同时会分配独立的虚拟机,这就相当于是重新启动一个应用的过程,启动应用是会自动创建新的Application对象,所以会造成Application对象重复创建。

IPC的基础知识准备

这里的基础知识准备主要讲讲序列化的知识点,当然还有另外一个知识点Binder,这个知识点准备放在AIDL实例讲解中介绍。
当我们在四大组件之间使用Intent传递对象时,这本身就是一种进程间通信的方式,需要将对象进行序列化和反序列化。再或者,当我们需要把对象存储在手机设备上,这个过程也是需要将对象序列化。我们来看一下两种序列化方式SerializableParcelable

Serializable

SerializableJava提供的一种序列化接口,可以为对象提供序列化和反序列化操作。使用的时候需要类实现Serializable接口,同时声明一个serialVersionUID来实现序列化。
从声明上我们可以看出,这个serialVersionUID相当于是类的唯一标识,也就是说,一个类在序列化时保存了唯一标识,在反序列化时会将这个唯一标识要反序列化对象的唯一表示进行对比,如果不一致会报:InvalidClassException

Parcelable

ParcelableAndroid提供的一种序列化方法,相比于Serializable其效率更高,占用的内存更少,但是使用起来也更麻烦。Android官方推荐使用Parcelable方法进行序列化操作。在AndroidBundleIntent等都默认实现了Parcelable接口。
既然是谷歌爸爸推荐的,我们肯定要多使用一下。但是在使用时比较麻烦,怎么办?其实不用担心,Android Studio已经中可以使用插件进行一键生成,美滋滋了。
举个例子吧,看看Parcelable序列化方式都做了什么。

public class Dog implements Parcelable {
    private String name;
    private String color;

    public Dog(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

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

    public void setColor(String color) {
        this.color = color;
    }
    /**
    * 内容描述
    */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
    * 序列化操作
    */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeString(this.color);
    }
    
    //反序列化数据恢复
    protected Dog(Parcel in) {
        this.name = in.readString();
        this.color = in.readString();
    }
    
    //创建Parcelable对象
    public static final Parcelable.Creator<Dog> CREATOR = new Parcelable.Creator<Dog>() {
        @Override
        public Dog createFromParcel(Parcel source) {
            return new Dog(source);
        }

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

Serializable和Parcelable的区别

1、Serializable属于Java;Parcelable属于Android
2、Serializable序列化和反序列化过程需要大量的I/O操作;Parcelable不需要
3、Serializable开销较大;Parcelable开销较小
4、Serializable效率低;Parcelable效率低

AIDL实例

在我们日常开发中很少使用到AIDL(也可能是本人开发过程中很少使用),但不论在平时学习还是面试的过程中,这都是一个非常重要的知识点。我们通过一个实例来练习一下,并通过这个例子探究一下Binder机制。有关AIDL的介绍网上有很多,我在这里就不再赘述,有兴趣的小伙伴可以参照AIDL谷歌官方文档
在写AIDL例子的时候,我在网上看到很多都是参照任玉刚老师《Android开发工艺探索》的中例子。这个例子很经典,我也打算参照这个例子,但是这次是对电影票进行操作。

创建服务端

1、创建Tickets类

public class Tickets implements Parcelable {
    private String name;
    private float prices;

    public Tickets(String name, float prices) {
        this.name = name;
        this.prices = prices;
    }

    public String getName() {
        return name;
    }

    public float getPrices() {
        return prices;
    }

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

    public void setPrices(float prices) {
        this.prices = prices;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeFloat(this.prices);
    }

    protected Tickets(Parcel in) {
        this.name = in.readString();
        this.prices = in.readFloat();
    }

    public static final Parcelable.Creator<Tickets> CREATOR = new Parcelable.Creator<Tickets>() {
        @Override
        public Tickets createFromParcel(Parcel source) {
            return new Tickets(source);
        }

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

    @Override
    public String toString() {
        return "票名:" + name + "---" + "票价:" + prices + "元";
    }
}

在这个类中创建了两个变量,分别表示名称和价格。

2、创建Tickets.aidl文件

创建文件很简单,在项目包目录下直接右键,选择新建->AIDL->AIDL File,取名为Tickets.aidl

文件创建成功之后,AndroiStudio会自动在工程目录下创建一个aidl包,并且其子包名一工程的包名一致,需要有两点需要注意的地方。第一,这个ADIL文件应该和第一步中创建的实体类的包名保持一致;第二,就是aidl文件中的内容,创建出来之后如果不对内容修改,会报出名称不唯一的错误。


//Tickets.aidl文件内容
package com.a00123.aidlservice;
// Declare any non-default types here with import statements
parcelable Tickets;

在这里需要再次提醒一下,每新建一个类或者aidl文件,最好重新build一下工程。

3、创建TicketsManager.aidl文件

创建步骤和第二步相同,这里要注意的是最好路径一上一个aidl文件路径相同,这样方便后期操作。

// TicketsManager.aidl
package com.a00123.aidlservice;

// Declare any non-default types here with import statements
import com.a00123.aidlservice.Tickets;

interface TicketsManager {
    List<Tickets> getTicketsList();

    void addTickets(in Tickets tickets);
}

在这里定义了两个方法,一个是获取票列表,另外一个是增加票的方法。aidl文件中是不支持自动导包,所以我们需要手动把Tickets的包名导入。我们看到在addTickets方法中,在参数之前有一个in,这是什么意思呢?
实际上in, out, inout是三个定向tag,它们实际的含义是:所有的非基本参数都需要一个定向tag来指出数据的流向,不管是 in , out , 还是 inout 。基本参数的定向tag默认是并且只能是 in
在我们重新build完项目之后,会在项目的build目录下生成两个文件。其中,有一个Tickets.java文件,这个文件内容为空。另外还会生成一个Interface文件,这个文件是比较中要的,它牵扯到AIDL是怎样在进程间进行通信的。由于这个文件比较复杂,放到文章的后部进行讲解,我们先把流程走通。

4、创建服务

public class TicketsManagerService extends Service {
   private CopyOnWriteArrayList<Tickets> mTickets = new CopyOnWriteArrayList<>();
   private Binder mBinder = new TicketsManager.Stub() {
       @Override
       public List<Tickets> getTicketsList() throws RemoteException {
           return mTickets;
       }

       @Override
       public void addTickets(Tickets tickets) throws RemoteException {
           mTickets.add(tickets);
       }
   };

   @Override
   public void onCreate() {
       super.onCreate();

       mTickets.add(new Tickets("攀登者",50));
       mTickets.add(new Tickets("我和我的祖国",55));
   }

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

在创建服务的时候我们新建一个TicketsManager.Stub对象,同时也实现了其内部的两个方法,这两个方法就是我们在第3步中定义的方法。在服务创建的时候新增了两张票。到此服务端代码已经创建完毕,不要忘记在清单文件中注册服务。

<service
    android:name=".TicketsManagerService"
    android:enabled="true"
    android:exported="true"/>

其中的enable属性表示是否可以被系统实例化,exported属性表示能否被其他应用隐式调用。

创建客服端

1、复制文件

我们需要将服务端的两个aidl文件、Tickets文件复制到客户端,这里需要注意,这些文件的路径要和服务端中对应文件路径保持一致。

2、调用服务进行进程间通信

public class MainActivity extends AppCompatActivity {
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            TicketsManager ticketsManager = TicketsManager.Stub.asInterface(service);
            try {
                List<Tickets> list = ticketsManager.getTicketsList();
                Log.i("MainActivity", "query tickets list:" + list.toString());
                Tickets tickets = new Tickets("中国机长", 45);
                ticketsManager.addTickets(tickets);
                Log.i("MainActivity", "add tickets:" + tickets);
                List<Tickets> newList = ticketsManager.getTicketsList();
                Log.i("MainActivity", "query tickets list:" + newList.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setClassName("com.a00123.aidlservice", "com.a00123.aidlservice.TicketsManagerService");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}

在服务连接之后,我们首调用了getTicketsList()方法并将结果打印了出来,然后有新增了一个Tickets对象,最后在一次打印。由于服务是在另外一个进程中,所以在绑定的时候需要知道服务所在的包名和全路径名称,最后直接绑定。让我们看一下打印结果。

AIDL原理

虽然我们已经学会了如何使用AIDL,但是有很多地方感觉还是有些云里雾里,就让我们探究一下AIDL的原理。
不知道大家是否还记得,当我们创建TicketsManager之后系统会在build目录下为我们自动创建的那个TicketsManager文件,这个文件很重要,可以说这里面就是AIDL的运行本质,同样也可以说是binder机制的原理,让我们来看一下。

public interface TicketsManager extends android.os.IInterface {
    /**
     * Default implementation for TicketsManager.
     */
    public static class Default implements com.a00123.aidlservice.TicketsManager {
        @Override
        public java.util.List<com.a00123.aidlservice.Tickets> getTicketsList() throws android.os.RemoteException {
            return null;
        }

        @Override
        public void addTickets(com.a00123.aidlservice.Tickets tickets) throws android.os.RemoteException {
        }

        @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.a00123.aidlservice.TicketsManager {
        private static final java.lang.String DESCRIPTOR = "com.a00123.aidlservice.TicketsManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.a00123.aidlservice.TicketsManager interface,
         * generating a proxy if needed.
         */
        public static com.a00123.aidlservice.TicketsManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.a00123.aidlservice.TicketsManager))) {
                return ((com.a00123.aidlservice.TicketsManager) iin);
            }
            return new com.a00123.aidlservice.TicketsManager.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_getTicketsList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.a00123.aidlservice.Tickets> _result = this.getTicketsList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addTickets: {
                    data.enforceInterface(descriptor);
                    com.a00123.aidlservice.Tickets _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.a00123.aidlservice.Tickets.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addTickets(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.a00123.aidlservice.TicketsManager {
            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;
            }

            @Override
            public java.util.List<com.a00123.aidlservice.Tickets> getTicketsList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.a00123.aidlservice.Tickets> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_getTicketsList, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().getTicketsList();
                    }
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.a00123.aidlservice.Tickets.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addTickets(com.a00123.aidlservice.Tickets tickets) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((tickets != null)) {
                        _data.writeInt(1);
                        tickets.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    boolean _status = mRemote.transact(Stub.TRANSACTION_addTickets, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        getDefaultImpl().addTickets(tickets);
                        return;
                    }
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            public static com.a00123.aidlservice.TicketsManager sDefaultImpl;
        }

        static final int TRANSACTION_getTicketsList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addTickets = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

        public static boolean setDefaultImpl(com.a00123.aidlservice.TicketsManager impl) {
            if (Stub.Proxy.sDefaultImpl == null && impl != null) {
                Stub.Proxy.sDefaultImpl = impl;
                return true;
            }
            return false;
        }

        public static com.a00123.aidlservice.TicketsManager getDefaultImpl() {
            return Stub.Proxy.sDefaultImpl;
        }
    }

    public java.util.List<com.a00123.aidlservice.Tickets> getTicketsList() throws android.os.RemoteException;

    public void addTickets(com.a00123.aidlservice.Tickets tickets) throws android.os.RemoteException;
}

如果大家将方法进行折叠,你会发现这里面其实一共做了以下几件事:

1、定义了getTicketsList()方法
2、定义了addTickets(com.a00123.aidlservice.Tickets tickets)方法
3、创建了一个默认实现TickManager接口的静态内部类
4、创建了一个名为Stub的静态内部类,这个类继承自Binder,同时也实现了TicketsManager接口
但是当我们仔细观察时,有感觉无从下手,别着急,我们一步一步的看。

android.os.IInterface

我们看到这个TicketManager接口继承了一android.os.IInterface接口,这个接口内部做了什么?

/**
 * Base class for Binder interfaces.  When defining a new interface,
 * you must derive it from IInterface.
 */
public interface IInterface
{
    /**
     * Retrieve the Binder object associated with this interface.
     * You must use this instead of a plain cast, so that proxy objects
     * can return the correct result.
     */
    public IBinder asBinder();
}

从翻译中我们不难看出,这个接口是Binder接口的基类,如果想定义一个新的接口,必须要继承它。这个接口中有一个asBinder()方法,默认返回的是IBinder。也就是说IInterface可以把所有继承它的对象转换成IBinder

IBinder

既然说到了IBinder,我们来看一IBinder是什么。

public interface IBinder {
...
    /**
     * Attempt to retrieve a local implementation of an interface
     * for this Binder object.  If null is returned, you will need
     * to instantiate a proxy class to marshall calls through
     * the transact() method.
     */
    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor);
   
   /**
     * Perform a generic operation with the object.
     * 
     * @param code The action to perform.  This should
     * be a number between {@link #FIRST_CALL_TRANSACTION} and
     * {@link #LAST_CALL_TRANSACTION}.
     * @param data Marshalled data to send to the target.  Must not be null.
     * If you are not sending any data, you must create an empty Parcel
     * that is given here.
     * @param reply Marshalled data to be received from the target.  May be
     * null if you are not interested in the return value.
     * @param flags Additional operation flags.  Either 0 for a normal
     * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
     *
     * @return Returns the result from {@link Binder#onTransact}.  A successful call
     * generally returns true; false generally means the transaction code was not
     * understood.
     */
    public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
        throws RemoteException; 
...
}

我们可以看到IBinder也是一个接口,这个接口中定义了很多变量和方法,其中我们着重看一下上面两个方法。

IInterface queryLocalInterface(@NonNull String descriptor)

这个方法希望返回一个本地实现了该接口的Binder对象,如果返回值为null,就需要实例化一个代理类去调用transact()方法。其实这个方法使用来判断AIDL中服务调用这的进程身份,如果是当前进程,返回值不为null;如果是其他进程,就需要创建一个代理类去调用transact()方法。

public boolean transact(...)

这个方法中传入了4个参数,我们分别看一下这4个参数都是什么意思:

1、code表示要执行的操作,它的取值范围在FIRST_CALL_TRANSACTIONLAST_CALL_TRANSACTION之间
2、data表示传输给目标的数据,一定不能为空,如果为空,需要创建一个未被初始化的Parcel数据。
3、replay表示从目标接收的数据,如果你不感兴趣可以返回空
4、flags附加操作的标识,0是指普通的RPC。或者FLAG_ONEWAY,指单向RPC。
这个方法返回值会通过调用Binder类中的onTransact方法。如果成功的执行了,则返回true;反之表示不清楚要处理的code值是什么含义。

RPC

从上面的方法中我们又知道了另外一个名词:RPC关于其定义这里就不多解释了,详情请参照柳树之大佬的如何给老婆解释什么是RPC

Binder

上面的transact(...)方法会调用Binder中的onTransact方法,所以我们来看一下Binder类。

/**
 * Base class for a remotable object, the core part of a lightweight
 * remote procedure call mechanism defined by {@link IBinder}.
 * This class is an implementation of IBinder that provides
 * standard local implementation of such an object.
 *
 * <p>Most developers will not implement this class directly, instead using the
 * <a href="{@docRoot}guide/components/aidl.html">aidl</a> tool to describe the desired
 * interface, having it generate the appropriate Binder subclass.  You can,
 * however, derive directly from Binder to implement your own custom RPC
 * protocol or simply instantiate a raw Binder object directly to use as a
 * token that can be shared across processes.
 *
 * <p>This class is just a basic IPC primitive; it has no impact on an application's
 * lifecycle, and is valid only as long as the process that created it continues to run.
 * To use this correctly, you must be doing so within the context of a top-level
 * application component (a {@link android.app.Service}, {@link android.app.Activity},
 * or {@link android.content.ContentProvider}) that lets the system know your process
 * should remain running.</p>
 *
 * <p>You must keep in mind the situations in which your process
 * could go away, and thus require that you later re-create a new Binder and re-attach
 * it when the process starts again.  For example, if you are using this within an
 * {@link android.app.Activity}, your activity's process may be killed any time the
 * activity is not started; if the activity is later re-created you will need to
 * create a new Binder and hand it back to the correct place again; you need to be
 * aware that your process may be started for another reason (for example to receive
 * a broadcast) that will not involve re-creating the activity and thus run its code
 * to create a new Binder.</p>
 *
 * @see IBinder
 */
public class Binder implements IBinder {
...
    /**
     * Use information supplied to attachInterface() to return the
     * associated IInterface if it matches the requested
     * descriptor.
     */
    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

    /**
     * Default implementation rewinds the parcels and calls onTransact.  On
     * the remote side, transact calls into the binder to do the IPC.
     */
    public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);

        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }
    
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,
            int flags) throws RemoteException {
        if (code == INTERFACE_TRANSACTION) {
            reply.writeString(getInterfaceDescriptor());
            return true;
        } else if (code == DUMP_TRANSACTION) {
            ParcelFileDescriptor fd = data.readFileDescriptor();
            String[] args = data.readStringArray();
            if (fd != null) {
                try {
                    dump(fd.getFileDescriptor(), args);
                } finally {
                    IoUtils.closeQuietly(fd);
                }
            }
            // Write the StrictMode header.
            if (reply != null) {
                reply.writeNoException();
            } else {
                StrictMode.clearGatheredViolations();
            }
            return true;
        } else if (code == SHELL_COMMAND_TRANSACTION) {
            ParcelFileDescriptor in = data.readFileDescriptor();
            ParcelFileDescriptor out = data.readFileDescriptor();
            ParcelFileDescriptor err = data.readFileDescriptor();
            String[] args = data.readStringArray();
            ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);
            ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
            try {
                if (out != null) {
                    shellCommand(in != null ? in.getFileDescriptor() : null,
                            out.getFileDescriptor(),
                            err != null ? err.getFileDescriptor() : out.getFileDescriptor(),
                            args, shellCallback, resultReceiver);
                }
            } finally {
                IoUtils.closeQuietly(in);
                IoUtils.closeQuietly(out);
                IoUtils.closeQuietly(err);
                // Write the StrictMode header.
                if (reply != null) {
                    reply.writeNoException();
                } else {
                    StrictMode.clearGatheredViolations();
                }
            }
            return true;
        }
        return false;
    }
...
}

我们瞅一眼注释就能看出Binder的重要性,它不仅用在AIDL,同时运用在ServiceActivityContentProvider中。注释中说了很多,总结成一句话:这个类很重要,你会不止一次地回头看它。看到这里,我们来分析以下我们自己写的代码。
首先,我们在创建服务端远程服务时调用TicketsManager.Stub()方法创建了一个Binder对象,这个Binder对象中重写了getTicketsList()addTickets(Tickets tickets)方法。最后在服务被绑定时将这个binder对象转成IBinder返回出去。
第二步,我们需要创建远程服务,并绑定。
第三步,创建远程对象时需要传入一个ServiceConnection对象,所以我们在客户端又创建ServiceConnection对象,并在其onServiceConnected方法回调中获取了一个IBinder对象,这个IBinder对象就是第一步一步中传递过来的。拿到这个对象之后,我们又调用了TicketsManager.Stub.asInterface(service)方法,将IBinder对象传入,获得一个TicketsManager对象。自此,客户端和服务端算是连接到一起。我们看一下TicketsManager.Stub.asInterface(service)内部的调用情况。

public static abstract class Stub extends android.os.Binder implements com.a00123.aidlservice.TicketsManager {
        private static final java.lang.String DESCRIPTOR = "com.a00123.aidlservice.TicketsManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.a00123.aidlservice.TicketsManager interface,
         * generating a proxy if needed.
         */
        public static com.a00123.aidlservice.TicketsManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 
            if (((iin != null) && (iin instanceof com.a00123.aidlservice.TicketsManager))) { //1
                return ((com.a00123.aidlservice.TicketsManager) iin);
            }
            return new com.a00123.aidlservice.TicketsManager.Stub.Proxy(obj);
        }
        
        private static class Proxy implements com.a00123.aidlservice.TicketsManager {
            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;
            }
            
            @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_getTicketsList: { //3
                        data.enforceInterface(descriptor);
                        java.util.List<com.a00123.aidlservice.Tickets> _result = this.getTicketsList();
                        reply.writeNoException();
                        reply.writeTypedList(_result);
                        return true;
                    }
                    case TRANSACTION_addTickets: { //3
                        data.enforceInterface(descriptor);
                        com.a00123.aidlservice.Tickets _arg0;
                        if ((0 != data.readInt())) {
                            _arg0 = com.a00123.aidlservice.Tickets.CREATOR.createFromParcel(data);
                        } else {
                            _arg0 = null;
                        }
                        this.addTickets(_arg0);
                        reply.writeNoException();
                        return true;
                    }
                    default: {
                        return super.onTransact(code, data, reply, flags);
                    }
            }
        }

            @Override
            public java.util.List<com.a00123.aidlservice.Tickets> getTicketsList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.a00123.aidlservice.Tickets> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_getTicketsList, _data, _reply, 0); //2
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().getTicketsList();
                    }
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.a00123.aidlservice.Tickets.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            ...
            @Override
            public void addTickets(com.a00123.aidlservice.Tickets tickets) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((tickets != null)) {
                        _data.writeInt(1);
                        tickets.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    boolean _status = mRemote.transact(Stub.TRANSACTION_addTickets, _data, _reply, 0); //2
                    if (!_status && getDefaultImpl() != null) {
                        getDefaultImpl().addTickets(tickets);
                        return;
                    }
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
            public static com.a00123.aidlservice.TicketsManager sDefaultImpl;
        }
}

上面的代码我们已经看过一次了,我们在来看以下。在调用TicketsManager.Stub.asInterface方法之后,代码会来到注释1处。在之前的源码分析中我们知道这里是对进程进行判断,如果属于同一进程,则会返回本地的TicketManager;如果不属于同一进程,则会返回Proxy对象。

第四步,在客户端的MainActivity中我们调用了ticketsManager.getTicketsList()方法,其实这个方法就是调用了Proxy对象的getTicketsList()方法。在这个方法中会调用mRemote.transact方法,并把参数传入了进去。我们看到传入的第一个参数是Stub.TRANSACTION_addTickets,这个参数是标识操作类型。同时,这个mRomote对象,其实就是一个TicketsManager对象,大家不要被绕晕。在调用transact(...)方法时,内部会去调用onTransact(...)方法,也就是在注释3处。在注释3处,首先判断了要操作的类型,这里的this就是我们在服务端的TicketManagerService中定义的那个mBinder对象,它内部会获取到一个List集合,并赋值给_result变量。由于有返回值。所以进行了reply.writeTypedList(_result)方法,该方法。。。。。

第五步,由于onTransact(...)方法中已经返回true,回到注释2处,将结果进行反序列化_reply.createTypedArrayList(com.a00123.aidlservice.Tickets.CREATOR),最后将结果返回到客服端。还有另外一个addTickets(Tickets tickets)方法,这个方法执行流程与getTickets()是一致的,这里就不做赘述。
至此,我们已经将AIDL的原理进行了说明,现在就让我们进行小结一下。

小结

1、当我们创建带有抽象方法的XXX.aidl文件时,系统会自动帮我们创建一个对应的XXXManager文件,如果有时间的话,我们可以自己实现。
2、在服务服务端创建一个Service对象,并将第一步中的XXXManager中子类Stub的实现对象在onBind()方法返回出去。
3、在客户端创建一个XXXManager.Stub对象,并调用asInterface()方法将服务完成连接后的IBinder对象传进去,这个IBinder对象就是我们在第二步中的onBind()方法中返回的XXXManager.Stub对象。在调用asInterface时,其内部对进程进行了判断,如果是不同的进程,则会返回一个XXXManagerProxy代理对象。
4、我们在客户端调用XXXManager中获取数据、修改数据方法时,会走到XXXManager.Stub对象的transact方法,并传入参数。在transact方法内部还将会调用XXXManager.Stub中的onTransact方法,在这个方法中回首先根据传入的参数进行操作类型的判断,然后对数据进行序列化或者赋值等操作,最后将数据做返回或者存入修改

由于篇幅有限,这篇文章暂时先分析到这里。本人资历尚浅,能力有限,如果有哪里写的不对的地方,欢迎各位大佬批评指正。关于Binder机制的总结和其他IPC方式,我会在后续的Android中的IPC机制(二)中继续分析,敬请期待。

参考资料

AIDL谷歌官方文档
任玉刚《Android开发艺术探索》
柳树之如何给老婆解释什么是RPC