Android进程间通信

  • 前言
  • AIDL
  • Messenger

前言

安卓开发中,提到进程间的通信,大部分人第一个想到的必是AIDL无疑。

可以理解,Android系统中,出于安全考虑,进程是每个应用的安全边界。各个应用程序都运行在自己的进程里,它们之间一般不能直接进行数据交换。

传统的Corba、Java技术采用RMI(远程接口调用)来解决不同进程之间的通讯,Android设计者模仿了这一做法,提供了AIDL Service或者Messenger,也巧妙地实现了跨进程通信。

AIDL Service和Messenger实现思路大体一种,只是应用的场景不尽相同。源码中有这样的描述:

Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. 
If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. 

英语有点渣,翻译过来大致意思是说:
只有当你允许不同客户端跨进程访问你的service(条件1),并在你的service处理多进程问题(条件2)时,AIDL 才是必须的。

如果你不需要执行不同应用之间的IPC并发,你应该通过实现Binder建立你的接口,或者如果你想执行IPC,但是不需要处理多线程。那么使用Messenger实现你的接口。

知道了使用环境的不同,下面各自细表。

AIDL

与RMI一样,AIDL Service首先需要定一个远程接口,然后为该接口提供一个实现类即可。但访问的时候有一点不一样,Android中客户端访问远程Service时,并不会把Service对象返回给客户端,而是使用了IBinder代理(这家伙在源码好多地方用到,有兴趣的可深入研究)。

说了这么多,AIDL到底是啥,可能不少初学者都会犯迷糊,尤其这中专业缩略词,笔者也极其痛恨,然并卵啊。

AIDL全称(Android Interface Definition Language),一种接口定义语言,却并不是真正的编程语言。使用起来一般注意这三点:

  • 文件必须以.aidl结尾,这没得商量;
  • 支持基本类型、String、List、Map、CharSequence,其他类型的数据类型都需要导入完整的路径。
  • 使用时,只需要定义.aidl文件,开发环境会自动将其映射为.java文件,完全不用手动添加。

有了上面的基础,我们不妨来写一个看看。
先在src的某一个包下创建一个Flower.aidl文件。

package com.cn.aidltest;
interface Flower{
    String getColor();
   int getCont();
}

然后右键refresh一下,看看你的gen文件下是不是多了一个Flower.java文件。如果有的话,那恭喜你,一个简单的aidl文件已经创建好了。

然后进行下一步,创建一个远端service。本着简单为原则。

public class AidlService extends Service {
private FlowerBinder flowerBinder;
public String color = "红色";
public int count = 3;

@Override
public IBinder onBind(Intent intent) {
    // TODO Auto-generated method stub
    return flowerBinder;
}

public class FlowerBinder extends Stub {

    @Override
    public String getColor() throws RemoteException {
        // TODO Auto-generated method stub
        return color;
    }

    @Override
    public int getCont() throws RemoteException {
        // TODO Auto-generated method stub
        return count;
    }

}

@Override
public void onCreate() {
    // TODO Auto-generated method stub
    super.onCreate();
    flowerBinder = new FlowerBinder();
    System.out.println("service已经创建");
}

}

不难发现,除了代理的Binder要换成继承自Stub之外,别的跟本地的Service并没有两毛钱的不一样。

当然,最后别忘了声明。

<service android:name="com.cn.aidltest.AidlService" >
        <intent-filter>
            <action android:name="com.cn.test.AIDL"></action>
        </intent-filter>
</service>

这样远端的Service已经创建好了,我们是不是该创建一个客户端来访问一下它试试?

客户端要简单一些,先将服务端的Flower.aidl文件copy到相同路径下,注意包名也必须一致,包名一致、包名一致、包名一致,重要的事情说三遍,不要问我为什么。

然后创建一个Activity来访问远端Service,流程跟开启绑定本地Service并没有两毛钱的不一样。

public class MainActivity extends Activity {
private Button read;
private TextView display;
private Flower flower;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Intent intent = new Intent();
    intent.setAction("com.cn.test.AIDL");
    bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
    read = (Button) findViewById(R.id.read);
    display = (TextView) findViewById(R.id.display);
    read.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            try {
                display.setText("花的颜色:" + flower.getColor() + "    花的朵数:"
                        + flower.getCont());
            } catch (RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });
}

protected ServiceConnection serviceConnection = new ServiceConnection() {

    @Override
    public void onServiceDisconnected(ComponentName name) {
        // TODO Auto-generated method stub
        flower = null;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // TODO Auto-generated method stub
        flower = Flower.Stub.asInterface(service);
    }
};

@Override
protected void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    this.unbindService(serviceConnection);
}

}

然后,就是运行一下啦,看看效果。注意一定是先启动远端Service,再开启Acitivity,不然会报错哒。

效果图就不贴了,后面附上源码,自己试试吧。

有同学可能会问,如果我要给远端Service传递复杂数据呢?

当然也是可以的,只不过需要将数据需要实现Parcelable接口序列化一下,这里不做深入探究,有兴趣的可自行尝试。

下面我们看看当远端Service不需要处理多线程时,用官方推荐的Messenger是如何实现IPC的。

Messenger

Messenger,翻译过来就是“信使”。毫无疑问,使用它来实现IPC,必须要借助于我们的老朋友Message(英语不好的同学可能已经懵逼了,这俩单词咋这么像嘞)和它的保姆Handler。

机理其实也比较简单,无非是客户端发送了一个Message给服务端,服务端对这个Message进行答复,返回给客户端。

下面我们还是通过一个小例子直观地看一下。首先创建一个远端Service:

public class MessageService extends Service {
private static final int FLAG = 0x110;

@Override
public void onCreate() {
    // TODO Auto-generated method stub
    super.onCreate();
    System.out.println("MessageService已启动");
}

@Override
public IBinder onBind(Intent intent) {
    // TODO Auto-generated method stub
    return messenger.getBinder();
}

public Messenger messenger = new Messenger(new Handler() {

    @Override
    public void handleMessage(Message message) {
        // TODO Auto-generated method stub
        super.handleMessage(message);
        switch (message.what) {
        case FLAG:
            try {
                // 模拟耗时
                Thread.sleep(500);
                System.out.println("收到客户端的消息: " + message.arg1);
                // message.arg2 = message.arg1 + message.arg2;
                // Send a Message to this Messenger's Handler
                // message.replyTo.send(message);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // catch (RemoteException e) {
            // // TODO Auto-generated catch block
            // e.printStackTrace();
            // }
            break;
        }
    }

});

}

我们看到了,代码其实也不难,返回代理IBinder时,用的是messenger.getBinder(),此外需要一个messenger来收发,一个Handler来处理消息。

此外不要忘了注册。

然后再创建一个客户端,来访问这个Service。

```
public class MainActivity extends Activity {
private static final int FLAG = 0x110;
private Button read;
private TextView display;
private Messenger messenger;// 客户端的信使

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Intent intent = new Intent();
    intent.setAction("com.cn.message.MESSAGE");
    bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
    display = (TextView) findViewById(R.id.display);
    read = (Button) findViewById(R.id.read);
    read.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            //创建一条消息
            Message message = Message.obtain(null, FLAG, 12, 12);
            // message.replyTo = messengeSelf;
            try {
                // 往服务端发送消息
                messenger.send(message);
            } catch (RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });
}

// public Messenger messengeSelf = new Messenger(new Handler() {
//
// @Override
// public void handleMessage(Message msg) {
// // TODO Auto-generated method stub
// super.handleMessage(msg);
// if (msg.what == FLAG) {
// System.out.println("收到服务端的消息:" + msg.arg2);
// }
// }
//
// });

protected ServiceConnection serviceConnection = new ServiceConnection() {

    @Override
    public void onServiceDisconnected(ComponentName name) {
        // TODO Auto-generated method stub
        messenger = null;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // TODO Auto-generated method stub
        System.out.println("service已连接...");
        messenger = new Messenger(service);
    }
};

}

流程大致相似,返回的messenger比较特殊,使用的是new Messenger(),虽然是new,但是打开源码会发现,它其实是这样的:

public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

有木有很眼熟啊?是了,跟aidl如出一撤,原理基本一样,只不过Messenger委托了Handler来处理消息。

运行代码看一下效果。老样子,还是先运行Service,再运行客户端,不然会出错的。

有的同学可能要问了,这样实现了客户端访问服务,只是单工通信,有什么卵用?如果这样想,你可就错怪我大安卓了。信使messenger其实要比你想象的强大的多,它不仅可以让服务端访问客户端,甚至还能将一个进程里的消息,发送到另外一个进程里,当然这就比较深入了,这里不做探讨。

话又说回来,双工通信如何实现呢?
代码中其实已经实现了,只需要将注释掉的代码恢复一下就可以。原理就是,在客户端也创建一个自己的信使messenger,并将发送的消息交付给它,然后就能处理从服务端收回的消息了。

下面附上源码链接,有兴趣的可自行尝试,欢迎留言拍砖。

地址:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值