Android中的IPC机制(一)

好久没有写文章了,内心可能有千种理由,可是说到底仍是惰性形成的。学习自己就是一场修行,在没有人督促的状况下,更容易产生惰性。因此,要时刻提醒本身要更努力一些。html

前言

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

IPC机制概念

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

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

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

二者之间的区别:
一、进程是资源分配的最小单位,线程是程序执行的最小单位。
二、线程之间通讯能够经过共享局部变量、静态变量等数据。进程之间通讯须要经过IPC机制。
markdown

固然二者之间的区别确定不止上面两条,你们能够自行查阅。
app

Android如何开启多进程

讲到如何在Android中开启多进程,你们可能有点懵:“两个进程不就是两个应用吗?”。没错,这里只是开启多进程的一种形式,可是咱们咱们若是想在一个应用中开启多进程该如何操做呢?最简单的咱们能够经过给四大组件设置不一样的android:process值来实如今一个应用中的多进程。下面咱们将举一个例子。ide

<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中分别设置了两个按钮,用于打开另外两个Activityoop

@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没发生变化的缘由。
当运行在不一样进程中的四大组件,它们想要经过共享内存的方式来共享数据都会产生失败。因此说,进程间共享数据时,会出现如下问题:

一、静态成员和单例模式失效
二、线程同步机制失效
三、SharePreference的可靠性降低
四、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的区别

一、Serializable属于Java;Parcelable属于Android
二、Serializable序列化和反序列化过程须要大量的I/O操做;Parcelable不须要
三、Serializable开销较大;Parcelable开销较小
四、Serializable效率低;Parcelable效率低

AIDL实例

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

建立服务端

一、建立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 + "元";
    }
}
复制代码

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

二、建立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一下工程。

三、建立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是怎样在进程间进行通讯的。因为这个文件比较复杂,放到文章的后部进行讲解,咱们先把流程走通。

四、建立服务

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属性表示可否被其余应用隐式调用。

建立客服端

一、复制文件

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

二、调用服务进行进程间通讯

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;
}
复制代码

若是你们将方法进行折叠,你会发现这里面其实一共作了如下几件事:

一、定义了getTicketsList()方法
二、定义了addTickets(com.a00123.aidlservice.Tickets tickets)方法
三、建立了一个默认实现TickManager接口的静态内部类
四、建立了一个名为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个参数都是什么意思:

一、code表示要执行的操做,它的取值范围在FIRST_CALL_TRANSACTIONLAST_CALL_TRANSACTION之间
二、data表示传输给目标的数据,必定不能为空,若是为空,须要建立一个未被初始化的Parcel数据。
三、replay表示从目标接收的数据,若是你不感兴趣能够返回空
四、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的原理进行了说明,如今就让咱们进行小结一下。

小结

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

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

参考资料

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