Android开发艺术探索读书笔记(第2章|Android开发艺术探索读书笔记(第2章 IPC机制)
1.Message中的字段obj在进程间通信的时候,仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。我们自定义的Parcelable对象是无法通过obj字段进行传输,这时候可以考虑使用Bundle.用法如下
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = new Bundle();
bundle.putString("age","10");
msg.setData(bundle);
}
};
在需要取出的时候,调用msg.getData().get()方法取出值
2.平常的注册与解注册在多进程中会失败,会提示找不到对象。因为Binder会把客户端传过来的对象重新转化并声称一个新的对象。这时候使用RemoteCallbackList,是系统专门提供的用于删除跨进程listener的接口。
3.使用Binder的工作注意事项:
(1)当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,如果一个远程方法是很耗时的,那么不能在UI线程中发起远程请求
(2)由于服务端的Binder方法运行在Binder的线程池,所以Binder方法不管是否耗时都应该采用同步的方法去实现,因为它已经运行在一个线程里面了。此时可以使用CopyOnWriteArrayList或者ConcurrentHashMap等线程安全的集合。
4.客户端的onServiceConnected和onServiceDisConnected方法都运行在UI线程,所以在这里面也不能调用服务端耗时的方法。服务端的方法本身就运行在Binder线程池中,所以服务端方法本身就可以执行耗时操作。没有必要在开启线程。同理,当服务端需要调用客户端的listener中的方法时,被调用的方法运行在客户端的Binder线程池中。所以,同样不能在服务端调用客户端比较耗时的方法,这样可能导致服务端无响应。
5.Binder是可能意外死亡的,往往是由于服务端进程意外停止了,这时我们需要重新连接服务,有两种方式:
(1)给Binder设置死亡代理,监听binderDied方法回调(运行在客户端Binder线程池)
(2)在onServiceDisConnected中重连服务(运行在主线程)
6.给远程服务加权限验证方法
(1)自定义权限,在onBind方法中进行验证,验证失败返回null,这样就无法绑定服务。
(2)在onTransact方法中验证,可以使用权限也可以使用包名,验证失败则返回false,折旧服务端就会终止AIDL中方法的执行。
7.ContentProvider的onCreate方法运行在主线程,其他方法运行在Binder线程。所以不能在onCreate方法中进行耗时操作。还有就是其他方法是存在多线程并发访问的,要做好线程同步。
开启多进程方式 在activity节点中添加
android:process=":remote"
:代表在当前进程的名称上附加上当前应用的包名,同时这也是当前应用的一个私有进程
进程名不以:开头的是一个全局进程,其他应用可以通过shareUID的方式和它运行在一个进程。
序列化 Serializable:
手动指定serialVersionUID是为了保证发序列化成功,有两点需要注意:
1.静态成员变量属于类不属于对象,不参与序列化,transient标记的变量不参与序列化。
Parcelable:
class User implements Parcelable{
private int userId;
private String userName;
private Book book;
public User(int userId,String userName) {
this.userId = userId;
this.userName = userName;
}private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
//book是一个对象,所以要传入上下文的类加载器
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}public static final Creator CREATOR = new Creator() {
@Override
//从序列化的对象中恢复原始对象
public User createFromParcel(Parcel in) {
return new User(in);
}@Override
//创建指定长度的原始对象数组
public User[] newArray(int size) {
return new User[size];
}
};
@Override
//返回当前对象的内容描述
public int describeContents() {
return 0;
}@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeParcelable(book,0);
}
AIDL使用 AIDL使用注意事项:
1.使用自定义对象作为方法参数的话,需要单独写一个aidl文件来描述这个对象,例如我们有个Book对象,那就要写个Book.aidl文件,内容如下
// Book.aidl
package com.example.server;
import com.example.server.Book;
// Declare any non-default types here with import statements
parcelable Book;
2.定向tag
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
3.默认的自定义对象只支持为in的定向tag,如果要支持out或者inout,那么还需要再自定义对象中写readFromParcel方法,写法如下
public void readFromParcel(Parcel dest) {
bookId = dest.readInt();
bookName = dest.readString();
}
4.在服务端,自定义对象要放在main文件夹下,
文章图片
在客户端,自定义对象要放在aidl文件夹下,否则会提示找不到对象
文章图片
同时,为了能让系统找到aidl下的了i,我们需要在客户端的gradle文件中配置
sourceSets{
main{
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
意思就是告诉系统aidl下也是java的类文件,否则我们在用户端无法找到aidl相关的类。
Binder工作原理
系统会为我们定义的aidl的接口类生成一个java类,我们根据这个类来分析Binder的工作原理
//IBookerManager是我们在aidl文件中定义的一个接口,
//所有在Binder中能传输的类都需要继承IInterface接口
public interface IBookManager extends android.os.IInterface
{public static abstract class Stub extends android.os.Binder implements com.example.app.IBookManager
{
//Binder的唯一标识
private static final java.lang.String DESCRIPTOR = "com.example.app.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
*将IBiner对象转化为IBookManager对象,如果服务端和用户端位于同一个进程,返回的就是IBookManager对象,否则返回一个Stub内部的代理对象。
*/
public static com.example.app.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.app.IBookManager))) {
return ((com.example.app.IBookManager)iin);
}
return new com.example.app.IBookManager.Stub.Proxy(obj);
}
//返回当前Binder对象
@Override public android.os.IBinder asBinder()
{
return this;
}
/*
运行在服务端的Binder线程中
code:用来标识需要执行那个方法
data:读取过来的数据
reply:返回去的数据
*/
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(DESCRIPTOR);
//获取getBookList方法的返回值
java.util.List _result = this.getBookList();
reply.writeNoException();
//将返回值写到reply中
reply.writeTypedList(_result);
//返回true代表请求成功
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.example.app.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.app.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.app.IBookManager
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}//运行在客户端进程
@Override public java.util.List getBookList() throws android.os.RemoteException
{
//创建输入型参数_data
android.os.Parcel _data = https://www.it610.com/article/android.os.Parcel.obtain();
//创建输出型参数_reply
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//调用transact方法发起RPC请求,当前线程挂起,然后服务端的onTransact方法会被调用
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.app.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.example.app.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = https://www.it610.com/article/android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
//把参数信息写入到_data中
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List getBookList() throws android.os.RemoteException;
public void addBook(com.example.app.Book book) throws android.os.RemoteException;
}
下面用一张图来说明整个过程:
文章图片
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookManager = IBookManager.Stub.asInterface(service);
try {
//连接成功后,给Biner设置死亡监听
service.linkToDeath(mDeathRecipient,0);
//也可以判断Binder是否死亡
//service.isBinderAlive();
iBookManager.addBook(new Book(1,"A"));
List bookList = iBookManager.getBookList();
} catch (RemoteException e) {
e.printStackTrace();
}
}@Override
public void onServiceDisconnected(ComponentName name) {}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (iBookManager == null) {
return;
}
//移除在Binder上注册的死亡监听,
iBookManager.asBinder().unlinkToDeath(this,0);
iBookManager = null;
//重新绑定远程Service
}
};
IPC方式
【Android开发艺术探索读书笔记(第2章|Android开发艺术探索读书笔记(第2章 IPC机制)】Bundle
Bundle继承了Parcelable,所以可以用来在不同进程传输,我们可以把Bundle支持的数据类型在两个进程间传递。
推荐阅读
- android第三方框架(五)ButterKnife
- 深入理解Go之generate
- Android中的AES加密-下
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- 广角叙述|广角叙述 展众生群像——试析鲁迅《示众》的展示艺术
- 带有Hilt的Android上的依赖注入
- android|android studio中ndk的使用
- Android事件传递源码分析
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库