Android

Android IPC机制

2022-03-12  本文已影响0人  漆先生

一、IPC简介

IPC是指两个进程之间进行数据交互的过程,即:跨进程通信。
进程是一个执行单,在移动设备上指一个程序或者一个应用。一个进程可以有多个线程,也可以只有一个线程,即主线程。在Android里边,主线程也叫作UI线程,要是在主线程执行大量耗时任务,就会造成界面无法响应,ANR问题,解决这类问题,把耗时操作放在子线程就好。
在Android中,最有特色的进程间通信就是Binder,Binder轻松的实现了进程间的通信。

二、Android中的多进程模式

1.开启多进程模式

给四大组件Activity、Service、Receiver、ContentProvider在AndroidMenifeist中指定android:process属性,可以指定其运行的进程。
:开头的线程是当前应用的私有进程,其它应用不可以和它跑在同一个进程中,而不以:开头的属于全局进程,其他应用通过ShareUID方式可以和它跑在一个进程中。

2.多进程模式的运行机制

Android为了每一个应用(进程)都分配了独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间。
多进程会造成如下几个反面方面的问题:

为了解决这些问题,系统提供了跨进程通信方法,虽然不能直接共享内存,但是可以实现数据共享。Intent来传递数据,共享文件,基于Binder的Messenger,ContentProvider,AIDL和Socket。

三、IPC技术概念介绍

当我们需要通过Intent和Binder传输数据,或者我们需要把对象持久化到存储设备上,再或者通过网络传输给其它客户端时,Serializable和Parcelable接口可以完成对象的序列化过程。

1.Serialzable接口

Serialzable是java提供的序列化接口,是一个空接口,为对象同序列化和反序列化操作。
想让一个类对象实现序列化,只需要这个类实现Serialzable接口,并声明一个serialVersionUID即可,serialVersionUID可以声明成1L或者IDE根据当前类接口自动生成它的hash值。
没有serialVersionUID不影响序列化,但是可能会影响反序列化。序列化时,系统当前类的serialVersionUID写入序列化文件中,当反序列化时,回去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果不一致,无法完成反序列化。

2.Parcelable接口

class User implements Parcelable {
    public int userId;
    public String userName;
    public boolean isMale;

    //从序列化后的对象中创建原始数据
    protected User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readByte() != 0;
    }

    //内容描述,基本都是0
    @Override
    public int describeContents() {
        return 0;
    }

    //序列化方法
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(userId);
        dest.writeString(userName);
        dest.writeByte((byte) (isMale ? 1 : 0));
    }

    //反序列方法
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

Seriallizable用起来简单但是开销大,序列化和反序列过程需要大量的I/O操作,而Parcelable是Android序列化方式,更适合Android平台,效率更高。Parcelable主要用于内存序列化上,而Seriallizable更适用于序列化到本地存储设备,或者将对象序列化后通过网络传输到别的客户端。

四、Android中的IPC方式

1.使用Intent传递Bundle

Activity、Service、Receiver都支持在 Intent中传递Bundle数据,Bundle实现了Pareclable接口,所以它可以方便地在不同进程间传输。

2.使用文件共享

Android基于Linux,使得其并发读写文件可以没有限制的进行,两个进程可以通过读写一个文件来交换数据。共享数据对文件格式没有要求,双反约定就行。使用文件共享很有可能出问题。
SharedPreferences是个特例,虽然也是属于文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对他的读写变得不可靠,高并发的时候,很大可能会丢失数据。

3.使用Messenger

Messenger可以在不同的进程中传递Message对象,在Message中存入我们需要传递的数据,就可以实现数据的跨进程传递。它是一种轻量级的IPC方案,底层实现是AIDL。
Messenger对AIDL做了封装,使得我们可以更便捷的实现跨进程通信,它一次只处理一个请求,在服务端不用考虑线程同步问题,在服务端不存在并发执行的情形。实现一个Messenger有如下几个步骤:

服务端进程

在服务端创建一个Service,同时创建一个Handler,并通过它来创建一个Messenger对象,然后再Service的onBind中返回这个Messenger对象底层Binder即可。

客户端进程

绑定服务端Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger。通过这个对象就可以向服务端发消息了。如果需要服务端回应客户端,就需要和服务端一样,创建一个Handler,并通过它来创建一个Messenger对象,然后把这个Messenger对象通过Message的replyTo参数传给服务端,服务端可以通过这个replyTo参数回应客户端。

image.png

4.使用AIDL

服务端

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

客户端

绑定服务端的Service,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可可以范文AIDL里边的方法了。

创建AIDL

在AIDL文件中,并不是所有的额数据类型都是可以使用的。

以上6种数据就是AIDL所支持的所有类型,其中自定义的Parecelable对象和AIDL对象必须显示的import,不管是否和当前的AIDL文件位于同一个包。
AIDL文件中用到了自定义的Parcelable对象,必须新建一个同名的AIDL文件,在其中声明它为parcelable类型。
AIDL中除了基础数据类型,其它类型参数都需要标上方向:in、out、inout,in是输入型参数,out是输出型参数,inout是输入输出型参数。

服务端实现
public class BookService extends Service {

    private static final CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    public BookService() {

    }

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "android"));
        mBookList.add(new Book(2, "ios"));
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new BookBinder();
    }


    public static class BookBinder extends IBookManager.Stub {

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

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    }
}

上面是远程服务端示例,AIDL方法在服务端的Binder线程池中执行,因此各个客户端同时连接的时候,会存在多个线程同时访问的情形,所以要在AIDL中处理线程同步,这个CopyOnWriteArrayList支持并发的读写。
AIDL所支持的是一个抽象的List,只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,当时Binder会按照List规范去范文数据并最终形成一个ArrayList传递给客户端。

5.客户端实现
class BookActivity : AppCompatActivity() {
    var mIBookManager: IBookManager? = null

    var mConn = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            service?.let {
                mIBookManager = IBookManager.Stub.asInterface(it)
                val bookList = mIBookManager?.bookList
                Toast.makeText(this@BookActivity, "书籍${bookList?.size ?: 0}", Toast.LENGTH_LONG)
                    .show()
            }
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            TODO("Not yet implemented")
        }

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_book)
        val intent = Intent(this, BookService::class.java)
        bindService(intent, mConn, BIND_AUTO_CREATE)
    }

    override fun onDestroy() {
        unbindService(mConn)
        super.onDestroy()
    }
}

ServiceConnection 的回调方法在UI线程中运行,服务端的方法有可能很久才能执行完毕,需要考虑ANR的问题。
服务的方法本省就运行再Binder线程池中,本身可以执行大量耗时操作,不要去服务端方法中开县城去进行异步任务。

服务端通知客户端示例

客户端

class BookActivity : AppCompatActivity() {
    var mIBookManager: IBookManager? = null

    var mConn = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            service?.let { it ->
                mIBookManager = IBookManager.Stub.asInterface(it)
                val bookList = mIBookManager?.bookList
                Toast.makeText(this@BookActivity, "书籍${bookList?.size ?: 0}", Toast.LENGTH_LONG)
                    .show()
                mIBookManager?.let {
                    it.registerListener(mArrivedListener)
                }
            }
        }

        override fun onServiceDisconnected(name: ComponentName?) {

        }

    }

    var mArrivedListener = object : IOnNewBookArrivedListener.Stub() {
        override fun onNewBookArried(book: Book?) {
            mHandler.obtainMessage(1, book).sendToTarget()
        }
    }

    var mHandler = object : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                1 -> Toast.makeText(this@BookActivity, msg.obj.toString(), Toast.LENGTH_LONG).show()
                else -> super.handleMessage(msg)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_book)
        val intent = Intent(this, BookService::class.java)
        bindService(intent, mConn, BIND_AUTO_CREATE)
    }

    override fun onDestroy() {
        mIBookManager?.let {
            if (it.asBinder().isBinderAlive) {
                it.unRegisterListener(mArrivedListener)
            }
        }
        unbindService(mConn)
        super.onDestroy()
    }
}

服务端

public class BookService extends Service {

    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
    private static final CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    private static final RemoteCallbackList<IOnNewBookArrivedListener> mListenerList =
            new RemoteCallbackList<>();

    public BookService() {

    }

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "android"));
        mBookList.add(new Book(2, "ios"));
        new Thread(new ServiceWorker()).start();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new BookBinder();
    }

    private void onNewArrived(Book book) throws RemoteException {
        mBookList.add(book);
        for (int i = 0; i < mListenerList.beginBroadcast(); i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            listener.onNewBookArried(book);
        }
        mListenerList.finishBroadcast();
    }

    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestroyed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    onNewArrived(new Book(mBookList.size() + 1, "a"));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void onDestroy() {
        mIsServiceDestroyed.set(true);
        super.onDestroy();
    }

    public static class BookBinder extends IBookManager.Stub {

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

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
        }

        @Override
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
        }
    }
}

RemoteCallbackList是系统提供专门用于删除跨进程listener的,它的内部有一个Map结构,用来保存所有的AIDL回调,这个Map的key就是Binder类型,value是CallBack类型。
客户端解注册的时候,我们只需要遍历服务端所有的listener,找出那个和接注册listener具有相同的Binder对象的服务端listener并把它删除即可。
RemoteCallbackList的beginBroadcast和finishBroadcast必须配对使用。

5.使用ContentProvider

ContentProvider是Android专门提供不同应用间进行数据共享的方式。底层实现一样是Binder。
系统预置了许多ContentProvider,比如通讯录,日程信息表,只需要通过ContentResolver的query、update、insert、delete方法即可。

6.使用Socket

六、选用合适IPC方式

image.png
上一篇 下一篇

猜你喜欢

热点阅读