9.Android中的IPC方式——Bundle、文件共享、Messenger、AIDL、Content-Provider

    Android中的IPC方式有很多,比如可以通过Intent中附加extras(Bundle类型)来传递信息,或者通过共享文件的方式来共享数据,还可以采用前面的文章说的Binder方式来跨进程通信,另外ContentProvider天生就支持跨进程访问的(ContentProvider底层使用的就是Binder机制),所以我们也可以使用ContengProvider来进行IPC。另外,我们也可以通过网络通信的方式来实现IPC,所以,我们也可以使用Socket来实现IPC。下面将介绍几种IPC的方式。
    
     9.1 使用Bundle
    Android中的四大Component都支持在Intent中传递Bunlde数据。其中我们要知道,只有可序列化的类才能在进程间传递,Bundle正是实现了Parcelable接口,我们常用的方式比如,打开一个Service、Activity,或者是发送一个广播,这时候我们都可以在intent中setExtras来放入Bundle数据,使得进程之间可以互相通信。(广播其实是非常常用的一种IPC方式,它本质也是使用Bundle来实现)
    
       9 .2 使用文件共享
    两个进程通过读/写同一个文件来交接数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。Android是基于Linux的,所以并发读、写文件可以没有限制地进行,甚至多个线程对一个文件进行写操作都是允许的,所以这样也很容易造成脏数据等问题。所以说,文件共享的方式适合在数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写的问题。我们知道Sharepreferences也是以文件形式的存储数据,所以,我们也可以用Sharepreferences  来实现进程通信。
    
      9.3 使用Messenger
    Messenger意思是信差。因此,它其实就是用来传递Message数据的。在Messager中加入我们需要传递的数据,就可以轻松地实现数据的进程间的通信了。(底层还是使用Binder实现的),在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。
     /**
     * Create a Messenger from a raw IBinder, which had previously been
     * retrieved with {@link #getBinder}.
     * 
     * @param target The IBinder this Messenger should communicate with.
     */
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }


    从这个函数中,就可以知道,Messenger其实就是AIDL。

    Messenger的使用是比较简单的,它对AIDL做了封装,所以我们可以更简便地进行进程间的通信。并且,Messenger一次只处理一个请求,因此在服务端我们不需要考虑线程同步的问题(已经为我们处理好了,那么普通的AIDL有这个问题吗?)。实现一个Messenger有如下几个步骤,可以分为服务端和客户端。
    
    1.服务端进程    
    下面看下服务端的代码实现:
package com.example.messengerserverapp;

import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;

public class MyMessengerService extends Service {

    public static final String TAG  = "MyMessengerService";

    public static final int MSG_FROM_CLIENT = 1;
    public static final int MSG_FROM_SERVICE  = 2;

    Handler handler = new Handler()
    {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            case MSG_FROM_CLIENT:
                Log.i( TAG,msg.getData().getString( "msg") );

                Messenger messenger = msg.replyTo;
                Message message = Message.obtain(null, MSG_FROM_SERVICE);
                Bundle bundle = new Bundle();
                bundle.putString( "reply", "服务端已经接受到了您的请求,已经处理完毕");
                message.setData(bundle);
                try {
                    messenger.send(message);
                } catch (RemoteException e) {
                    // TODO: handle exception
                }
                break;

            default:
                break;
            }

        };
    };

    Messenger messenger = new Messenger(handler);


    @Override
    public IBinder onBind(Intent arg0) {
         return messenger.getBinder();
    }

}


从上面的代码中可以看到,在服务端,我们可以给Messenger指定一个Handler来处理客户端发送的数据。服务端在onBind方法中返回这个Messenger的Binder对象,然后,客户端就可以获取到这个Binder的对象,在前面的文章:http://blog.csdn.net/savelove911/article/details/51288577中,我们学过了Binder了,这里就不再赘述了。接下来我们开始看客户端的代码。

2.客户端进程
    下面是客户端的代码实现:
package com.example.messengerclientapp;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SearchViewCompatIcs.MySearchView;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

public class MainActivity extends Activity {

    public static final String TAG = "MessengerActivity";
    public static final int MSG_FROM_CLIENT = 1;
    public static final int MSG_FROM_SERVICE = 2;
    private Messenger mService;

    private Messenger mGetReplyMessenger = new Messenger( new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_FROM_SERVICE:
                Log.i( TAG,"receiver msg from services:" + msg.getData().getString( "reply"));
                break;

            default:
                break;
            }
        };
    });

    private ServiceConnection mConnection = new ServiceConnection(){

        @Override
        public void onServiceConnected(ComponentName arg0, IBinder arg1) {
            mService = new Messenger(arg1);
            Message msg = Message.obtain( null , MSG_FROM_CLIENT );
            Bundle data = new Bundle();
            data.putString( "msg", "这个消息来自于客户端");
            msg.setData(data);
            msg.replyto = mGetReplyMessenger;
            try {
                mService.send(msg);
            } catch (RemoteException e) {

             }
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent startService = new Intent( );
        startService.setAction( "com.zhenfei.messengertest");
        bindService(startService, mConnection, Context.BIND_AUTO_CREATE);
     
    }

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


接下来我们主要看下面这几行代码中:
   mService = new Messenger(arg1);
    在这行代码中,我们使用了服务端的Binder对象来创建Messenger对象,这个Messenger对象可以根据Binder对象发送Message给服务端。实现细节这边就不进行讨论了。然后,服务端就会接收到客户端的消息了,并且,在本例中,服务端可以在接收到消息以后返回数据给客户端,因为我们在客户端设置了replyTo,看到如下代码:
      msg.replyto = mGetReplyMessenger;
    在这边,我们把客户端的Messenger对象放在了Message当中传递给了服务端,所以服务端那边可以通过Message来获取客户端的Messenger,使得他们之间可以互相通信。
    
    本例只是Messenger的简单应用,在实际应用的时候,我们可以在Message中放入一些客户端进程的信息,然后,服务端那边可以用一个List或者是一个Map来维护客户端的发送过来的客户端信息和Message的replyTo属性,这样服务端就能和客户端进行通信了。
    那么,客户端不发送信息,服务端能主动给客户端发送信息吗?答案是,NO。不行.简单地理解,你不写信给别人,别人如何给你回信呢?所以,使用Messenger在连接服务端后,只能客户端先发送Message给服务端。

 9.4 使用AIDL进行进程通信。
    9.4.1 在AIDL中设置回调
    
在前面的文章中:http://blog.csdn.net/savelove911/article/details/51288577 我们已经简单地使用了AIDL,接下来,我们进一步丰富AIDL的使用。其中包括了服务端主动回调客户端的接口,实现客户端监听服务端,比如在前面的例子中,当服务端添加了一本书以后,回调给客户端,通知客户端。下面我们就开始继续完善上面的代码,代码从上一篇文章开始修改。
    首先,在这边先实现客户端添加书到服务端上,代码如下:
    
public void onClick( View view )
    {
        switch (view.getId()) {
        case R.id.addNewBook:
            if( iBookManager != null )
            {
                try {
                    iBookManager.addBook( new Book( "客户端的第"+cilentCount+"本书" ));
                } catch (RemoteException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            break;

        default:
            break;
        }
    }      


    其实就是调用IBookManager的addBook方法,接下来我们重点放在符合让服务端回调客户端。在AIDL中,我们是无法使用普通的接口的,只能使用AIDL接口(进程不同,所以无法互相访问对方的内存,所以找不到对方的接口对象,自然无法使用普通的接口)
    那我们这边先做一个aidl接口,如下:
 
   package com.zhenfei.aidl;
    import com.zhenfei.aidl.Book;

    interface AddNewBookListener{
    void onAddNewBook( in Book book );
    }

    在这边,我们继续来讨论这个方法的声明,在上一篇文章,我们说过这样的一段话:

    如果client不需要传输数据给server,client只需要处理经过server处理过后的数据,

    那么 client 和 server 都为 out 

    如果client只需要传输数据给server,而不需要处理返回的数据,

    那么client和server都为 in


    那么,为什么上面我们这个方法声明是in,这个方法应该是Service调用,然后客户端App接收才对吧?
    那这边应该是out才对吧?
    错,这边必须是in。那上面的内容错了吗?也不是,我们这边先要搞清楚,什么样才叫服务端,什么样又叫客户端。
    在AIDL中,服务端并非固定是Service,并没有这样定义过,对于AIDL来说,服务端是指:
    创建了Binder对象那一方
    客户端是指:
    从Binder驱动获取Binder对象的那一方。

    所以,因为这个AddNewBookListener的Binder对象是在“客户端”创建的,所以,调用回调方法的时候,客户端充当了服务端的角色,因此,在此处,我们需要使用in 作为参数的方向。

    接下来,我们需要继续完善IBookManager代码:
    
package com.zhenfei.aidl;

import com.zhenfei.aidl.Book;
import com.zhenfei.aidl.AddNewBookListener;
interface IBookManager{
    List<Book> getBookList();
    void addBook( in Book book);
    void registerListener( AddNewBookListener listener);
    void unregisterListener( AddNewBookListener listener);
}

这边看到,我们在AddNewBookListener 上,并不需要使用方向,因为他们并不是数据类型,而是接口类型的参数。
然后,我们先完善客户端的代码,也就是在客户端上,加上添加监听器和注销监听器的代码:
 如下:
private Stub stub = new Stub() {

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

        @Override
        public void addBook(Book book) throws RemoteException {
            books.add(book);
            for( AddNewBookListener addNewBookListener : list )
            {
                addNewBookListener.onAddNewBook(book);
            }
        }

        @Override
        public void registerListener(AddNewBookListener listener)
                throws RemoteException {
            Log.i(TAG, "添加监听器成功");

            list.add(listener);
        }

        @Override
        public boolean unregisterListener(AddNewBookListener listener)
                throws RemoteException {
             for( AddNewBookListener addNewBookListener : list )
             {
                 if( addNewBookListener == listener )
                 {
                      list.remove(listener);
                      return true;
                 }

             }
            Log.i(TAG, "移除监听器失败,没有找到对应的Listener对象");

             return false;
        }
    };

然后是客户端的监听器的代码:
private class MyAddNewBookListener extends AddNewBookListener.Stub
   	 {
      		 int pos;
     	  	 private  MyAddNewBookListener( int pos )
    		    {
   	         this.pos = pos;
  	      }

        @Override
        public void onAddNewBook(Book book) throws RemoteException {
            Log.i(TAG, "第"+pos+"个监听器接收到回调,添加了一本新书:"+book.bookName);
            List<Book> books;
            try {
                books = iBookManager.getBookList();
                String formatStr = getResources().getString(R.string.tv_book_info);
                formatStr = String.format(formatStr,books.size() + "");

                placeholderFragment.tvBookInfo.setText( formatStr);

            } catch (RemoteException e) {
                 e.printStackTrace();
            }
        }

    }

下面做添加监听器 并且注销监听器的操作,打印的日志如下:

 

我们会发现,在服务端是找不到这个Listener的,因为,客户端传过来的Listener,每次从服务端接口中获取,都是一个全新的,尽管看似都是一个对象,但是很显然他们的地址是不一样的。所以,直接匹配监听器对象来卸载是不现实的,那我们应该怎么做才能成功地注销监听器呢?
   
     一种方法是使用RemoteCallbackList,这个是系统专门提供的用于删除跨进程listener的接口,RemoteCallbackList是一个泛型,支持管理任意的AIDL接口。
    它的声明如下:
   
 public class RemoteCallbackList<E extends IInterface> 

    好了,在这边问一下,什么样的接口才能叫AIDL接口?我想,应该是实现了IInterface的接口。
    RemoteCallbackList 的工作原理很简单,在它的内部中有一个Map结构专门用来保存所有的AIDL接口,这个Map的key是IBinder类型,value是Callback类型,所以,其中key和value分别通过下面的方式来获取数得
 
   IBinder key = listener.asBinder();
    Callback value = new Callback( listener , cookie);
    

    下面,我们来使用RemoteCallbackList,首先把监听器列表类型改为RemoteCallbackList
    
private RemoteCallbackList<AddNewBookListener> mListenerList = new RemoteCallbackList<AddNewBookListener>();
    

    然后修改resgisterListener 和unregisterListener 这两个接口是实现,如下:
    
    
public void registerListener( AddNewBookListener listener )
    {
        mListenerList.register( listener );
    }

    public void unregisterListener( AddNewBookListener listener)
    {
       mListenerList.unregister( listener );
    }

    
    最后要修改addBook方法,有新书的时候就用纸所有已经注册的listener
    
 public void addBook( Book book ) throws RemoteException{
       books.add( book );
        final int N = mListenerList.beginBroadcast();
        for( int i = 0 ; i < N ; i ++ )    
        {
            AddNewBookListener listener = mListenerList.getBroadcastItem( i );
            if( listener != null )
            {
                try{
                        listener.onAddNewBook( book );
                }catch( RemoteException exception)
                {
                    exception.printStackTrace();
                }
            }
        }
       mListener.finishBroadcast();
    }


   这边需要注意,RemoteCallBackList它其实不是一个List所以,我们不能够像操作普通的List一样去操作它,所以我们只能够通过它的getBroadcastItem方法来获取其中的元素,而不能直接使用for进行遍历,而且beginBroadcast和finishBroadcast这两个方法,必须成对地使用。
    那上面就是系统提供给我们的监听器方法了,那还有别的方法可以做到注销监听器吗?其实是有的,比如我们给AddNewBookListener这个接口中,添加一个getId方法,然后把每个listener对象和一个id绑定,就可以实现Listener和id绑定。只会,我们就可以根据id匹配listener,这样就可以正常地注销listener对象了。
    在这边,我们考虑这样一种情况,客户端想要获取服务端有多少本书,服务端查询的时候,加入需要花费一些时间,那么,会发生什么样的情况呢?我们在服务端的getBookList方法改成如下:
    
<span style="white-space:pre">	</span>@Override
        public List<Book> getBookList() throws RemoteException {
            SystemClock.sleep(3000);
             return books;
        }

    然后,我们点击按钮添加书本,并且获取几次获取图书列表,就会发现出现ANR了。这是因为,客户端执行AIDL接口方法的时候,会挂起当前的线程,然后去调用服务端的Binder线程,所以,如果在服务端的接口方法中做了耗时操作,就会造成无响应,ANR。
    所以我们应该尽量避免在服务端的接口方法上做耗时操作,如果这个接口方法是耗时的,我们就应该是用子线程去调用。
    同理,对于listener方法也是,服务端应该避免在主线程调用客户端的耗时方法。可以的话,可以在子线程中调用。
    
    9.4.2 处理Binder意外死亡事件
    
    为了程序的健壮性,我们接下来还要继续完善这个AIDL代码。比如当服务端进程挂掉了,那么就会导致Binder死亡,这时候,我们需要做一些相应的操作,比如重新连接远程服务端、或者是在客户端提醒远程服务可不用。
    一般来说,有两种方法来处理,第一种是设置Binder的死亡代理。
    
    这就需要使用到Binder很重要的两个方法linkToDeath和unlinkToDeath,在linkToDeath方法中,我们可以为Binder设置一个死亡代理,当Binder死亡的时候,我们就能够收到通知,这样我们就可以重新连接请求从而恢复连接。
    第一种是为Binder设置一个死亡代理,首先我们需要声明一个DeathRecipient对象,DeathRecipient是一个接口,这个接口只有一个方法就是binderDied,很明显在,在Binder死亡的时候,系统会回调这个方法。然后,我们就可以移除之前绑定的binder代理并且,重新绑定远程服务。如下:
 
   

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {

        @Override
        public void binderDied() {
            if( iBookManager == null )
                return ;
            iBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0 );
            iBookManager = null;
        }
    };

     其次,在客户端绑定了远程服务成功以后,给binder设置死亡代理:
    
<span style="white-space:pre">	</span>@Override
        public void onServiceConnected(ComponentName service, IBinder binder) {
             Log.i(TAG, "连接远程服务成功:"+service.getClassName());

            iBookManager = IBookManager.Stub.asInterface(binder);
            List<Book> books;
            try {
                //远程调用获取书本数量的方法
                books = iBookManager.getBookList();

                String formatStr = getResources().getString(R.string.tv_book_info);
                formatStr = String.format(formatStr,books.size() + "");

                placeholderFragment.tvBookInfo.setText( formatStr);
                //绑定死亡代理
                binder.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                 e.printStackTrace();
            }
        } 

    其中第二参数是个标记位,一般设置为0就可以了。这样子,我们就成功地为客户端绑定了远程的Binder的死亡代理,这样Binder死亡的时候我们就可以接收到通知了,另外,通过Binder的方法,isBinderAlive也可以判断Binder是否死亡。
    
    第二种方法,是在客户端的onServiceDisconnected中重连远程服务,这两种方法,我们可以随便选择一种来使用,它们的区别在于,onServiceDisconnected在客户端的UI线程中被回调,而binderDied在客户端的Binder线程池中被回调,也就是说,在binderDied方法中,我们是不能访问UI的,这就是它们的区别。
    
     9.4.3 设置AIDL服务端的权限
    默认情况下,我们的远程服务,是允许任何人都可以连接的,但是这样会带来一些风险,因此,我们必须在服务上加入权限验证的功能,验证的方法很多。下面介绍两种常用的方法:
    第一种,可以使用权限来验证。
    首先,在服务端的AndroidManifest.xml中加入权限定义:
 <!-- 加入自定义的权限 -->
    <permission android:name="com.zhenfei.aidl.ACCESS_SERVER_SERVICE" android:protectionLevel="normal"></permission>
    然后,在Service中声明所需要的权限
 
<span style="white-space:pre">	</span><service 
            android:name="com.zhenfei.aidl.ServerService"
            android:permission="com.zhenfei.aidl.ACCESS_SERVER_SERVICE"
            >
            <intent-filter 

                >
                <action android:name="com.zhenfei.myaidl"/>

            </intent-filter>

        </service>

    之后,在客户端就可以使用这个权限来绑定服务,在客户端Androidmanifest.xml中加入如下权限:
        <uses-permission android:name="com.zhenfei.aidl.ACCESS_SERVER_SERVICE"/>

    这样就可以了。
    第二种,可以在服务端的onTransact方法中进行权限验证,如果验证失败,返回false,这样服务端就不会执行AIDL中的方法,验证的方法可以使用getCallingUid和getCallingPid来获取客户端所属应用的Uid和Pid,然后在通过这两个参数做一些验证工作,比如验证包名什么的。
    如下:
    public boolean onTransact( int code, Parcel data,Parcel reply , int flags) throws RemoteException
    {
        int check = checkCallingOrSelfPermission( "com.zhenfei.aild.ACCESS_SERVR_SERVICE");
        if( check == PackageManager.PERMISSION_DENIED)
        {
            return false;
        }

        String packageName = null;
        String[] packaghes = getPackageManager().getPackagesForUid( getCallingUid());
        if( package != null && packages.length>0)
            packageName = packages[0];
        
        if( !packageName.startWith( "com.zhenfei")){
            return false;
        }
        return super.onTransact( code, data , reply ,flags);
    }


    上面的代码,服务端在new一个Stub对象的时候,覆盖重写即可。

    好了,关于AIDL的介绍就到这为止,IPC方式还有一种很常用的,那就是四大组件之一:ContentProvider。
    

9.5 Content Provider简介
    ContentProvider是Android提供的专门用于不同应用之间进行数据共享的方式,因此,ContentProvider天生就支持进程之间通信。和Messenger一样,ContentProvider的底层也是用Binder来实现的,只是由于系统为我们封装了许多,因此,使用起来,比AIDL简单多了。在这边就先不重点介绍了。
    
9.6 使用Socket进行IPC
    Socket也就是我们常说的套接字,我们经常使用Socket来实现网络编程,分为流式套接字和用户数据报套接字。分别对应网络传输协议的TCP协议和UDP协议。TCP协议是面对连接的协议,而UDP协议是面对无连接的协议。Socket编程是属于Java基础范畴的,这里就不进行详细的描述,大致就是服务端打开个端口,让客户端访问。
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值