什么是IPC?

IPC全称Inter-Process Communication,含义为进程间通信,又叫跨进程通信,是指两个进程之间进行数据交互的过程。在学习IPC之间我们要先区分进程和线程的区别以及了解多进程。

  • 进程:是系统进行资源分配和调用的独立单位,在PC和移动设备上指一个程序或者一个应用。
  • 线程:线程是CPU调度的最小单元,一个进程中可以包含一个或多个线程

而多进程在Android中是指一个应用中存在多个进程的情况,我们可以通过给四大组件(Activity、Service、BoardcastReceiver、ContentProvider)在AndroidMenifest中指定android:process属性开启多进程。

但是由于Android为每一个进程都分配了一个独立的虚拟机,我们知道,不同的虚拟机在内存分配上有不同的地址控件,这就导致在不同的虚拟机中访问同一个类的对象会产生多个副本。从而导致下列问题:

  • 静态成员变量和单例模式完全失效
  • 线程同步机制完全失效
  • SharedProferences可靠性降低
  • Application被多次创建

为了解决上面这些问题,系统提供了非常多的方法来实现多进程间的通信,比如:

  • Intent
  • sharedPreferences
  • 共享文件
  • binder
  • AIDL
  • Socket等

在了解这些方法之前,我们需要了解一下Android序列化过程。

二、Serializable和Parcelable

Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据的时候就需要用到Parcelable和Serializable。我们也可以通过Serializable来完成对象的持久化。

Serializable

使用Serializable来实现序列化相当简单,只需要让类实现Serializable接口并在类的声明中制定一个SerialVersionUID的标识即可自动实现默认的序列化接口,举个🌰:

public class Book implements Serializable {
	private static final long serialVersionUID = 51906712371497665L;
	
	public int bookId;
	public String bookName;
	public boolean isRead;
	....
}

实现起来非常简单,几乎所有工作都被系统自动完成。那么如何进行对象的序列化和反序列化呢?只需要采用ObjectOutputStream和ObjectInputStream即可:

//序列化过程
User user = new User(“0”,“jake”,“true”);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();

//反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close()

以下几点需要注意:

  • 恢复后的对象newUser和user的内容完全一样,但是两者并不是同一个对象。
  • 即使不指定serialVersionUID也可以实现序列化,但是有可能造成反序列化失败。序列化的时候系统会把当前的serialVersionUID写入序列化文件中,当反序列化的时候系统会去检测文件中的serialVersionUID是否与当前类中的serialVersionUID匹配,如果不匹配就说明当前类和序列化的类相比发生了某些变化,这个时候是无法正常的反序列化的。
  • 静态成员变量属于类不属于对象,因此不会参与序列化过程
  • 用transient关键字标记的成员变量不参与序列化过程
Parcelable

Parcelable的实现方式比Serializable要复杂一点,举个🌰:

public class Book implements Parcelable {
	public int bookId;
	public String bookName;
	public boolean isRead;

	public User user;

	public Book(int bookId,String bookName,boolean isRead){
		this.bookId = bookId;
		this.bookName = bookName;
		this.isRead = isRead;
	}
	
	//内容描述 几乎所有情况下都返回0,如果含有文件描述符返回1
	public int describeContents(){
		return 0;
	}
	//序列化 Parcel内部包装了可序列化的数据,可以在Binder中自有传输
	public void writeToParcel(Parcel out,int flags){
		out.writeInt(bookId);
		out.writeString(bookName);
		out.writeInt(isRead ? 1 : 0);
		//由于user是另一个可序列化对象,所以他的反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的错误
		out.writeParcelable(user,0);
	}
	//反序列化 CREATOR内部标明了如何创建序列化对象和数组
	public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>(){
		public Book createFromParcel(Parcel in) {
			return new Book(in);
		}

		public User[] newArray(int size) {
			return new Book[size];
		}
	});
	
	private Book(Parcel in){
		bookId = in.readInt();
		bookName = in.readString();
		isRead = in.readInt()==1;
		user = in.readParcelable(Thread.currentThread().getContextClass-Loader());
	}
}

系统中许多类都实现了Parcelable接口,例如:Intent、Bundle、Bitmap等,同时list和map也可以序列化,前提是它们里面的每个元素是可序列化的。

如何选择?

Serializable是Java中的序列化接口,使用起来简单但是开销很大,序列化过程和反序列化过程需要大量的I/O操作。
Parcelable是Android提供的序列化方式,更适用于Android平台,缺点是使用的时候相对比较麻烦,但是效率高。
所以在Android开发上,我们在内存序列化上,我们首选Parcelable。但是将对象序列化到存储设备中或者将对象序列化后通过网路传输则建议使用Serializable,因为Parcelable实现这个过程相对比较麻烦。

三、AIDL

AIDL全称是Android Interface Definition Language,翻译过来就是安卓接口定义语言。是用来定义服务器和客户端通信接口的一种语言,可以拿来生成用于IPC的代码。某种意义上来说AIDL其实是一个模版,因为在使用过程中,真正起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模版。通过AIDL,可以在一个进程中后去另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求。这里我们只简单讲一下AIDL的使用,举个简单的🌰:

需要实现的功能是:客户端通过绑定服务器端的Service的方式来调用服务器端的方法,获取服务器端的书籍列表并向其添加书籍,实现应用间数据共享。

由于是进程间通讯,所以我们需要新建两个工程,一个作为服务器端一个作为客户端。

服务端

首先我们需要在服务器端新建一个Book类并实现Parcelable接口,如下:

public class Book implements Parcelable {

    public String name;
    public int pages;

    public Book(String name, int pages) {
        this.name = name;
        this.pages = pages;
    }

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

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

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

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

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

然后在服务端工程中右键新建一个和Book同名的AIDL文件Book.aidl,这个Book.aidl是Book类在AIDL中的声明:

package com.aline.trex;
//注意这里的parcelable的p是小写
parcelable Book;

然后我们接着在服务端工程右键新建一个IBookManager.aidl,我们需要在里面定义为客户端提供的方法:

// IBookManager.aidl
package com.aline.trex;
// Declare any non-default types here with import statements

import com.aline.trex.Book;
interface IBookManager {
	//获取书籍列表
   List<Book> getBookList();
   //新增书籍
   //这里的in是定向Tag,表示跨进程通讯中数据的流向,in表示数据只能由客户端流向服务端,out表示数据只能由服务端流向客户端,inout表示双向流通。基本数据类型、String、CharSequence类型的参数值定向Tag只能为in,除此之外的其他类型参数值都需要明确标注使用哪种定向Tag
   void addBook(in Book book);
}

定义好之后,我们需要sycn project一下,然后新建一个service,在service里创建一个内部类,继承刚刚创建的IBookManager中的Stub类,并实现接口方法。

public class MyService extends Service {
    private static final String TAG = "MyService";

    public List<Book> list;

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        list = new ArrayList<>();
        initData();
    }

    private void initData() {
        Book book;
        for (int i = 0; i < 3; i++) {
            book = new Book("人间日报第" + i + "期", i);
            list.add(book);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return new MyBinder();
    }

    class MyBinder extends IBookManager.Stub {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return list;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            if (book != null) {
                list.add(book);
            } else {
                Log.e(TAG, "接收到空对象");
            }

        }
    }
}

我们在创建IBookManager时里面并没有stub对象,那么它是哪来的呢?上面说过,进程间通信真正起作用的并不是aidl文件,而是系统据此生成的文件,这个stub对象就是系统自动生成的,它继承于Binder类,可以看到我们的onBind方法最终返回的也是stub。最后不要忘记在AndroidManifest文件中注册我们的Service:

android aidl通信 主线程_ipc


注意:在新建AIDL文件的时候,系统自动生成一个basicTypes方法,这个方法可以无视,看注解知道这个方法只是告诉你在AIDL中你可以使用的基本类型(int、long、boolean、float、double、String),可以删除掉。

客户端

在客户端中我们需要把服务端的AIDL文件及Book类复制过来,将aidl文件夹整个复制到和Java文件夹同个层级下,不需要改动任何代码

android aidl通信 主线程_ide_02

创建和服务端Book类所在的相同包名来存放Book类:

android aidl通信 主线程_ide_03


在activity中我们通过隐式意图来绑定service,在onserviceConnected方法中通过IBookManager.Stub.asInterface(service)获取iBookManager对象,然后在点击事件中调用通过iBookManager对象调用获取书籍和添加书籍的方法。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivity";

    private IBookManager iBookManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

        Intent intent = new Intent();
        intent.setAction("com.aline.MyService");
        intent.setPackage("com.aline.trex");
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iBookManager = IBookManager.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, BIND_AUTO_CREATE);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_get_book://获取书籍
                List<Book> list = null;
                try {
                    list = iBookManager.getBookList();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                if (list != null) {
                    for (Book book : list) {
                        Log.e(TAG, book.name + "-" + book.pages);
                    }
                }

                break;
            case R.id.btn_add_book://添加书籍
                try {
                    iBookManager.addBook(new Book("新的一本书", 3));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }

    }

    private void initView() {
        Button btn_click = findViewById(R.id.btn_get_book);
        Button btn_add_book = findViewById(R.id.btn_add_book);

        btn_click.setOnClickListener(this);
        btn_add_book.setOnClickListener(this);

    }
}

代码到这里就编写完成了,让我们来测试一下,点击查看书籍按钮:

android aidl通信 主线程_序列化_04

然后点击新增书籍按钮之后再次点击查看书籍按钮:

android aidl通信 主线程_ipc_05

可以看到在原有的3本书籍之外,还多了一本我们新增的“新的一本书”。