Android基础-进程间通信简介

320 阅读8分钟

「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

多进程

定义:进程指一个执行单元,在移动设备上就是一个程序或应用,我们所说的多进程一般是指一个应用包含多个进程。

使用多进程的原因:某些模块由于特殊的需求要在单独的进程中,增加应用的可用内存控件。

开启多进程

开启多进程方式只有一种,在AndroidManifest.xml中注册Service、Activity、Provider、Receiver时指定 android:process 属性,例如:

<service android:name=".BleService"
            android:process=":remote"/>
        
        <activity android:name=".DeviceActivity"
            android:process="com.king.app.remote2"/>

进程名可使用缩写开头:remote,也可以使用完整命名com.king.app.remote2

多进程引发的问题

  • 1.静态成员和单例模式失效
  • 2.线程同步机制失效
  • 3.SharedPreferences可靠性降低
  • 4.Application被多次创建

前两个问题:系统为每个进程、应用分配独立的虚拟机,不同的虚拟机占有不同的内存地址,所以同一个类会产生不同的副本,导致共享数据失败,必然也不能实现线程同步。

SharedPreference底层使用XML文件的读写来实现,多进程并发的读写很可能导致数据异常。

系统分配多个虚拟机时相当于把同一个应用重新启动多次,必然会导致Application多次创建。

多进程通信方式

名称优点缺点适用场景
Bundle简单易用只能传输Bundle支持的数据类型四大组件间的进程间通信
文件共享简单易用不适合高并发场景,并且无法做到进程间的即时通信无并发访问情形,交换简单的数据实时性不高的场景
AIDL功能强大,支持一对多并发通信,支持实时通信使用稍复杂,需要处理好线程同步一对多通信且有RPC需求
Messenger功能一般,支持一对多的串行通信,支持实时通信不能很好的处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型低并发的一对多即时通信,无RPC需求,或者无需返回结果的RPC需求
Contentprovider在数据访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作可理解为受约束的AIDL,主要提供数据的CRUD操作一对多的进程间数据共享
Socket可以通过网络传输字节流,支持一对多并发实时通信不支持RPC网络数据交换

RPC:远程过程调用。

Bundle

四大组件直接可以使用Intent.putExtras(Bundle bunle)方法来传递数据

Intent intent = new Intent(MainActivity.this, LoginActivity.class);
Bundle bundle = new Bundle();
bundle.putString("msg", "This is a message");
intent.putExtras(bundle);
startActivity(intent);

文件共享

使用Serializable序列化对象,使用ObjectOutputSteam将数据写入到文件中

NoteBook noteBook = new NoteBook();
noteBook.setName("cr");
noteBook.setContent("Empty");

File file = new File("xxx");
ObjectOutputStream outputStream = null;
try{
    outputStream = new ObjectOutputStream(new FileOutputStream(file));
    outputStream.writeObject(noteBook);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        outputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

NoteBook.java

public class NoteBook implements Serializable {
    private String mName;
    private String mContent;

    public String getName() {
        return mName;
    }

    public void setName(String name) {
        mName = name;
    }

    public String getContent() {
        return mContent;
    }

    public void setContent(String content) {
        mContent = content;
    }
}

使用ObjectInputStream读取文件中的数据

File file = new File("xxx");
ObjectInputStream objectInputStream = null;

try {
    objectInputStream = new ObjectInputStream(new FileInputStream(file));
    NoteBook noteBook = (NoteBook) objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
} finally {
    try {
        objectInputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

AIDL

支持的数据类型

  • 基本数据类型
  • String、CharSequence
  • ArrayList、HashMap(其内部元素也需要被AIDL支持)
  • 实现了Parcelable接口的对象
  • AIDL类型的接口,非普通接口

实现:

1.定义暴露给客户端的AIDL接口

2.实现服务端服务,构造Binder对象

3.客户端bind服务,并通过AIDL的代理对象的asInterface获取服务端AIDL接口对象

例:

定义服务端暴露给客户端的AIDL接口

1.定义Book类,实现Parcelable接口

Book.java位于com.pass.animationtest.Bean包下

package com.pass.animationtest.Bean;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * @author houki
 * @since 2021-08-12
 */
public class Book implements Parcelable {
    public int bookId;
    public String bookName;

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

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

    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];
        }
    };

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

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

2.定义Book.aidl

AIDL无法直接使用普通类中的对象,需要使用相同名字的引用,

Book.aidl位于com.pass.animationtest.Bean包下

// Book.aidl
package com.pass.animationtest.Bean;

parcelable Book;

3.定义暴露给客户端的AIDL接口

使用到Book类或其他AIDL接口时,需要使用import引入

// ILibraryManager.aidl
package com.pass.animationtest;

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

import com.pass.animationtest.Bean.Book;
import com.pass.animationtest.IBookListener;

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

    void register(IBookListener listener);
    void unregister(IBookListener listener);
}

实现服务端服务

主要目的是构造出Binder对象,定义Binder类型成员变量,实现ILibraryManager.Stub

public class RemoteService extends Service {

    private final static String TAG = "RemoteService";
    //暂不考虑原子问题
    private boolean mIsStop = false;
    
    // 系统提供的专门用于保存、删除跨进程 listener 的类
    private final RemoteCallbackList<IBookListener> mListenerList = new RemoteCallbackList<>();

    private final List<Book> mBookList = new ArrayList<>();

    private final Binder binder = new ILibraryManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            Log.e(TAG, "获取图书列表");
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            Log.e(TAG, "I get a new Book " + book.bookName);
            mBookList.add(book);
        }

        @Override
        public void register(IBookListener listener) throws RemoteException {
            Log.e(TAG, "注册通知");
            mListenerList.register(listener);
        }

        @Override
        public void unregister(IBookListener listener) throws RemoteException {
            Log.e(TAG, "注销通知");
            mListenerList.unregister(listener);
        }
    };

    @Override
    public void onCreate() {
        mBookList.add(new Book(1024, "金瓶梅1"));
        mBookList.add(new Book(1025, "金瓶梅2"));
        mBookList.add(new Book(1026, "金瓶梅3"));
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        startBuy();
        return binder;
    }

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

    private void startBuy() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!mIsStop) {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Book newBook = new Book(new Random().nextInt(10000), "新书");
                    mBookList.add(newBook);
                    notifyBook(newBook);
                }
            }
        }).start();
    }

    private void notifyBook(Book book) {
        int n = mListenerList.beginBroadcast();
        for (int i = 0; i < n; i++) {
            IBookListener listener = mListenerList.getBroadcastItem(i);
            try {
                listener.onNewBookArrived(book);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        mListenerList.finishBroadcast();
    }
}

客户端实现

使用ILibraryManager代理的asInterface方法获取AIDL的接口对象

private ILibraryManager iLibraryManager;
private final ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        iLibraryManager = ILibraryManager.Stub.asInterface(service);
        try {
            iLibraryManager.register(mListener);
            List<Book> bookList = iLibraryManager.getBookList();
            for (Book book : bookList) {
                Log.e(TAG, book.bookId + " - " + book.bookName);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        try {
            iLibraryManager.unregister(mListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        iLibraryManager = null;
    }
};

//回调,线程为Binder线程,若需更新UI,使用Handler处理
private IBookListener mListener = new IBookListener.Stub() {
    @Override
    public void onNewBookArrived(Book book) throws RemoteException {
        Log.e(TAG,  "新书已到:" + book.bookId + " - " + book.bookName);
    }
};

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

    //启动远程服务端Service
    bindService(new Intent(this, RemoteService.class), 
                mConnection, 
                Context.BIND_AUTO_CREATE);
    
    //添加图书
    try {
        iLibraryManager.addBook(new Book(2048, "玉蒲团1"));
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

重新连接

创建DeatchRecipient对象

private IBinder.DeathRecipient mAidlDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (iLibraryManager != null) {
            iLibraryManager.asBinder().unlinkToDeath(mAidlDeathRecipient, 0);
            iLibraryManager = null;

            //重新连接 bindService
        }
    }
};

在客户端得到Binder对象时,注册一个到DeatchRecipient监听中,在服务进程终止时,可以重新bindService

iLibraryManager = ILibraryManager.Stub.asInterface(service);

try {
    iLibraryManager.asBinder().linkToDeath(mAidlDeathRecipient, 0);
} catch (RemoteException e) {
    e.printStackTrace();
}

Messenger

Messenger是一种轻量级的多进程通信方式,它是在AIDL基础上封装而成的,可以看做AIDL的简化版,支持一对多的串行试试通信,一次只处理一个请求,不存在并发的问题,和AIDL使用方式类似,但是简单的多,同样需要C/S

定义Service

初始化Messenger时,传入Handler,并在handleMessage中接收和处理消息;

public class RemoteService2 extends Service {

    private final static String TAG = "RemoteService2";

    private Messenger mRemoteMessenger = new Messenger(new MessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mRemoteMessenger.getBinder();
    }

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);

            Bundle bundle = msg.getData();
            Log.e(TAG, "Messenger get message " + bundle.getString("msg"));

            Message replyMsg = Message.obtain();
            replyMsg.what = 1025;

            Bundle replyBundle = new Bundle();
            replyBundle.putString("msg", "I am remote service");
            replyMsg.setData(replyBundle);
            try {
                msg.replyTo.send(replyMsg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

定义客户端

//服务端Messenger,通过Binder获取
private Messenger mServiceMessenger;
private ServiceConnection mMessengerConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mServiceMessenger = new Messenger(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mServiceMessenger = null;
    }
};

//客户端Messenger,用于接收消息
private Messenger mClientMessenger = new Messenger(new MessengerHandler());

//客户端发送消息,消息内容使用Bundle作为载体,不能直接使用obj
Message msg = Message.obtain();
msg.what = 1024;
//msg.obj = "Hi, I am Main.";
Bundle bundle = new Bundle();
bundle.putString("msg", "Hi, I am Main.");
msg.setData(bundle);

msg.replyTo = mClientMessenger;//回复消息
try {
    mServiceMessenger.send(msg);
} catch (RemoteException e) {
    e.printStackTrace();
}

ContentProvider

Uri统一资源标识符

唯一标识ContentProvider其中的数据,外界的进程、应用可以通过Uri找到对应的ContentProvider

具体划分:

content://com.king.long/Book?name=110

content:scheme,URI前缀

com.king.long:授权信息AUTHORITY,唯一标识

book:path

name=110:query

MIME

指定某个扩展名的文件使用哪种应用程序来打开

ContentProvider更加URI返回MIME类型

ConentProvider.getType(uri)

MIME组成:类型+子类型

如:text/html、text/css

单条记录:vnd.android.cursor.item/自定义

多条记录:vnd.android.cursor.dir/自定义

ContentProvider类

本质是:添加、删除、获取 & 修改(更新)数据

ContentResolver类

统一管理ContentProvider

具体使用

// 使用ContentResolver前,需要先获取ContentResolver
// 可通过在所有继承Context的类中 通过调用getContentResolver()来获得ContentResolver
ContentResolver resolver =  getContentResolver(); 

// 设置ContentProvider的URI
Uri uri = Uri.parse("content://cn.scu.myprovider/user"); 
 
// 根据URI 操作 ContentProvider中的数据
// 此处是获取ContentProvider中 user表的所有记录 
Cursor cursor = resolver.query(uri, null, null, null, "userid desc"); 

Android提供了3个辅助类

  • ContentUris
  • UriMatch
  • ContentObserver

ContentUris

// withAppendedId()作用:向URI追加一个id
Uri uri = Uri.parse("content://cn.scu.myprovider/user") 
Uri resultUri = ContentUris.withAppendedId(uri, 7);  
// 最终生成后的Uri为:content://cn.scu.myprovider/user/7

// parseId()作用:从URL中获取ID
Uri uri = Uri.parse("content://cn.scu.myprovider/user/7") 
long personid = ContentUris.parseId(uri); 
//获取的结果为:7

UriMatcher

  • 在ContentProvider中注册URI
  • 根据URI匹配ContentProvider中对应方法
// 步骤1:初始化UriMatcher对象
    UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); 
    //常量UriMatcher.NO_MATCH  = 不匹配任何路径的返回码
    // 即初始化时不匹配任何东西

// 步骤2:在ContentProvider 中注册URI(addURI())
    int URI_CODE_a = 1int URI_CODE_b = 2;
    matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a); 
    matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b); 
    // 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a
    // 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b

// 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match())

@Override   
    public String getType(Uri uri) {   
      Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");   

      switch(matcher.match(uri)){   
     // 根据URI匹配的返回码是URI_CODE_a
     // 即matcher.match(uri) == URI_CODE_a
      case URI_CODE_a:   
        return tableNameUser1;   
        // 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表
      case URI_CODE_b:   
        return tableNameUser2;
        // 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表
    }   
}

ContentObserver类

当数据发生变化是,触发

// 步骤1:注册内容观察者ContentObserver
    getContentResolver().registerContentObserver(uri);
    // 通过ContentResolver类进行注册,并指定需要观察的URI

// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
    public class UserContentProvider extends ContentProvider { 
      public Uri insert(Uri uri, ContentValues values) { 
      db.insert("user", "userid", values); 
      getContext().getContentResolver().notifyChange(uri, null); 
      // 通知访问者
   } 
}

// 步骤3:解除观察者
 getContentResolver().unregisterContentObserver(uri);
    // 同样需要通过ContentResolver类进行解除

\