天天看点

《Android开发艺术探索》之IPC机制下(三)

(四)Android中的IPC方式

(3)使用Messenger

  4.3.1什么是Messenger

       Messenger翻译为信使,Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。从其构造方法可以看出痕迹,Messenger对AIDL做了封装,它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。

public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
    public Messenger(IBinder target){
        mTarget = IMessenger.Stud.asInterface(target);
  }
           

4.3.2 实现一个Messenger的几个步骤:

        步骤一:服务端进程:(1)创建一个Service来处理客户端的连接请求,(2)创建一个Handler并通过它来创建一个Messenger对象,(3)Service的onBind中返回这个Messenger对象底层的Binder。

       步骤二:客户端进程:(1)绑定服务端的Service;(2)成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为 Messager对象。(3)创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这一参数可以进行回应。

4.3.3 服务端/客户端代码:

       //服务端代码:MessengerHandler用来处理客户端发送的消息(去除客户端发来的文本信息),Messenger 作用是将客户端发送的消息传递给MessengerHandler,并在onBind方法中返回他的IBind对象。记得在XML中注册Service。

public class MessengerService extends Service {
    public static final String TAG = "MessengerService";
    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 100:
                    Log.i(TAG,"数据:" + msg.getData());
                    break;
                default:

                super.handleMessage(msg);
            }
        }
    }
    private final Messenger mMessenger = new Messenger(new MessengerHandler());
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}
           

       //客户端代码:(1)绑定远程进程的MessengerService;(2)绑定成功后,根据服务端返回的binder对象创建Messager对象并使用此对象向服务器利用Data发送消息。

public class MessengerActivity extends AppCompatActivity {
    public static final String TAG = "MessengerActivity";
    private Messenger mService;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService =  new Messenger(service);//利用IBinder创建一个Messenger
            Message msg = Message.obtain(null,10);//构造发消息的类型为Message 
            Bundle data = new Bundle();//构建Bundle模式的data数据
            data.putString("msg","hell this is client");
            //很关键的一句,通过Message的replyTo参数传递给服务器。
msg.replyTo  = mGetReplyMessenger;
            msg.setData(data);//将Data数据放到Message 载体中
            try {
                mService.send(msg);//Messager发送消息
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        Intent intent = new Intent(this,MessengerService.class);
        bindService(intent,mConnection, Context.BIND_AUTO_CREATE);//绑定服务
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);//断开服务
    }
}
           

       注意:通过 Messenger来传递Message,Message中能使用的载体就只有what、arg1、arg2、Bundle以及replyTo字段,我们还有Bundle,Bundle中可以支持大量的数据类型。将Bundle对象放在Message中。

4.3.4 服务端的回复功能

      //服务端修改:只需要修改MessengerHandler,当收到消息后,会立即回复一条消息给客户端。

private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 100:
                    Log.i(TAG,"数据:" + msg.getData());
                    Messenger messenger = msg.replyTo;
                    Message reply = Message.obtain(null,200);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply","收到你的消息,等下回复");
                    try {
                        messenger.send(reply);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
           

        //客户端修改:为了接受服务端的回复,客户端也需要准备一个接收消息的Messenger和handler。

private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 200:
                    Log.i(TAG,"Service:" + msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
           

         //很关键的一句,通过Message的replyTo参数传递给服务器。

msg.replyTo  = mGetReplyMessenger;
           

4.3.5 Messenger工作原理

《Android开发艺术探索》之IPC机制下(三)

       创建一个Handler并通过它来创建一个Messenger对象,Service的onBind中返回这个Messenger对象底层的Binder。

       用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为 Messager对象。创建一个Handdler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端。

        同一个应用的不同组件,如果它们运行在不同进程中,那么和它们分别属于两个应用没有本质区别。

(4)使用AIDL

4.4.1 Messenger和AIDL的区别

        Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,凉凉。Messenger的是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法。AIDL是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装。

4.4.2 AIDL进行进程间通信步骤

       服务端:创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

       客户端:绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

4.4.3 AIDL接口的创建

      AIDL接口的创建,创建了一个后缀为AIDL.的文件,在里面声明了一个接口和两个接口方法。如下:

//IBookManager.aidl
package com.ryg.chapter_2.aidl;
//显示自定义的Parcelable对象
import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
     List<Book> getBookList();
     void addBook(in Book book);
}
           

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

        显式导入自定义的Parcelable对象和AIDL,无论是不是在同一个包里;如果AIDL文件中用到了自定义的Parcelable对象,那么必须新增一个和他同名的AIDL文件,并在其中声明它为Parcelable 类型。在上面的IBookManager.AIDL这个类中我们用到Book,所以,我们必须要创建Book.aidl。

//Book.aidl
package com.ryg.chapter_2.aidl;
parcelable Book;
           

       AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。有开销,所以不能一概使用,此外: AIDL只支持方法,不支持声明静态变量。建议把所有和AIDL相关的类和文件全部放入同一个包中,方便复制、序列化与反序列化。

4.4.4 代码实现

      //1.远程服务端Service实现AIDL的接口,记得XML中注册服务

//1.远程服务端Service实现AIDL的接口,记得XML中注册服务。
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";
    //CopyOnWriteArrayList/ConcurrentHashMap支持并发读/写,完成线程同步
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    //3.Stub并且实现了它内部的AIDL方法,两个AIDL方法的实现
    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);
        }
    };
    //1.在onCreate中初始化添加了两本图书的信息
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1,"Android"));
        mBookList.add(new Book(1,"IOS"));
    }
   //2.创建了一个Binder对象并在onBind中返回它
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}
           

       //2.客户端的实现,先绑定远程服务,绑定成功之后返回的Binder对象转换成AIDL接口,让后可以通过这个接口去调用服务端的远程方法。功能:添加书籍、查询书籍。 

//2.客户端的实现,先绑定远程服务,绑定成功之后返回的Binder对象转换成AIDL接口,让后可以通过这个接口去调用服务端的远程方法。功能:添加书籍、查询书籍。
public class BookManagerActivity extends AppCompatActivity {
    private static final String TAG = "BookManagerActivity";
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //2.将Binder对象转换成AIDL接口
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
               //3.bookManager去掉用getBookList方法,然后打印出所获取的图书信息,添加书,查询所有列表。
                List<Book> list = bookManager.getBookList();
                Book newBook = new Book(3, "降龙十八掌");
                bookManager.addBook(newBook);
                Log.i(TAG, "add Book: " + newBook);
                List<Book> newList = bookManager.getBookList();
                Log.i(TAG, "query list : " + newList.toString());
                getCanonicalName();
                Log.i(TAG, "list string : " + list.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    private void getCanonicalName() {
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        //1.绑定远程服务
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
//4.销毁服务,断开连接
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}
           

      //3.场景:添加新书,当有新书时能不能把书的信息告诉我呢?观察者模式。当新书到的时候,图书馆就通知每一个对这本书感兴趣的用户。

解决方案:

      创建一个IOnNewBookArrivedListener.aidl文件,当服务端有新书到来时,就会通知每一个己经申请提醒功能的用户。从程序上来说就是调用所有IOnNewBookArivedListener对象中的onNewBookArived方法,并把新书的对象通过参数传递给客户端

       还需要在原有的接口IBookManager.aidl中添加两个新的方法: void registerListener

(IOnNewBookArrivedListener listener);和 void unregisterListener(IOnNewBookArrivedListener listener);

       服务端也是要稍微的修改一下,每隔5s向感兴趣的用户提醒: 

private class ServiceWorker implements Runnable{
        @Override
        public void run() {
            while (!mIsServiceDestoryed.get()){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookList.size()+1;
                Book newBook = new Book(bookId,"new book#" + bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
           

       我们还需要修改一下客户端的代码,主要是两方面,首先是客户端要注册IOnNewBookArrivedListener到远程的服务器,这样当有新书时服务端才能通知客户端,同时在我们的Activity的onDestory方法里面去取消绑定,另一方面,当有新书的时候,服务端会回调客户端的Binder线程池中执行,因此,为了便于进行UI操作,我们需要有一个Hnadler可以将其切换。

       注意:1.RemoteCallbackList实现解注册的功能。它的工作原理很简单,在它的内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是Callback类型,Callback中封装了真正的远程listener。虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象底层的Binder对象是同一个,利用这些特性,就可以实现我们无法实现的功能了,当客户端解注册的时候,我们只要遍历服务端所有的listener,找到那个和解注册listener具有相同Binder对象的服务端listener并把它删掉,内部自动实现了线程同步的功能,所以我们使用他来注册和解注册,不需要做额外的线程工作。2.客户端的onServiceConnected和 onServiceDisconnected方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法。新建线程处理耗时过程。3.Binder可能会意外死亡,若意外停止,则重连服务。方法一:给Binder设置DeathRecipient监听,Binder死亡,收到Binderdied回调,在binderDied中我们可以重连远程服务。方法二:onServiceDisconnected中进行重连操作。

(5)使用ContentProvider

       ContentProvider是Android中提供的专门用来不同应用之间数据共享的方式。适合进程间通信。和Messenger一样,             ContentProvider的底层实现同样也是Binder。

       ContentProvider的用法主要参考我的Github链接:Chap2的MyApplication项目和TestMyAppliaction项目,当然也可以使用android:process模拟同一应用的不同线程。主要讲了如何增删查改SQLite数据库,如何使用CV读取系统联系人,自定义内容提供器实现跨程序数据共享。玉刚大神的代码实在是跑不通啊,而且好像是用Eclipse写的。参考《第一行代码》的P211-P228,P254-P265。 getContentResolver()实现。

(6)使用Socket

 4.6.1什么是套接字? 

       分为流式套接字和用户数据报套接字两种,分别是应用于网络的传输控制层中的TCP(面向的连接协议,建立需要经过“三次握手”,稳定的数据传输功能,其本身提供了超时重传机制)和UDP协议(UDP具有更好的效率,其缺点是不保证数据一定能够正确传输),套接字=IP地址+端口号。Socket本身支持传输任意字节流。

4.6.2 Socket使用注意事项

       声明权限android.permission.INTERNET和android.permission.ACCESS_NETWORK_STATE;不能在主线程中访问网络,因为网络操作很可能是耗时的。

4.6.3代码设计

      TestSocket项目。

      //1.服务端设计:(1)Service启动时,线程中建立TCP服务(监听8688端口),(2)当有客户端连接时,就会生成一个新的Socket,(3)通过每次新创建的Socket就可以分别和不同的客户端通信了。随即回复一句话,(4)客户端断开连接时,服务端这边也会相应的关闭对应Socket并结束通话线程(通过判断服务端输入流的返回值来确定)。

     //2.客户端设计:(1)客户端Activity启动时,会在onCreate中开启一个线程去连接服务端的socket;(2)采用了超时重连的机制,每次连接失败后都会重新连接(3)你的activity在退出的时候,就要关闭socket了。

(五)Binder连接池

       如何使用AIDL?首先创建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。

       场景:公司的项目越来越庞大了,现在有100个不同的业务模块都需要使用AIDL来进行进程间通信。

       解决方案:采用Binder连接池BinderPool,主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。客户端:每个业务模块创建自己的AIDL接口并实现此接口,不同业务模块之间不能有耦合;向服务端提供自己的唯一标识和其对应的Binder对象;服务端:只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用。

《Android开发艺术探索》之IPC机制下(三)

(六)选择合适的IPC方式

《Android开发艺术探索》之IPC机制下(三)

继续阅读