Android开发艺术探索笔记(7)- IPC方式

来源:互联网 发布:php环境配置教程 百度 编辑:程序博客网 时间:2024/06/05 09:43

之前说的多进程模式和Binder都是为了这节做的铺垫(这个铺垫好长呀~~)。那么Android的IPC方式有多少种呢?
(1)Bundle
(2)文件同享
(3)Messenger
(4)AIDL
(5)ContentProvider
(6)Socket

Bundle

好吧,这个也算是一种IPC??Bundle的确是实现了Parcelable接口,但是也只是一种数据集合类型,如形同Map。我们都忘了,Intent这个的存在。我们可以通过Intent去启动一个程序Activity,Service,通知一个程序Receiver。而Bundle可以通过setData到Intent中,这就达到IPC的效果了。(这下认了吧!!)

文件同享

一个普通文件可以由所有进程读和写,一个进程去写如些数据,如对象序列化到文件,另一个进程就可以去读取这个文件的内容,如将文件内容反序列化成对象,这也可以达到IPC的目的。不过要注意的是,一个文件并发读/写的问题。之前说过Android的SharedPreferences是采用xml文件保存键值对,底层也是一个文件。之前也说过为什么多进程读/写SharedPreferences会造成数据混乱,可能说的不是很明白。现在说的明白点,文件并发性读写是一个问题。还有个严重的问题是,程序内存会还保存了一份SharedPreferences的缓存,所以读写数据变得不可靠。所以不建议在多进程使用SharedPreferences。

Messenger

Messenger被意为信使(表示之前没见过)。它底层其实就是AIDL,只不过在AIDL的基础上做了一定的封装,使得我们用AIDL变得方便。AIDL的原理在上一节已经了解过了,这里不做详解。说一下怎么用。

Messenger有两个构造函数:Messenger(Binder binder)和Messenger(Handler handler)。

(1)服务端先写新建一个Service类,在Service类中定义一个Handler,用于创建Messenger对象。在onBinder返回创建好的Messenger实例的Binder即可。

(2)客户端绑定服务,在onServiceConnected方法内,用Binder参数新建Messenger对象,新建Message(不是Messenger哦)用于传输数据,使用Bundle作为保存数据的容器,调用Messenger.send(Message)方法传输数据到服务端。此时服务端的定义的Handler就会收到数据,在handleMessage方法内处理客户端发过来的数据。书中说明了一点我很在意的是,为什么不用object来传数据(对象)呢?吼吼,原来object属性不能用于跨进程传输。算了,还有Bundle可以用,虽然麻烦了点…

服务端可是可以反馈数据给客户端的。Message有一个属性叫replyTo,这个意思也很明显啦。我们在客户端也用定义一个Handler来创建一个Messenger对象(注意,此时有两个Messenger对象,一个是用Binder创建的,用户发送数据到服务端。另一个用Handler创建的,用户接收数据时调用)。然后将由Handler创建的Messenger赋值给Message的replyTo属性。在服务端收到replyTo这个对象后,也要像客户端一样,新建Message,添加数据,利用replyTo属性的对象传输数据给客户端。可能说的不是很清楚,看图:

Messenger工作原理

具体代码,请看书!!

AIDL
之前整一节都在说AIDL,连Messenger的底层也是AIDL,作者又在这里说AIDL,我倒觉得有点重复了。详细使用可以参考Android中的AIDL进程间通信这个栗子。因为常用,所以我把重要代码copy过来,以便查看。

CalculateInterface.aidl

对于aidl的方法变量,书中还说了aidl支持的6种数据类型:
(1)基本类型(int,float,double…)
(2)String和CharSequence
(3)List:只支持ArrayList,要求加进List的对象都实现Parcelable接口
(4)Map:只支持hashMap,要求加进List的对象都实现Parcelable接口
(5)Parcelable:所有实现Parcelable接口对象
(6)ADIL:所有ADIL接口也可以在ADIL中使用(接下来会有栗子)

package com.example.aidl.calculate;interface CalculateInterface {    double doCalculate(double a, double b);}

服务端

public class CalculateService extends Service {    // ...    @Override    public IBinder onBind(Intent arg0) {        // TODO Auto-generated method stub        return mBinder;    }    // ...    private final CalculateInterface.Stub mBinder = new CalculateInterface.Stub() {          @Override        public double doCalculate(double a, double b) throws RemoteException {            // TODO Auto-generated method stub            Log.e("Calculate", "远程计算中");            Calculate calculate = new Calculate();            double answer = calculate.calculateSum(a, b);            return answer;        }    };    // ...}

客户端

public class CalculateClient extends Activity {    // ...    private ServiceConnection mServiceConnection = new ServiceConnection() {        @Override        public void onServiceDisconnected(ComponentName name) {            // TODO Auto-generated method stub            mService = null;        }        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            // TODO Auto-generated method stub            mService = CalculateInterface.Stub.asInterface(service);        }    };    // ...    @Override    protected void onCreate(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        Intent intent = new Intent("com.example.calculate.CalculateService");        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);        etNum1 = (EditText) findViewById(R.id.et_num_one);        etNum2 = (EditText) findViewById(R.id.et_num_two);        tvResult = (TextView) findViewById(R.id.tv_result);        btnCalculate = (Button) findViewById(R.id.btn_cal);        btnCalculate.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                // TODO Auto-generated method stub                try {                    double num1 = Double.parseDouble(etNum1.getText().toString());                    double num2 = Double.parseDouble(etNum2.getText().toString());                    String answer = "计算结果:" + mService.doCalculate(num1, num2);                    tvResult.setTextColor(Color.BLUE);                    tvResult.setText(answer);                } catch (RemoteException e) {                }            }        });    }    // ...}

作者还说到一种场景,就是我们需要服务器发生某事件后能及时通知客户端。这种类似于观察者模式,只不过跨进程的。

实现的原理和我们写观察模式的程序一样。我们多定义一个AIDL文件,里面定义一个接口,如

INewBookListener.aidl

interface INewBookListener {    void onNewBook(in Book newBook);}

在服务端的aidl添加两个方法,如

IBookManager.adil

interface IBookManager {    // ...    void addBook(in Book book);    void addRegister(INewBookListener listener);    void removeRegister(INewBookListener listener);}

然后服务端写Service时,获取Binder的时候,实现这两个方法。

public class BookManagerService extends Service {    /**     * 因为进程传输的对象都是序列化的,所以add的对象和要remove的对象虽然数据一样,但是不是同一个对象,所以无法真正移除要remove的对象     **/    // private CopyOnWriteArrayList<INewBookListener> listenerList = new CopyOnWriteArrayList<INewBookListener>();    /**     * RemoteCallbackList虽然名为List,但实际上是一个Map,保存的键是连接的Binder。所以我们移除时可以准确移除要remoew的对象,而且做了同步处理     **/    private RemoteCallbackList<INewBookListener> listenerList  = new RemoteCallbackList<INewBookListener>();    // ...    private BookManager.Stub binder = new IBookManager.Stub() {        // ...        /**         * listenerList.beginBroadcast()和listenerList.finishBroadcast()必须成对出现,及时只是获取listenerList的size         **/        @Override        public void addBook(Book book) {            int count = listenerList.beginBroadcast();            for (int i = 0; i < count; i++) {                INewBookListener listener = listenerList.getBroadcastItem(i);                listener.onNewBook(book);            }            listenerList.finishBroadcast();        }        @Override        public void addRegister(INewBookListener listener) {            listenerList.register(listener);        }        @Override        public void removeRegister(INewBookListener listener) {            listenerList.unregister(listener);        }    }    // ...}

客户端绑定时调用,调用IBookManager的register方法把INewBookListener添加服务器的listenerList中。当客户端destroy时,调用IBookManager的unregister方法就可以服务端listenerList中移除。不知道你有没有注意到,我们传输的是一个AIDL接口,也证实了AIDL接口也可以作为AIDL的数据类型。客户端获取INewBookListener接口的Java对象如下:

private INewBookListener bookLisetner = new INewBookListener.Stub() {    @Override    public void onNewBook(Book newBook) {        // TODO    }}

ContentProvider

我们都知道ContentProvider作为四大组件之一,用于同享数据。在一个程序中定义类继承ContentProvider,然后再公开出来,让别的程序可以读写自己数据。但是你知道,其实他的底层也是Binder,相对与ADIL,使用起来很简单。因为本章作者关心的是IPC,所以没对ContentProvider的原理没怎么说,我看到后面章节有说,现在我也不care。

具体怎么使用?简单来说,自定义ContentProvider类,实现里面6个方法,然后在AndroidMenifest.xml文件公开自己访问地址。

具体6个方法是:onCreate、insert、update、query、delete、getType。getType用来返回的是URI请求的MIME类型。听说有点复杂,如果不太关心的话,直接返回null或者*/ *就行。

怎么对外公开自己呢?很简单,在AndroidMenifest.xml文件添加provider标签,必须指定android:authoritise属性,这个就是对外访问的路径,并且是唯一的。一般这个属性值加上包名作为前缀。还可以加上android:permission作为权限控制。其实权限控制还分readPremission和writePermission。

举个栗子:

<provider     android:name="com.johan.project.BookProvider"    android:authoritise="com.johan.project.provider"    android:permission="com.johan.project.PROVIDER"    />

客户端怎么访问呢?既然是四大组件,Activity当然体用了建议的访问方式。

public class MainActivity extends Activity {    @Override    public void onCreate(Bundle saveInstanceState) {        Uri uri = new Uri("content://com.johan.project.provider");        getContentResolver().query(uri, null, null, null, null);    }}

这样就可以访问到服务端的数据了。

Socket

Socket一般用于网络通信,当然也可以用于进程间通信。只是没想到而已。这里只是做一下记录。

作者还给出各种IPC方式的优缺点和使用场景,如图:

这里写图片描述

作者下一节说到的Binder池,我不打算做笔记,做到再说!!

0 0
原创粉丝点击