Android-IPC系列(一)

未经博主同意,不得转载该篇文章

前言

IPC-进程间通信。安卓虽然是一个基于linux内核的系统,但是安卓却有自己的一套IPC机制。想要弄懂安卓的IPC机制首先要理解几个framework层的概念,安卓的序列化机制以及binder和AIDL的实现流程(其它基于binder的ipc实现方式就不再多说了~)。在写这篇博客之前反复看了几遍《安卓开发艺术探索》的第二章,以及凯子哥(csdn)的framework层的文章and与一位大神学长交流,以保证文章的可靠性。这篇文章我将以自己的语言总结对于ipc的学习成果~

Demo地址:IPC-demo

几个概念

1.Binder:

Binder是安卓的一个类,实现了Binder接口(后面多次出现)。Binder我理解成安卓IPC机制的核心,也就是实现IPC的核心工具。它不仅用于安卓开发层还用于安卓的framework层。在架构层里面binder是各种系统manager之间连接的工具(WindowManager, ActivityManager等等)。开发层用于service,别告诉我你不知道service里面会返回一个IBinder。

2.AIDL:

前不久在一个安卓群看到有个面试官问别人aidl是什么。。别人当时就蒙蔽了。。

AIDL(Android Interface Definition Language),安卓接口定义语言。它是安卓实现IPC通信的一种比较重要的方式,并且底层基于binder。所以我们就讲这个啦!

3.linux的进程

这个问题我专门请教了一个学长~

Linux没有很严格的纯粹进程概念。一堆线程共享一块内存区域就是一个进程。当你的手机启动的时候,系统会启动一个init进程,这个应该可以成为Linux的主进程了。之后的所有进程都是从init进程fork出来的,比如zygote进程和SystemServer进程。

4.zygote进程

顾名思义(受精卵),这个进程会像个受精卵一样不停的“分裂”,去fork出别的进程。几乎后面出现的所有进程都是从这个进程fork出来的。包括SystemServer进程,ActivityManagerService等等。但是具体来说,AMS是SystemServer里面fork出来的。也许你会问为什么要这样做,那是因为这样设计更高效(当然我只是个普通的开发者,并不懂那些大神是怎么想的哈哈)。

4.ActivityManagerService:

这个玩意,我觉得是相当重要的,为什么这么说呢,因为它管理着手机中所有Activity的生死。你说重不重要???当你打开一个app后,AMS会立马在zygote里面fork一个进程出来,并且复制一个虚拟机(Dalvik or ART)和一些资源以及一个线程(是的,这就是UI线程~,不要怀疑自己!)。启动一个app是AMS和Lanucher, ActivityThread一起合作做到的。具体的实现,自己可以去看看别的文章,这不是我们要讲的重点。另外说一点,AMS, activity之间也是通过binder来进行通信,你要知道,AMS, zygote, activity都是在不同的进程里面。

5.App与进程:

一个app对应一个进程。这种说法我不太敢苟同。首先一个app,一个进程这种说法太模糊,因为app是可以设置多进程的哇。。(设置组件的process),所以我觉得多进程的app应该看成共享apk资源的多个应用。

6.ShareUID:

之前在一个群里面听前辈们讨论app资源共享的问题,多次看到这个单词,当时在想,卧槽,这tm什么鬼?!

这个东西你可以大概理解成每个apk的ID。一个apk对应一个uid,所以一个app里面跑在同一个进程里面的组件数据可以共享。如果一个app里面某个组件让他跑在别的进程里面,相当于是创建了一个新的application,这个组件跟自己app里面的其它组件并没有多大关系。然后ipc就可以起作用了,通过binder进行进程间通信。如果两个属于不同app的组件,自然是有不同的application和虚拟机了,然后通过签名文件和uid来进行数据共享。普通的资源文件比如string, color这些文件是不需要相同的uid就可以访问的,但是data里面的数据是需要这样的。具体怎么做,自己有兴趣也可以去查查资料~

7.序列化与反序列化:

首先你要知道,数据的传输都是要把数据转换成字节码,不管你是什么类型的数据。(嗯,没有例外!)序列化就是把数据转换成字节码的过程,而反序列化自然就是在数据传输的目的地把字节码转换成原始数据。之前自己为了方便,所有都使用Serializable来做序列化。后来知道Parcelable的效率更高。因为Serializable要做大量的IO操作。所以以后都要使用安卓里面的Parcelable来做序列化哦~

好了,基本的概念介绍完毕。!如果我有说的不合理的地方请大家无情给我指出!!啪啪的打我的脸


Binder的工作原理

我们直接通过aidl文件生成的源码来理解binder的工作原理!

首先新建一个aidl的文件包。声明三个aidl文件。并且在java包里面声明我们要传输的类Book。

ipc1.png

代码如下:

/**
 * Created by Zane on 16/3/16.
 */
public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

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

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

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>(){

        public Book createFromParcel(Parcel in){
            return new Book(in);
        }

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

    private Book(Parcel in){
        bookId = in.readInt();
        bookName = in.readString();
    }

    @Override
    public String toString() {
        return "bookId " + bookId +" bookName " + bookName;
    }
}


// Book.aidl
package com.example.zane.ipc_test;
parcelable Book;


// IBookManager.aidl
package com.example.zane.ipc_test;
import com.example.zane.ipc_test.Book;
import com.example.zane.ipc_test.IOnNewBookArrivedListener;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unRegisterListener(IOnNewBookArrivedListener listener);
}


// IOnNewBookArrivedListener.aidl
package com.example.zane.ipc_test;
//监听服务端是否有新书籍,如果有新书籍就立即推送到客户端,观察者模式
import com.example.zane.ipc_test.Book;
interface IOnNewBookArrivedListener {
    void newBookArrived(in Book book);
}

好了,咱先不管IOnNewBookArrivedListener.aidl这个文件。我们分析IBookManager.aidl的生成源码。项目包里面的gen目录下面有一个xxx.aidl包里面会有一个IBookManager.java的文件。我们就是要分析它!嗯,搞掂它!

当我用sublime打开它之后,老子差点吐掉。。

ipc2.png

。。然后我凭借我的强迫症一个个的给它缩进!我就是这么雷锋。。

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/Zane/编程/AndroidStudioProjects 13-52-23-071/IPC_Test/app/src/main/aidl/com/example/zane/ipc_test/IBookManager.aidl
 */
package com.example.zane.ipc_test;
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.example.zane.ipc_test.IBookManager
  {

    //binder的唯一标识符
    private static final java.lang.String DESCRIPTOR = "com.example.zane.ipc_test.IBookManager";


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

/**
 * Cast an IBinder object into an com.example.zane.ipc_test.IBookManager interface,
 * generating a proxy if needed.
 */
    public static com.example.zane.ipc_test.IBookManager asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.zane.ipc_test.IBookManager))) {
        return ((com.example.zane.ipc_test.IBookManager)iin);
      }
      return new com.example.zane.ipc_test.IBookManager.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
    {
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(DESCRIPTOR);
          return true;
        }
        case TRANSACTION_getBookList:
        {
          data.enforceInterface(DESCRIPTOR);
          java.util.List<com.example.zane.ipc_test.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        case TRANSACTION_addBook:
        {
          data.enforceInterface(DESCRIPTOR);
          com.example.zane.ipc_test.Book _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.example.zane.ipc_test.Book.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          this.addBook(_arg0);
          reply.writeNoException();
          return true;
        }
      }
    return super.onTransact(code, data, reply, flags);
  }

  private static class Proxy implements com.example.zane.ipc_test.IBookManager
  {
    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.example.zane.ipc_test.Book> getBookList() throws android.os.RemoteException
    {
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      java.util.List<com.example.zane.ipc_test.Book> _result;
      try {
        _data.writeInterfaceToken(DESCRIPTOR);
        mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
        _reply.readException();
        _result = _reply.createTypedArrayList(com.example.zane.ipc_test.Book.CREATOR);
      }
      finally {
        _reply.recycle();
        _data.recycle();
      }
      return _result;
    }

    @Override
    public void addBook(com.example.zane.ipc_test.Book book) 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 ((book!=null)) {
          _data.writeInt(1);
          book.writeToParcel(_data, 0);
        }
        else {
          _data.writeInt(0);
        }
          mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
          _reply.readException();
      }
      finally {
        _reply.recycle();
        _data.recycle();
      }
    }
  }

    static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
  public java.util.List<com.example.zane.ipc_test.Book> getBookList() throws android.os.RemoteException;
  public void addBook(com.example.zane.ipc_test.Book book) throws android.os.RemoteException;
}

如果,你是第一次接触这个,或者以前没看过什么源码,内心应该跟我之前一样,也是崩溃的。但是,我们是程序员,在源码面前千万不能低头!大概看一遍,Proxy这个类看到没,嗯,没错了,用到了代理。再看Stub这个这个内部类,继承了什么?大声告诉我!嗯,就是Binder类。没错,这个Stub就是后面多次用到的Binder!其实这么多代码,只需要理解Stub, Proxy这两个类就差不多了。我们来细看代码:

asInterface(Binder obj):

public static com.example.zane.ipc_test.IBookManager asInterface(android.os.IBinder obj)
    {

      if ((obj==null)) {
        return null;
      }

      //查询本地的binder
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

      if (((iin!=null)&&(iin instanceof com.example.zane.ipc_test.IBookManager))) {
        return ((com.example.zane.ipc_test.IBookManager)iin);
      }

      return new com.example.zane.ipc_test.IBookManager.Stub.Proxy(obj);

    }

这个方法在后面使用的部分会多次用到。作用就是将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象。不知道你有没有感受到,这其实就是一种类似接口回调的过程。在客户端使用这个方法去得到服务端的binder类型的接口,然后调用服务端的方法。

代码很简单,如果客户端和服务端在一个进程那么就返回这个binder,如果是多进程就返回远程代理类Proxy的实例。

写到这里,突然感冒加重有点发烧的感觉。。。orz,我还是坚持下去!

我们再来看Proxy类里面的两个实现了IBookManager接口的方法:

@Override
    public java.util.List<com.example.zane.ipc_test.Book> getBookList() throws android.os.RemoteException
    {
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      java.util.List<com.example.zane.ipc_test.Book> _result;
      try {
        _data.writeInterfaceToken(DESCRIPTOR);
        //调用onTransact()方法
        mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
        //读取异常
        _reply.readException();
        //获得返回值并返回给客户端
        _result = _reply.createTypedArrayList(com.example.zane.ipc_test.Book.CREATOR);
      }
      finally {
        _reply.recycle();
        _data.recycle();
      }
      return _result;
    }

data是输入对象,reply是输出对象,result是最后返回给客户端的数据。这个是getBookList的方法,没有参数,只有返回值,所以data写入token标识符之后就直接调用了Stub类里面的onTransact()方法。在onTransact()方法里面把结果值写入reply并且返回true表示客户端与服务端连接成功。如果返回false就表示连接失败!再看addBook(Book book)的方法:

@Override
    public void addBook(com.example.zane.ipc_test.Book book) throws android.os.RemoteException
    {
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try {
        _data.writeInterfaceToken(DESCRIPTOR);
        //防止传入的参数为null
        if ((book!=null)) {
          _data.writeInt(1);
          book.writeToParcel(_data, 0);
        }
        else {
          _data.writeInt(0);
        }
        //调用onTransact()方法
          mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
          _reply.readException();
      }
      finally {
        _reply.recycle();
        _data.recycle();
      }
    }
  }

我想如果你不是特别傻,应该可以类比上面看得懂这个吧!我在上面写了一些注释。然后就来看Stub是来如何响应RPC(远程过程调用)的。

@Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(DESCRIPTOR);
          return true;
        }
        case TRANSACTION_getBookList:
        {
          data.enforceInterface(DESCRIPTOR);
          java.util.List<com.example.zane.ipc_test.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        case TRANSACTION_addBook:
        {
          data.enforceInterface(DESCRIPTOR);
          com.example.zane.ipc_test.Book _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.example.zane.ipc_test.Book.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          this.addBook(_arg0);
          reply.writeNoException();
          return true;
        }
      }
    return super.onTransact(code, data, reply, flags);
  }

这个方法通过传进来的不同code来响应客户端不同的请求。

  1. TRANSACTION_getBookList里面,首先调用获得服务端的List,然后写入reply。返回true表示连接成功,并且让客户端从reply里面read出来返回过来的数据。

  2. TRANSACTION_addBook里面,通过Parcelable去new出了一个新的Book实例。然后调用服务端的addBook方法,把这个实例返回给服务端。你如果第一次使用Parceable可能会不理解怎么new出新的Book的。你回头去看看你的Book类你就懂了!

嗯!就是这么简单!源码我们基本就分析完了!我们再来总结一下流程。

  1. 客户端发出请求,然后将客户端发送请求的线程挂起(这个下篇文章再说)
  2. Binder写入参数(从客户端传入)到data,如果没有则不写入。
  3. 调用transact()方法
  4. onTransact()响应,调用Service(服务端)的实现方法,并且把客户端需要的数据写入reply,如果没有则不写入。
  5. result读出数据返回客户端,唤醒客户端,客户端获得数据。

总结

开始准备一篇文章直接写完。。发现得需要两篇了!

这篇文章我们解决了一些IPC的基本概念知识and通过aidl学习binder的工作原理。

啊啊,,,突然发烧了卧槽,好难受,下篇我们接着说AIDL的使用!

未经博主同意,不得转载该篇文章

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

推荐阅读更多精彩内容