IPC的用法的学习 一

IPC的用法的学习

这篇文章来小结一下自己学的IPC的知识,包括Messenger和AIDL,然后我在总结一下自己最近学的关于Binder的知识。

在了解IPC前先说明一下什么是进程什么是线程,按照操作系统中的描述,线程是CPU调度的最小单元,同时线程是一种有限的系统资源,而进程一般指一个执行单元,在android指一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系,在最简单的情况下,一个进程中可以只有一个线程即主线程,在android中主线程也叫UI线程,在UI线程里面才能操作界面元素。IPC(进程间通信)是主要用于两个进程,有时候我们需要在应用间(即两个进程间)交换数据,这时候就需要IPC。实现进程间通信有很多方式,我这里就只介绍Messenger和AIDL,至于其它的方式我就不介绍了。

Messenger

Messenger就是对Binder做了一个简单的封装,我们在做一些简单的跨进程通信的时候可以使用Messenger,比AILDL简单。首先看一下构造方法

Messenger(Handler target);

//用一个Handler创造一个Messenger实例,当有一个新的Message发送到这个Messenger的时候,这个Messenger就回调用它的handler.sendMessage(),这个时候这个Message就会被这个Handler所处理。里面具体的怎么跨进程的就留到总结Binder的时候再细说,这里就需要理解到这个时候就会调用远程进程的那个Handler。

Messenger(IBinder target);
//用一个IBinder对象来构建一个Messenger实例,这个Messnegr就会关联到这个IBinder对象的Handler,向这个Messenger发送 Message的时候就会让对应的Handler来处理,这个主要用来在客户端构造服务端的Messenger对象时调用。

下面来个简单的例子:
上代码:

//服务端
public class MessengerService extends Service {
    public static final int  SAY_HELLO =1;
    class MessengerHandler  extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case SAY_HELLO:
                    Bundle data = msg.getData();
                    String str = data.getString("data");
                    Log.e("TAG","MessengerService Received say_hello: "+str);

                    break;
            }
        }
    }
    private Messenger mMessenger = new Messenger(new MessengerHandler());

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mMessenger.getBinder();
    }
    
}
//客户端
 private ServiceConnection mConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mMessenger = new Messenger(service);
        Message msg = Message.obtain(null,MessengerService.SAY_HELLO);
        Bundle data = new Bundle();
        data.putString("data","Hello from Client");
        msg.setData(data);
        try {
            mMessenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mBond = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mBond = false;
    }
};
看一下服务端的打印
ipc_1.png

上面只是客户端向服务端传递数据,那么服务端向客户端传递数据的原理是一样的,首先需要创建一个客户端的Messenger的实例,然后丢给发送到服务端的Message的replyTo字段,服务端去的这个字端就是拿到了客户端的Messenger,就可以向客户端发送数据了。下面看一下更改后的代码:

//客户端
class ClientHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case 1:
                Bundle data = msg.getData();
                String str = data.getString("data");
                Log.e("TAG","客户端已经收到服务端的信息"+str);
            break;
        }
    }
}
private Messenger mClient = new Messenger(new ClientHandler());
private ServiceConnection mConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mMessenger = new Messenger(service);
        Message msg = Message.obtain(null,MessengerService.SAY_HELLO);
        Bundle data = new Bundle();
        data.putString("data","Hello from Client");
        msg.setData(data);
        msg.replyTo = mClient;
        try {
            mMessenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mBond = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mBond = false;
    }
};
//服务端的代码
public class MessengerService extends Service {
public static final int  SAY_HELLO =1;
class MessengerHandler  extends Handler{
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case SAY_HELLO:
                Bundle data = msg.getData();
                String str = data.getString("data");
                Log.e("TAG","MessengerService Received say_hello: "+str);

                Messenger mClient = msg.replyTo;
                Message mMsgClient = new Message();
                mMsgClient.what = 1;
                Bundle bun = new Bundle();
                bun.putString("data","服务端已经收到你的消息,稍后会回复你的....");
                mMsgClient.setData(bun);
                try {
                    mClient.send(mMsgClient);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
        }
    }
}
private Messenger mMessenger = new Messenger(new MessengerHandler());

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

}
ipc_2.png
AIDL(Android 接口定义语言)
  • 定义 AIDL 接口
    当每次新建一个aidl文件时,Android SDK 工具都会生成一个基于该 .aidl 文件的 IBinder 接口,并将其保存在项目的 gen/ 目录中。服务必须视情况实现 IBinder 接口。然后客户端应用便可绑定到该服务,并调用 IBinder 中的方法来执行 IPC。一般创建一个aidl服务执行下面的步骤:
    1.创建 .aidl 文件
    2.实现接口
    Android SDK 工具基于您的 .aidl 文件,使用 Java 编程语言生成一个接口。此接口具有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现方法。

    3.向客户端公开该接口
    实现 Service 并重写 onBind() 以返回 Stub 类的实现。

上代码:

//新建AIDL文件
// First.aidl
package com.hq.demo.aidltest;

// Declare any non-default types here with import statements

interface First {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    String getStringFromClient(in String str);
}

//服务端的代码,实现其Stub类
public class FirstAidlService extends Service {
private FirstBinder mBinder;
@Override
public void onCreate() {
    super.onCreate();
    mBinder = new FirstBinder();
}

public FirstAidlService() {
}

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


class FirstBinder extends First.Stub{

    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

    }

    @Override
    public String getStringFromClient(String str) throws RemoteException {
        return "String from Server    "+str ;
    }
}
}

 //客户端绑定服务时需要的ServiceConnection
 private ServiceConnection mConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mBinder = First.Stub.asInterface(service);//获得服务端的Binder对象,并将其转换成为First类,此时客户端就可以调用服务端的代码了,实现跨进程访问
        mBond = true;
        try {
            String str = mBinder.getStringFromClient("Client to Server");
            Log.e("TAG","Client : "+str);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mBond = false;
    }
};

看下日志:


ipc_aidl_4.png

客户端和服务端实现了跨进程调用,这是最简单的跨进程调用,我们客户端只是向服务端传递了一个字符串,并且服务端返回了一个字符串,下面就了解一下AIDL跨进程调用能够传递哪些数据。

  • 通过 IPC 传递对象

默认情况下,AIDL 支持下列数据类型:
Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等),String,CharSequence
List
List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 可选择将 List 用作“通用”类(例如,List<String>)。

另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
Map
Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。

上面是基本的类型,通过 IPC 接口把某个类从一个进程发送到另一个进程是可以实现的。 不过,您必须确保该类的代码对 IPC 通道的另一端可用,并且该类必须支持 Parcelable 接口。因为是跨进程这个对象在两个进程中的包名都必须是一样的,因为在不同的进程,就是不同的对象,程序需要把另一个进程传递过来的对象,实例化为本进程的可识别的对象。把需要传递的对象实现Parcelable接口,并且要定义成.aidl接口的形式,在写aidl文件的时候必须为每个附加类型加入一个import语句,即使这些类型与你定义的aidl的接口在同一个包中。还有一点就是在写aidl方法时,如果方法的参数类型时非源语,那么就必须要加上指示数据走向的方向标记,可以是in,out,inout,原语默认是in,不能是其它方向

注意:您应该将方向限定为真正需要的方向,因为编组参数的开销极大。

上代码:

// First.aidl
package com.hq.demo.aidltest;
import com.hq.demo.aidltest.Book;   
interface First {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    String getStringFromClient(String str);
    Book addBook(in Book book);
    List<Book> getBookList();
}

//服务端
public class FirstAidlService extends Service {
    private FirstBinder mBinder;
    //因为通过Binder类调用服务端的方法,改方法会执行在Binder线程池里面,不是
    //主线程,所以需要关注线程间的同步问题。而CopyOnWriteArrayList已经帮我
    //们实现了线程同步的问题。
    private CopyOnWriteArrayList<Book> mData;

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

        mData = new CopyOnWriteArrayList<>();
        Book b1 = new Book();
        b1.setmBookName("book1");
        b1.setmPrice(1.0f);
        Book b2 = new Book();
        b2.setmBookName("book2");
        b2.setmPrice(2.0f);
        mData.add(b1);
        mData.add(b2);
        mBinder = new FirstBinder();
    }

    public FirstAidlService() {
    }

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

    //实现First.Stub 接口,实现里面的方法,在onBind()里面返回相应的Binder类,
    class FirstBinder extends First.Stub{
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
        
        }

        @Override
        public String getStringFromClient(String str) throws RemoteException {
            return "String from Server    "+str ;
        }

        @Override
        public Book addBook(Book book){
            Book book1 = new Book();
            Log.e("TAG","Server addBook from Client Book:"+book);
            book1.setmBookName("ServerBookName");
            book1.setmPrice(10.2f);
            mData.add(book);
            return book1;
        }

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

    }

}
// 客户端代码
private ServiceConnection mConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mBinder = First.Stub.asInterface(service);//获得服务端的Binder接口
        mBond = true;
        try {
            String str = mBinder.getStringFromClient("Client to Server");
            Log.e("TAG","Client : "+str);
            Book b = new Book();
            b.setmBookName("Client Book");
            b.setmPrice(9.1f);
            //通过Binder调用服务端的方法
            Book book = mBinder.addBook(b);
            Log.e("TAG", "AIDL Base addBook(): " + book);

            List<Book> books  = mBinder.getBookList();
            Log.e("TAG",books.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mBond = false;
    }
}
ipc_aidl_5.png
接下来,简单介绍两个很重要的方法,linkToDeath()和unlinkToDeath();我们知道,Binder运行在服务端的进程中,如果服务端进程由于某种原因异常终止,这个时候如果我们还在调用服务端的方法,这就会导致调用失败,这个时候急需要用到linkToDeath();当Binder死亡的时候我们就会收到相应的通知。

上代码:

 private IBinder.DeathRecipient mRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        Log.e("TAG","mRecipient binderDied");
        if(mBinder == null)
            return;
        mBinder.asBinder().unlinkToDeath(mRecipient,0);
        mBinder = null;
       //下面可以做重新绑定的操作
    }
};


 @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mBinder = First.Stub.asInterface(service);
        mBond = true;
        try {
        ........
            service.linkToDeath(mRecipient,0);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

上面就实现了当服务端的进程异常终止时,所要回调的方法,并且可以执行重新绑定服务的操作。

这篇文章就到这吧,太长了,接下来还会继续介绍AIDL和Binder相关的知识点。

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

推荐阅读更多精彩内容