Android中实现跨进程通信(IPC)的方式(三)之观察者模式

前言

    在Android中实现跨进程通信(IPC)的几种方式(一)中介绍了什么是多进程,为什么需要多进程,多进程的优缺点等。这篇我们将会使用AIDL来实现跨进程通信
在Android中实现跨进程通信(IPC)的几种方式(二)中讲解了怎么用AIDL实现跨进程通信。如果还不了解什么AIDL,那么可以看一下这篇文章。

背景

     现在有一个需求是在另外一个进程中进行数据处理,我们需要获取它的处理结果。所以这个时候我们需要另一个进程在处理完数据过后主动告诉我们。说到这里很多开发者肯定就马上想到了观察者模式。想到了接口回调。下面我们就用接口回调来实现当前需求。

实现接口

回调接口 IBookListener实现

// IBookListener.aidlpackage com.example.huangjie.aidl3;// Declare any non-default types here with import statementsimport com.example.huangjie.aidl3.Book;interface IBookListener {   void onBookChange(in Book book);}
image

实现接口的注册与取消注册

// IBookManager.aidlpackage com.example.huangjie.aidl3;// Declare any non-default types here with import statementsimport com.example.huangjie.aidl3.Book;import com.example.huangjie.aidl3.IBookListener;interface IBookManager {   void addBook(in Book book);   List  getBookList();    void registerListener(IBookListener listener);    void unregisterListener(IBookListener listener);}
这里写图片描述

服务端实现

package com.example.huangjie.aidl3;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;import android.support.annotation.Nullable;import android.util.Log;import java.util.ArrayList;import java.util.List;/** * Created by huangjie on 2018/6/3. */public class BookService extends Service {    private ArrayList mListenerList;    private static final String TAG = "BookService";    private ArrayList mBookList;    private boolean destory;    @Override    public void onCreate() {        super.onCreate();        mListenerList = new ArrayList<>();        mBookList = new ArrayList<>();        new Thread() {            @Override            public void run() {                super.run();                while (!destory) {                    try {                        Thread.sleep(6000);                        String bookId = "id:" + mBookList.size() + 1;                        String bookName = "书名" + bookId;                        Book book = new Book(bookId, bookName);                        mBookList.add(book);                        onNewBookArrived(book);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }.start();    }    /**     * 当新书到达     * @param book     */    public void onNewBookArrived(Book book) {        for (IBookListener listener : mListenerList) {            try {                listener.onBookChange(book);            } catch (RemoteException e) {                e.printStackTrace();            }        }    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return mBookBinder;    }    private IBinder mBookBinder = new IBookManager.Stub() {        @Override        public void addBook(Book book) throws RemoteException {            mBookList.add(book);            onNewBookArrived(book);        }        @Override        public List getBookList() throws RemoteException {            return mBookList;        }        @Override        public void registerListener(IBookListener listener) throws RemoteException {            if (!mListenerList.contains(listener)) {                mListenerList.add(listener);            }            Log.e(TAG, "registerListener success" + mListenerList.size());        }        @Override        public void unregisterListener(IBookListener listener) throws RemoteException {            if (mListenerList.contains(listener)) {                mListenerList.remove(listener);                Log.e(TAG, "unregisterListener success" + mListenerList.size());            }        }    };    @Override    public void onDestroy() {        super.onDestroy();        destory = true;    }}

客户端实现

package com.example.huangjie.aidl3;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.os.RemoteException;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.Toast;import java.lang.ref.WeakReference;public class MainActivity extends AppCompatActivity {    public static final String TAG = "MainActivity";    private ServiceConnection connection;    private IBookManager mBookManager;    private IBookListener.Stub mListener;    private MyHander mHandler = new MyHander(this);    public static class MyHander extends Handler {        private WeakReference reference;        public MyHander(Context context) {            reference = new WeakReference<>(context);        }        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            if (reference.get() != null) {                Toast.makeText(reference.get(), msg.obj.toString(), Toast.LENGTH_SHORT).show();            }        }    }    ;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();        Intent intent = new Intent(this, BookService.class);        bindService(intent, connection, Context.BIND_AUTO_CREATE);    }    private void init() {        connection = new ServiceConnection() {            @Override            public void onServiceConnected(ComponentName name, IBinder service) {                mBookManager = IBookManager.Stub.asInterface(service);                try {                    mBookManager.registerListener(mListener);                } catch (RemoteException e) {                    e.printStackTrace();                }            }            @Override            public void onServiceDisconnected(ComponentName name) {            }        };        mListener = new IBookListener.Stub() {            @Override            public void onBookChange(Book book) throws RemoteException {                mHandler.obtainMessage(0x12, book).sendToTarget();            }        };    }    @Override    protected void onDestroy() {        super.onDestroy();        if (mBookManager != null && mBookManager.asBinder().isBinderAlive()) {            try {                mBookManager.unregisterListener(mListener);            } catch (RemoteException e) {                e.printStackTrace();            }        }        unbindService(connection);    }}

    通过以上我们已经实现了跨进程通信中的观察者模式,但是我们是否发现了一个问题,那就是客户端解绑监听器的时候发现居然解绑失败。也就是说服务端找不到我们注册的监听器。但是客户端传递的明明是同一个监听器对象啊。其实出现这种情况也是意料之中的。因为这种回调模式只适用于我们在同一个进程中进行回调,而在跨进程中这种模式是不会奏效的。因为Binder会把客户端传递的对象是从新转转换,并且生成一个新的对象,虽然我们在注册和接触注册的时候传递的对象是同一个客户端对象。但是通过Binder传递到服务端后,却会产生两个全新的对象。因为对象是不能直接进行跨进程中传输。对象的跨进程传输本质上都是序列化与反序列化的过程。这就是为什么AIDL中自定义对象都必须实现Parcelable接口的原因。说了那么多,到底怎么才能实现跨进程通信的观察者模式呢?

什么是RemoteCallbackList

    简单一句话概括就是RemoteCallbackList是系统提供的用于删除跨进程listener的接口。RemoteCallbackList是一个范型,支持管理任意的AIDL接口。

image
具体可以看 Android官方文档
修改过后的服务端代码

package com.example.huangjie.aidl3;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteCallbackList;import android.os.RemoteException;import android.support.annotation.Nullable;import android.util.Log;import java.util.ArrayList;import java.util.List;/** * Created by huangjie on 2018/6/3. */public class BookService extends Service {    private RemoteCallbackList mListenerList;    private static final String TAG = "BookService";    private ArrayList mBookList;    private boolean destory;    @Override    public void onCreate() {        super.onCreate();        mListenerList = new RemoteCallbackList<>();        mBookList = new ArrayList<>();        new Thread() {            @Override            public void run() {                super.run();                while (!destory) {                    try {                        Thread.sleep(6000);                        String bookId = "id:" + mBookList.size() + 1;                        String bookName = "书名" + bookId;                        Book book = new Book(bookId, bookName);                        mBookList.add(book);                        onNewBookArrived(book);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }.start();    }    /**     * 当新书到达     *     * @param book     */    public void onNewBookArrived(Book book) {        final int count = mListenerList.beginBroadcast();        for (int i = 0; i < count; i++) {            IBookListener broadcastItem = mListenerList.getBroadcastItem(i);            if (broadcastItem != null) {                try {                    broadcastItem.onBookChange(book);                } catch (RemoteException e) {                    e.printStackTrace();                }            }        }        mListenerList.finishBroadcast();    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return mBookBinder;    }    private IBinder mBookBinder = new IBookManager.Stub() {        @Override        public void addBook(Book book) throws RemoteException {            mBookList.add(book);            onNewBookArrived(book);        }        @Override        public List getBookList() throws RemoteException {            return mBookList;        }        @Override        public void registerListener(IBookListener listener) throws RemoteException {            mListenerList.register(listener);        }        @Override        public void unregisterListener(IBookListener listener) throws RemoteException {            mListenerList.unregister(listener);        }    };    @Override    public void onDestroy() {        super.onDestroy();        destory = true;    }}

注意事项

    客户端在调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时的话,就会导致客户端线程长时间阻塞在这里,如果这个时候客户端调用服务端方法的方法所在线程是UI线程的话,就会导致客户端ANR,这当然是不行的,如果我们知道服务端方法是个耗时的方法,这个时候我们要避免在客户端UI线程中去访问远程方法。由于onServiceConnected和onServiceDisconnected方法都是运行在UI线程中,所以不可以在它们里面直接调用服务端耗时方法。特别需要注意的是:由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量的耗时操作。这个时候不要在服务端中开线程去进行异步任务。

源码下载

更多相关文章

  1. haproxy根据客户端浏览器进行跳转
  2. 【安卓笔记】android客户端与服务端交互的三种方式
  3. [置顶] Android(安卓)跨进程通信Aidl的使用及注意事项
  4. android手机客户端上传文件,java servlet服务器端接收并保存到服
  5. Android例子—直接通过Binder的onTransact完成跨进程通信
  6. android web services6
  7. android客户端加密代码
  8. android 利用socket 发送Json数据demo
  9. Android控制台中运行Java程序

随机推荐

  1. vue api实例方法 有四个 $watch $emit $f
  2. 0512 作业
  3. PHP:MySQL常用DDL数据定义语言, DML数据
  4. 210429 PHP 回调函数 递归函数 数组函数
  5. 触屏事件-上下左右滑动
  6. Kubernetes结合Docker的优势
  7. 一个 当前点击元素放大的效果
  8. Kubernetes Containerd集成进入GA阶段
  9. git基本使用命令
  10. git 的入门使用到团队协作