Android学习笔记之IPC

目录

一、IPC的基本概念

二、IPC的几种方式

1.使用Bundle传输

2.通过共享文件读写数据

3.使用Messenger传输

4.使用AIDL传输

4.1.什么是AIDL

4.2.使用AIDL文件的注意事项

4.3.使用AIDL的注意事项

4.4.使用AIDL实现IPC

5.使用ContentProvider 


一、IPC的基本概念

所谓IPC,是Inter-Process Communication的缩写,即跨进程通讯。说道进程,就要区别于线程:

  • 线程是cpu调度的最小单元,同时线程是一种有限的系统资源
  • 进程一般指一个执行单元,在PC和移动设备上指一个程序或一个应用,一个进程可以包含多的线程

二、IPC的几种方式

1.使用Bundle传输

这个在之前的文章《Intent传递数据》中有讲过,所以不再赘述了,思路就是将数据(对象)绑定在Bundle上,然后再将Bundle绑定在Intent上,最后通过Intent传递数据。

2.通过共享文件读写数据

这种IPC方式的思路是,多个进程通过共享同一个文件来交换数据。比如A进程把数据写入文件,然后B进程通过读取这个文件来获取数据。这个听起来就像SharedPreferences,因为SharedPreferences的底层实现就是采用XML文件来存储键值对,而这个文件就在/data/data/package name/shared_prefs目录下。因此,就像SharedPreferences在多进程模式下,面对高并发的读写访问,有很大几率丢失数据那样,文件共享方式的IPC仅适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写的问题。

3.使用Messenger传输

Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的跨进程传递了。

先来看一个客户端向服务端发送请求的样例:

因为样例是在一个应用程序中开启了多进程模式,因此服务端通过指定process属性来指定其运行所在进程:

<service
    android:name=".messenger.MessengerService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote" />

1.服务端:创建一个服务来处理客户端请求,然后在其内部创建一个Handler并通过它创建一个Messenger对象,最后在onBind()方法中返回Messenger的底层Binder

public class MessengerService extends Service {
    private static class MessengerHandler extends Handler{
        
    }
    private final Messenger mMessenger = new Messenger(new MessengerHandler());
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

2.客户端:bindService方式启动服务的基本流程。在ServiceConnection的onServiceConnected()方法中获取到服务端返回的Binder对象,并通过它构造Messenger对象,再借由Messenger发送Message传输数据。

public class MessengerActivity extends AppCompatActivity {

    private static final String TAG = "MessengerActivity";
    private Messenger mService;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            //第二个参数即为msg.what的值
            Message msg = Message.obtain(null,1);
            Bundle data = new Bundle();
            data.putString("msg","hello,this is client.");
            msg.setData(data);
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate: ");
        setContentView(R.layout.activity_messenger);
        Intent intent = new Intent(this,MessengerService.class);
        bindService(intent,connection, Context.BIND_AUTO_CREATE);
    }

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

3.服务端:在客户端发出数据后,我们就可以接着来写服务端的数据接收工作了,重写Handler的handleMessage()方法

private static class MessengerHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 1:
                Log.i(TAG, "Receive msg from client: "+msg.getData().getString("msg"));
                break;
            default:
                super.handleMessage(msg);
        }
    }
}

这么一来,我们就完成了客户端向服务端的单向跨进程通讯。这里有一个小细节,就是Message是可以通过obj来传递对象的,可为什么还要通过Bundle呢? 这是因为非系统的Parcelable对象无法通过obj字段来传输。因此,跨进程通讯时,Message中能使用的载体只有what、arg1、arg2、Bundle以及replyTo。

4.想要让服务端返回数据给客户端,其实操作上与上述类似。首先让我们处理一下服务端的返回数据:

private static class MessengerHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 1:
                Log.i(TAG, "Receive msg from client: "+msg.getData().getString("msg"));
                Messenger client = msg.replyTo;
                Message replyMessage = Message.obtain(null,2);
                Bundle bundle = new Bundle();
                bundle.putString("reply","I got,I'll reply soon.");
                replyMessage.setData(bundle);
                try {
                    client.send(replyMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                super.handleMessage(msg);
        }
    }
}

可以看到,我们先通过msg.replyTo从客户端那里拿到一个Messenger对象client,然后通过它去发送一个回复用的Message对象replyMessage,需要回复的数据就通过它来传递。

5.上面用到了客户端传递的replyTo,目前还没有添加进去,now,let's do it。在客户端中编辑:

private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler{
    
}

首先得为客户端声明一个Messenger的成员变量,和服务端一样,先创建一个Handler然后通过它创建一个Messenger。

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mService = new Messenger(service);
        Message msg = Message.obtain(null,1);
        Bundle data = new Bundle();
        data.putString("msg","hello,this is client.");
        msg.setData(data);
        //变更在这里
        msg.replyTo = mGetReplyMessenger;
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

然后在发送请求的同时,将上述声明的Messenger成员变量mGetReplyMessenger通过replyTo字段传递给服务端。

6.客户端:最后编辑一下接收服务端返回数据的工作,重写Handler的handleMessage()方法

private static class MessengerHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 2:
                Log.i(TAG, "Receive msg from service: "+msg.getData().getString("reply"));
                break;
            default:
                super.handleMessage(msg);
        }
    }
}

4.使用AIDL传输

4.1.什么是AIDL

AIDL是android interface description language的缩写,即Android接口描述语言。使用AIDL是为了方便系统为我们生成代码从而实现跨进程通讯。

那么如何理解使用AIDL实现的IPC呢?我个人理解为:假如现在有一家无良公司,把所有员工都关在各自的房间里专心工作,不允许他们私下交流。那么员工A(进程A)想和员工B(进程B)商量一下工作相关的事情(通讯)该怎么办呢,这个时候公司就派出一个管理员(Binder驱动),让他做了一张表(ServiceManager)用来记录所有员工的名字和门牌号,每个员工都只能联系这个管理员。这么一来,当员工A想联系员工B的时候,就先呼叫管理员,问他B的门牌号,然后等管理员查表告诉他门牌号之后,A再写封信让管理员送到指定门牌号的地方。然而有一天B终于忍受不了A每天向他炫耀按摩椅有多舒服,他就去问管理员能不能让A把按摩椅送过来。管理员告诉他,不行,只能寄信。于是A就把按摩椅的购买地址写在纸上(序列化)寄给B,B再去那个地方给自己买了一个按摩椅(反序列化)。

4.2.使用AIDL文件的注意事项

1.AIDL文件(*.aidl的文件)仅支持以下六种数据类型

  • 基本数据类型(int,long,char,boolean,double等)
  • String和CharSequence
  • List:只支持ArrayList,里面的每个元素都必须能够被AIDL支持
  • Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
  • Parcelable:所有实现了Parcelable接口的对象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

2.如果AIDL文件中使用到了自定义的Parcelable对象,那么需要新建一个与它同名的AIDL文件,并在其中声明它为parcelable。比如在IBookManager.aidl文件中要使用序列化的Book对象,那么需要额外新建Book.aidl,如下

//Book.aidl
package com.xxxx.xxxxx.ipcdemo.aidl;
parcelable Book;

 3.如果AIDL文件中使用到了自定义的Parcelable对象或者AIDL对象,不管它们是否和当前的AIDL文件位于同一个包内,都必须显示import进来。比如在IBookManager.aidl文件中要使用序列化的Book对象,那么需要在最开始将 Book import 进来,如下

//IBookManager.aidl
package com.xxxx.xxxxx.ipcdemo.aidl;
import com.xxxx.xxxxx.ipcdemo.aidl.Book;

interface IBookManager {

}

4.AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out、inout。其中in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。比如在IBookManager接口中定义两个方法,其中addBook方法的参数指定为输入型

//IBookManager.aidl
package com.xxxx.xxxxx.ipcdemo.aidl;
import com.xxxx.xxxxx.ipcdemo.aidl.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

5.AIDL接口中只支持声明方法,不支持声明静态常量。(区别于传统的接口)

6.建议将所有和AIDL相关的类和文件全部放入同一个包中,以便将整个包复制到客户端中。因为AIDL的包结构在服务端和客户端要保持一致,否则反序列化会出错。

4.3.使用AIDL的注意事项

在使用AIDL传输的时候,由于数据的载体是Binder,因此要注意以下两点:

  • 客户端发起远程请求时,当前线程会被挂起直到服务端进程返回数据,因此不能在UI线程中发起远程请求
  • 服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了

但是Messenger方式传输的底层实现不也是AIDL么?为什么它就没有在服务端采用同步锁呢?这是因为Messenger传输一次只处理一个请求,服务端中不存在并发执行的情形,因此在服务端 我们不用考虑线程同步的问题。

4.4.使用AIDL实现IPC

Demo场景:

服务端是一个图书馆,客户端可以查询服务端的所有书籍。然后简单起见,只再额外添加一个方法允许客户端请求服务端添加书籍(addBook方法)。

1.创建AIDL文件包(此步骤中所有新建文件都在该包内)

在app/src/main目录下新建一个AIDL文件夹(与java目录同级,具体新建方式可以右键main,New→Folder→AIDL Folder)

然后编辑app/build.gradle文件,在android闭包中添加如下代码使上述文件夹生效

sourceSets {
    main {
        java.srcDirs = ['src/main/java', 'src/main/aidl']
    }
}

再来新建Book实体类,让其实现Parcelable接口

package ein.aidlserver;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable{
    private String bookName;
    private int bookId;

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

    protected Book(Parcel in) {
        bookName = in.readString();
        bookId = 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(bookName);
        dest.writeInt(bookId);
    }
    //get,set方法
    ......
}

参考使用AIDL文件注意事项的第2条,新建Book.aidl文件

package ein.aidlserver;
parcelable Book;

最后新建AIDL的接口文件IBookManager.aidl。注意即使和Book.java文件同在ein.aidlserver包下,也需要手动import

package ein.aidlserver;

import ein.aidlserver.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

PS:这个时候我们手动Rebuild Project就可以发现系统为我们生成的接口文件IBookManager.java了。具体位置如下

分析这个文件我们就可以知道AIDL是如何实现IPC的。在这里我推荐这位前辈的博文深入浅出AIDL(二)

2.通讯桥梁搭好了之后,我们再来创建服务端(此时的代码已经不在上述的AIDL包中了哟)

首先,新建一个服务BookManagerService用来接收客户端的请求。

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

    //这里的CopyOnWriteArrayList支持并发读写,用它来处理线程同步问题
    private CopyOnWriteArrayList<Book> mBooklist = new CopyOnWriteArrayList<>();
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBooklist;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBooklist.add(book);
        }
    };

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

    @Override
    public void onCreate() {
        super.onCreate();
        mBooklist.add(new Book("The Da Vinci Code",1));
        mBooklist.add(new Book("Thinking in Java",2));
    }
}

可以看到,在onBind方式启动服务的时候,我们是需要返回一个IBinder对象的。然后我们发现系统生成的IBookManager有个内部类Stub继承了Binder,所以直接拿过来用

public static abstract class Stub extends android.os.Binder implements ein.aidlserver.IBookManager {

同时Stub又实现了IBookManager接口,因此要重写我们在AIDL文件中定义的那两个方法。最后在onCreate方法里面暂时添加两本书。

然后为了客户端能简单的调用该服务,我们在AndroidManifest中为该服务指定一下action属性(这里采用了包名加类名)

<service
    android:name=".BookManagerService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.xxxx.xxxxx.aidldemoserver.BookManagerService"/>
    </intent-filter>
</service>

3.客户端的创建

首先要做的就是把AIDL文件包(第一步创建的那个)整个拷贝到客户端的工程里面来(同样放在与java同级的位置)。

然后,客户端直接通过onBind的方式绑定远程服务,再通过服务端返回的Binder对象转换成AIDL接口IBookManager,这样就可以通过它调用服务端的远程方法了。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                //由于目前方法体是执行在UI线程中的,假设getBookList方法耗时,需要再开子线程去执行
                List<Book> list = bookManager.getBookList();
                for(Book book : list)
                    Log.d(TAG, "query book list: "+"book id is "+book.getBookId()+" book name is "+book.getBookName());
            } 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.setAction("com.xxxx.xxxxx.aidldemoserver.BookManagerService");
        intent.setPackage("com.xxxx.xxxxx.aidldemoserver");
        bindService(intent,connection, Context.BIND_AUTO_CREATE);
    }

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

这样一来,在手机装上上面的两个应用,打开客户端便能看到日志中输出了服务端的图书列表。接着我们再来尝试添加书籍

public void onServiceConnected(ComponentName name, IBinder service) {
    IBookManager bookManager = IBookManager.Stub.asInterface(service);
    try {
        //由于目前方法体是执行在UI线程中的假设getBookList方法耗时,需要再开子线程去执行
        List<Book> list = bookManager.getBookList();
        for(Book book : list)
            Log.d(TAG, "query book list: "+"book id is "+book.getBookId()+" book name is "+book.getBookName());
        //添加书籍
        bookManager.addBook(new Book("In the Cases",3));
        list = bookManager.getBookList();
        for(Book book : list)
            Log.d(TAG, "query book list: "+"book id is "+book.getBookId()+" book name is "+book.getBookName());
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

重新运行可见如下日志

5.使用ContentProvider 

可参考我之前的博文ContentProvider

猜你喜欢

转载自blog.csdn.net/Ein3614/article/details/82744462