Android IPC机制笔记

参考《Android开发艺术探索》

一、 IPC简介

IPC含义为跨进程通信,通常不同的APP运行在不同的进程中,当两个app需要交互时,就要跨进程进行通信;或者一个app开了多个进程,大部分情况下也是需要跨进程通信的。
IPC有多种方式实现,有Bundle、文件共享、Messenger、AIDL、ContentProvider、Socket等。

首先是IPC的基础部分

二、IPC基础

1.开启多进程

在AndroidMenifest中指定activity的process属性,即可将这个这个activity放在单独的进程中。

<activity android:name=".ThirdActivity" android:process=":remote"/>

这个activity将运行在包名.remote进程中。这个进程是当前应用的私有进程,其它应用不可以和它运行在同一个进程中。也可以直接指定一个字符串作为进程名。

多进程模式下进程运行在独立的虚拟机中,不同虚拟机分配有不同的内存地址,因此不同进程访问同一类会有多个副本,当一个进程改变了类的变量时对另一个进程并没有影响。因此

  1. 静态成员和单例会失效
  2. 线程同步机制失效
  3. Application存在多次创建现象

2.序列化和反序列化

  1. Serialable
    Serialable是Java中提供的序列化接口,一个类若要序列化只需要实现Serialable接口即可。如:
public class User implements Serializable {

    private String name;
    private int id;
    private boolean isMale;

    public User(String name, int id, boolean isMale) {
        this.name = name;
        this.id = id;
        this.isMale = isMale;
    }

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public boolean isMale() {
        return isMale;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", isMale=" + isMale +
                '}';
    }
}

序列化时可以通过ObjectOutputStream#writeObject(Object)方法将类实例写入通过流写入文件或者其它的地方,反序列化时通过ObjectInputStream#readObject()方法从输入流中读取即可。
2. Parcelable
Parcelable是Android提供的序列化和反序列化方法,使用相较于Serialable稍显复杂但是效率较高。需要序列化的类要实现Parcelable接口并实现序列化和反序列化的过程:

public class Book implements Parcelable {
    private String bookName;
    private int bookId;

    public Book(String bookName, int bookId) {
        this.bookName = bookName;
        this.bookId = bookId;
    }

    private Book(Parcel in) {
        bookName = in.readString ();
        bookId = in.readInt ();
    }

    public static final Creator<Book> CREATOR = new Creator<Book> () {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book (in);
        }

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

    public String getBookName() {
        return bookName;
    }

    public int getBookId() {
        return bookId;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString (bookName);
        dest.writeInt (bookId);
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                ", bookId=" + bookId +
                '}';
    }

}

通过writeToParcel(Parcel dest, int flags)方法实现序列化过程,通过createFromParcel(Parcel in)实现反序列化,通过describeContents()返回内容描述,一般返回0。
Parcelabe可作为Intent携带的数据在组件和进程间传递。
3. aidl
aidl是常用的跨进程通信方式,底层基于Binder,类似于客户端和服务端的架构,客户端根据自身需求规定接口,服务端则实现接口中的方法,并将结果通过Binder返回个客户端。其特殊之处在于接口定义需要定义为.aidl文件,使用到的自定义类需要实现Parcelabe接口,即可序列化。下面是一个例子:
假设客户端是读者,服务端是图书馆,读者需要知道图书馆的所有藏书,并且也可以向其中添加书,因此客户端要定义一个接口IBookManager,这个接口是一个aidl接口,新建IBookManager.aidl文件如下:

// IBookManager.aidl
package com.github.xiaogegechen.ipctest;

import com.github.xiaogegechen.ipctest.Book;
import com.github.xiaogegechen.ipctest.IOnNewBookArrivedListener;

// Declare any non-default types here with import statements

interface IBookManager {
   void addBook(in Book book);
   List<Book> getBookList();
}

Book类实现了Parcelabe接口,并且新建Book.aidl文件并声明,如下:

package com.github.xiaogegechen.ipctest;

parcelable Book;

接着需要服务端实现客户端的需求,将新建的Book.aidlBook.javaIBookManager.aidl文件拷贝到服务端app工程中,build工程,可以发现生成了一个新的类IBookManager.java,如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: F:\\program\\Android\\IPCTestClient\\app\\src\\main\\aidl\\com\\github\\xiaogegechen\\ipctest\\IBookManager.aidl
 */
package com.github.xiaogegechen.ipctest;
// Declare any non-default types here with import statements
// android.os.IInterface接口中定义了asBinder()方法,
public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     * 服务端的binder
     */
    public static abstract class Stub extends android.os.Binder implements com.github.xiaogegechen.ipctest.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.github.xiaogegechen.ipctest.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface (this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.github.xiaogegechen.ipctest.IBookManager interface,
         * generating a proxy if needed.
         * 将服务端的binder对象封装成客户端需要的binder对象
         */
        public static com.github.xiaogegechen.ipctest.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.github.xiaogegechen.ipctest.IBookManager))) {
                return ((com.github.xiaogegechen.ipctest.IBookManager) iin);
            }
            return new com.github.xiaogegechen.ipctest.IBookManager.Stub.Proxy (obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        /**
         * 从data中取出参数,从code中取出方法标志,从而调用服务端相应的方法,并将结果写进reply中
         */
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString (descriptor);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface (descriptor);
                    com.github.xiaogegechen.ipctest.Book _arg0;
                    if ((0 != data.readInt ())) {
                        _arg0 = com.github.xiaogegechen.ipctest.Book.CREATOR.createFromParcel (data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook (_arg0);
                    reply.writeNoException ();
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface (descriptor);
                    java.util.List<com.github.xiaogegechen.ipctest.Book> _result = this.getBookList ();
                    reply.writeNoException ();
                    reply.writeTypedList (_result);
                    return true;
                }
                default: {
                    return super.onTransact (code, data, reply, flags);
                }
            }
        }

        /**
         * 客户端的binder
         */
        private static class Proxy implements com.github.xiaogegechen.ipctest.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            // 将客户端的binder对象封装成服务端的binder对象
            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            // 客户端会调用这个方法,此方法将调用服务端的transact()方法,transact()方法调用onTransact()方法,onTransact()方法
            // 内部会调用服务端的addBook()方法,从而完成从客户端到服务端的调用
            @Override
            public void addBook(com.github.xiaogegechen.ipctest.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain ();
                android.os.Parcel _reply = android.os.Parcel.obtain ();
                try {
                    _data.writeInterfaceToken (DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt (1);
                        book.writeToParcel (_data, 0);
                    } else {
                        _data.writeInt (0);
                    }
                    // 调用服务端的addBook方法,参数在_data中,结果在_reply中,只是这里没有返回值
                    mRemote.transact (Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException ();
                } finally {
                    _reply.recycle ();
                    _data.recycle ();
                }
            }

            // 和addBook()方法类似
            @Override
            public java.util.List<com.github.xiaogegechen.ipctest.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain ();
                android.os.Parcel _reply = android.os.Parcel.obtain ();
                java.util.List<com.github.xiaogegechen.ipctest.Book> _result;
                try {
                    _data.writeInterfaceToken (DESCRIPTOR);
                    // 调用服务端的addBook方法,参数在_data中,结果在_reply中
                    mRemote.transact (Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException ();
                    _result = _reply.createTypedArrayList (com.github.xiaogegechen.ipctest.Book.CREATOR);
                } finally {
                    _reply.recycle ();
                    _data.recycle ();
                }
                return _result;
            }

        }

        // 方法标记符号,用于区分接口方法
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public void addBook(com.github.xiaogegechen.ipctest.Book book) throws android.os.RemoteException;

    public java.util.List<com.github.xiaogegechen.ipctest.Book> getBookList() throws android.os.RemoteException;
}


服务端需要继承IBookManager.Stub类并实现客户端规定的接口中的方法,然后将这个对象binder对象放回个客户端,而客户端需要调用IBookManager.Stub.asInterface()方法将这个binder封装成客户端需要的接口,从而实现在客户端调用服务端的服务。服务端:

public class MyService extends Service {

    private static final String TAG = "MyService";

    private CopyOnWriteArrayList<Book> mBooks;
    
    private IBookManager.Stub mStub = new IBookManager.Stub () {
        @Override
        public void addBook(Book book) throws RemoteException {
            mBooks.add (book);
        }

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

    @Override
    public void onCreate() {
        super.onCreate ();
        mBooks = new CopyOnWriteArrayList<> ();
        mBooks.add (new Book ("Book1", 1));
        mBooks.add (new Book ("Book2", 2));
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mStub;
    }

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

客户端:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private TextView mTextView;
    private IBookManager mBookManager;
    
    private ServiceConnection mConnection = new ServiceConnection () {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManager = IBookManager.Stub.asInterface (service);
            try {
                mBookManager.registerListener (mOnNewBookArrivedListener);
                List<Book> bookList = mBookManager.getBookList ();
                Log.d (TAG, "onServiceConnected: " + bookList);
                mBookManager.addBook (new Book ("Book3", 3));
                List<Book> newBookList = mBookManager.getBookList ();
                Log.d (TAG, "onServiceConnected: " + newBookList);
            } catch (RemoteException e) {
                e.printStackTrace ();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (R.layout.activity_main);
        mTextView = findViewById (R.id.text_view);
        Intent intent = new Intent ();
        intent.setAction ("com.github.xiaogegechen.ipctest.MyService");
        intent.setPackage ("com.github.xiaogegechen.ipctest");
        bindService (intent, mConnection, BIND_AUTO_CREATE);
    }

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

三、IPC方式

1. Bundle

Bundle实现了Parcelable接口,所以支持在四大组件中通过Intent携带Bundle数据从而实现进程间通信,使用比较简单。

2. 文件共享

通过共享文件实现进程通信,也比较简单,但是要注意并发写的问题

3. 使用aidl

上述已经介绍过。

4. 使用ContentProvider

Android四大组件之一,专门为进程间共享数据服务。

5. 使用Socket

socket工作原理(摘自大神博客
socket工作原理
很显然,socket也是可以完成进程间通信的。
服务端:

public class SocketService extends Service {

    private static final String TAG = "SocketService";

    private boolean mIsServiceDestroyed = false;

    private static final String[] CHAT_CONTENT = new String[]{
            "你好啊!哈哈",
            "请问你叫什么名字啊",
            "今天沈阳天气不错啊,shy",
            "你知道吗?我只和你聊天的哦!",
            "给你讲个笑话吧,你比我帅,哈哈!"
    };

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate ();
        new Thread (new Runnable (){
            @Override
            public void run() {
                ServerSocket serverSocket = null;
                try {
                    // 监听5000端口
                    serverSocket = new ServerSocket (5000);
                } catch (IOException e) {
                    Log.e (TAG, "open socket failed");
                    e.printStackTrace ();
                    return;
                }
                Log.d (TAG, "open socket successfully");

                while(!mIsServiceDestroyed){
                    try {
                        // 等待有客户端连接进来,当没有客户端连接时会阻塞
                        final Socket socket = serverSocket.accept ();
                        Log.d (TAG, "a client connect, it is: " + socket);
                        replyClient (socket);
                    } catch (IOException e) {
                        e.printStackTrace ();
                    }
                }
            }
        }).start ();
    }

    private void replyClient(Socket socket) throws IOException{
        BufferedReader reader = new BufferedReader (new InputStreamReader (socket.getInputStream ()));
        PrintWriter writer = new PrintWriter (new BufferedWriter (new OutputStreamWriter (socket.getOutputStream ())));
        while(!mIsServiceDestroyed){
            // 读取客户端发来的信息,以"\n"为参考,当客户端没有发送信息时会阻塞
            // 读到输入流的尾部时返回null
            String line = reader.readLine ();
            Log.d (TAG, "receive message from client, the message is: " + line);
            if(line == null){
                // 客户端断开链接,结束
                break;
            }
            // 随机进行回复
            int idx = new Random ().nextInt (CHAT_CONTENT.length);
            String reply = CHAT_CONTENT[idx];
            writer.println (reply);
            writer.flush ();
        }
        Log.d (TAG, "client quit!");
        // 断开读取客户端的连接
        reader.close ();
        // 断开向客户端发消息的连接
        writer.close ();
        // 关闭socket
        socket.close ();
    }

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

客户端:

public class SocketActivity extends AppCompatActivity {

    private static final String TAG = "SocketActivity";

    private static final int CONNECT_OK = 0;
    private static final int NEW_MESSAGE_ARRIVED = 1;
    private static final int SEND_MESSAGE = 2;

    private Button mButton;
    private TextView mTextView;
    private EditText mEditText;

    private PrintWriter mPrintWriter;

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler (){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage (msg);
            switch (msg.what){
                case CONNECT_OK:
                    mButton.setEnabled (true);
                    break;
                case NEW_MESSAGE_ARRIVED:
                    mTextView.append ("from server:\n");
                    String reply = (String) msg.obj;
                    mTextView.append (reply);
                    mTextView.append ("\n");
                    break;
                case SEND_MESSAGE:
                    String str = (String) msg.obj;
                    mTextView.append (str);
                    break;
                default:
                    break;
            }
        }
    };

    private Socket mSocket;

    @Override
    protected void onCreate(@Nullable final Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (R.layout.activity_socket);

        mButton = findViewById (R.id.send);
        mTextView = findViewById (R.id.text_chat);
        mEditText = findViewById (R.id.input);

        mButton.setEnabled (false);
        mButton.setOnClickListener (new View.OnClickListener () {
            @Override
            public void onClick(View v) {
                final String str = mEditText.getText ().toString ();
                mTextView.append ("from client:\n" + str + "\n");
                new Thread (new Runnable (){
                    @Override
                    public void run() {
                        mPrintWriter.println (str);
                        mPrintWriter.flush ();
                    }
                }).start ();
            }
        });

        // 开启远程服务
        Intent intent = new Intent ();
        intent.setAction ("com.github.xiaogegechen.ipctest.SocketService");
        intent.setPackage ("com.github.xiaogegechen.ipctest");
        ComponentName componentName = startService (intent);
        Log.d (TAG, "startService, componentName is: " + componentName);

        new Thread (new Runnable (){
            @Override
            public void run() {
                Socket socket = null;
                boolean success = false;
                while(!success){
                    try {
                        // 建立链接,三次握手会在这里完成
                        socket = new Socket ("localhost", 5000);
                        mSocket = socket;
                        // 建立向服务端发送数据的连接
                        mPrintWriter = new PrintWriter (new BufferedWriter (new OutputStreamWriter (mSocket.getOutputStream ())));
                        mPrintWriter.println ("hello");
                        mPrintWriter.flush ();
                        success = true;
                        mHandler.sendEmptyMessage (CONNECT_OK);
                    } catch (IOException e) {
                        e.printStackTrace ();
                        Log.e (TAG, "connect tcp server failed! retry ...");
                        // 暂停一秒继续重试
                        SystemClock.sleep (1000);
                    }
                }
                Log.d (TAG, "connection ok!");

                try {
                    // 链接成功,开始通信
                    BufferedReader reader = new BufferedReader (new InputStreamReader (socket.getInputStream ()));
                    while(!SocketActivity.this.isFinishing ()){
                        // 读取服务端传回的信息
                        String reply = reader.readLine ();
                        Log.d (TAG, "receive: " + reply);
                        if(!TextUtils.isEmpty (reply)){
                            Message message = mHandler.obtainMessage ();
                            message.what = NEW_MESSAGE_ARRIVED;
                            message.obj = reply;
                            message.sendToTarget ();
                        }
                    }
                    Log.d (TAG, "quit...");
                    // 关闭向服务端发送数据的连接
                    mPrintWriter.close ();
                    // 关闭从服务端读数据的链接
                    reader.close ();
                    // 关闭socket
                    socket.close ();
                } catch (IOException e) {
                    e.printStackTrace ();
                }
            }
        }).start ();
    }

    @Override
    protected void onDestroy() {
        try {
            if (mSocket != null) {
                mSocket.shutdownInput ();
                mSocket.close ();
            }
        } catch (IOException e) {
            e.printStackTrace ();
        }
        super.onDestroy ();
    }
}

四、Binder连接池

当客户端有大量的服务需要服务端完成时,如果是普通的aidl方法就需要创建镀铬服务,非常不方便管理,这时可以使用binder连接池。这时服务端仅仅需要开启一个服务即可,当客户端需要服务的时候,通过一个queryCode向该服务发送请求,拿到自己需要的Binder,利用这个binder拿到结果。

需要在客户端添加一个aidl接口,接收一个queryCode并返回binder,服务端开启服务并实现这个接口,根据不同的queryCode返回不同的binder,如下:

// IBinderPool.aidl
package com.github.xiaogegechen.ipctest;

import android.os.IBinder;

interface IBinderPool {
    // 根据queryCode返回相应的binder
    IBinder queryBinder(int queryCode);
}

服务端:

// BinderPoolService.java
public class BinderPoolService extends Service {

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

// BinderPoolImpl.java
public class BinderPoolImpl extends IBinderPool.Stub{
    @Override
    public IBinder queryBinder(int queryCode) throws RemoteException {
        IBinder binder = null;
        switch(queryCode){
            case QueryCode.CODE_ICOMPUTE:
                binder = new CopmuteImpl ();
                break;
            case QueryCode.CODE_ISECURIT_CENTER:
                binder = new SecurityCenterImpl ();
                break;
            default:
                break;
        }
        return binder;
    }
}

当客户端有IPC需求的时候,就需要开启这个服务,将返回的binder封装成客户端定义的IBinderPool,通过向IBinderPool提供queryCode拿到对应的binder,在将此binder封装成对应的客户端接口即可使用:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private ServiceConnection mConnection = new ServiceConnection () {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 封装成客户端需要的binderPool
            IBinderPool binderPool = IBinderPool.Stub.asInterface (service);
            try {
                // 拿到服务端ICompute的IBinder
                IBinder computeBinder = binderPool.queryBinder (QueryCode.CODE_ICOMPUTE);
                // 封装成客户端需要的ICompute
                ICompute compute = ICompute.Stub.asInterface (computeBinder);
                // 计算
                int result = compute.add (10, 20);
                Log.d (TAG, "result is: " + result);

                IBinder securityBinder = binderPool.queryBinder (QueryCode.CODE_ISECURIT_CENTER);
                ISecurityCenter securityCenter = ISecurityCenter.Stub.asInterface (securityBinder);
                String decode = securityCenter.decode ("hello world");
                Log.d (TAG, decode);

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

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (R.layout.activity_main);

        Intent intent = new Intent ();
        intent.setPackage ("com.github.xiaogegechen.ipctest");
        intent.setAction ("com.github.xiaogegechen.ipctest.binder_pool.BinderPoolService");
        bindService (intent, mConnection, BIND_AUTO_CREATE);

    }
}

binder连接池避免了service的重复创建,使用非常方便,因此应该尽量在项目中使用binder连接池管理IPC请求。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值