Binder、AIDL学习笔记

本文是任玉刚《Android开发艺术探索》的学习笔记,介绍Binder的使用以及上层原理

Android中的多进程模式

在了解Binder之前,需要理解Android的多进程模式,因为Binder是Android的跨进程通信方式。

1、开启多进程模式

通常就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定android:process属性

<service
       android:name=".BookManagerService"
       android:process=":remote" >
</service>

其中,进程名以":"开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“:”开头的进程属于全局进程,其他应用通过ShareUID方法可以和它跑在同一个进程中。

2、开启多进程的注意点

因为Android为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,所以在不同的虚拟机中访问同一个类的对象会产生多份副本。

所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这也是多进程带来的主要影响。

一般来说会造成以下几方面问题:

(1)静态成员和单例模式完全失效。

(2)线程同步机制完全失效。

(3)SharedPreferences的可靠性下降。

(4)Application会多次创建。

什么是Binder

  • 直观上,Binder是Android的一个类,实现IBider借口
  • 从IPC角度,Binder是Android的一种跨进程通信方式
  • 从硬件角度,Binder是一种虚拟的物理设备,设备驱动是/dev/binder
  • 从Framework角度,Binder是ServiceManager连接各种Manager和ManagerService的桥梁
  • 从应用层角度,Binder是客户端和服务端进行通信的媒介。当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据。这里的服务包括普通服务和基于AIDL的服务

通过AIDL分析Binder的工作机制

Android开发中,Binder主要用在Service中,包括AIDL,其中普通Service中的Binder不涉及进程间的通信,所以较为简单,无法触及Binder的核心。

AIDL (Android Interface Definition Language) 是一种IDL 语言,编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程(IPC)的目的。如果需要在一个Activity中, 访问另一个Service中的某个对象, 需要先将对象转化成AIDL可识别的参数(可能是多个参数), 然后使用AIDL来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象。

实例
涉及三个文件:

Book.java 表示图书信息的类

package com.example.zzh.binderdemo;

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

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

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

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

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

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

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

	@Override
	public String toString() {
		return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
	}
}

Book.aidl Book类在AIDL中的声明

package com.example.zzh.binderdemo;
parcelable Book;

IBookManager.aidl 定义的接口

package com.example.zzh.binderdemo;

import com.example.zzh.binderdemo.Book;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}	

编译后,在model工程的build/generated目录下,系统为IBookManager.aidl生成了Binder类IBookManager.java

其中这个类核心内容是内部类Stub和Stub的内部代理类Proxy

Stub是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程;

当二者在不同进程中时,方法调用需要走transact过程,由Proxy来完成。

介绍针对这两个类的买个方法的含义:

  • DESCRIPTOR

    Binder的唯一标识,一般用当前Binder的类名表示,如***.***.IBookManager

  • asInterface(android.os.IBinder obj)

    用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程,
    如果客户端和服务端位于同一进程,那么这个方法返回就是服务端的Stub对象本身,反则返回的是
    系统封装后的Stub.proxy对象。

  • asBinder

    返回当前Binder对象。

  • onTransact

    这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统封装后
    交由此方法来处理。当这个方法返回false时,那么客户端的请求会失败,因此可以利用这个特性来做
    权限验证(避免随便一个进程都能远程调用我们的服务)。

  • Proxy#getBookList Proxy#addBook

    这两个方法运行在客户端中。内部实现是:首先创建该方法所需要的输入型Parcel对象_data、输出型
    Parcel对象_reply和返回值对象List(addBook没有返回值);然后把该方法的参数信息写入_data中
    (如果有参数的话);接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;
    然后服务的onTransaction方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取
    出RPC过程的返回结果;最后返回_reply中的数据。

  • 当客户端发起远程请求时,由于当前线程会挂起直至服务端进程返回数据,所以如果一个远程方法是耗时的,那么不能在UI线程中发起此远程请求。
  • 由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应采用同步的方式去实现,因为它已经运行一个线程中。

Binder的工作机制图

Binder两个重要的方法

Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候与服务端的Binder连接断裂,导致远程调用失败。

linkToDeath unlinkToDeath

给Binder设置一个死亡代理,当Binder死亡时,收到通知,可以重新发起连接请求从而恢复连接:

首先,声明一个DeathRecipient对象。当Binder死亡时候,系统就回调DeathRecipient接口方法binderDied

然后就可以移除之前绑定的Binder代理并重新绑定远程服务。

IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
	@Override
	public void binderDied() {
		if (mRemoteBookManager == null) {
			return;
		}
		mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
		mRemoteBookManager = null;
		
		//TODO 这里重新绑定远程Service
	}
};

其次,在客户端绑定远程Service成功后,给binder设置死亡代理

IBookManager bookManager = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);

另外,通过Binder的方法isBinderAlive也可以判断Binder是否死亡。

基于AIDL的IPC

使用AIDL进行进程间通信的流程
  • 服务端

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

  • 客户端

    首先绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,最后
    在Service中实现这个AIDL接口即可。

AIDL文件支持的数据类型
  • 基本数据类型(int、long、char、boolean等)
  • String和CharSequence
  • List: 只支持ArrayList, 里面每个元素都必须能够被AIDL支持
  • Map: 只支持HashMap, 里面的每个元素都必须被AIDL支持,包括key和value
  • Parcelabe: 所有实现了Parcelable接口的对象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

其中自定义的Parcelable对象和AIDL对象必须要显式import进来,无论是否位于同一个包内。
如果AIDL文件用到了Parcelabl对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为
Parcelable类型。如上个实例中Book.aidl

RemoteCallbackList

如果服务端提供一些监听给客户端,那么我们需要采用RemoteCallbackList,才能实现在客户端取消
注册功能。RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。
其中原理是:虽然跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一
共同点,那就是它们底层的Binder是同一个,利用这个特性(Array<IBinder, Callback>)解决问题。

  • 如果明确知道某个远程方式是耗时的,那么就避免在客户端的UI线程中去访问远程方法。
  • 客户端的onServiceConnected和onServiceDisconnected方法都运行在UI线程中,所以不可以在它们里面调用服务端的耗时方法。
  • 服务端的方法本身运行在Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,不需要在服务端方法中开线程去进行异步任务。
实例

远程服务端Service的实现

public class BookManagerService extends Service {

	private static final String TAG = "BMS";

	private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);

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

	private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList =
		new RemoteCallbackList<>();

	private Binder mBinder = new 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);
		}
	};

	@Override
	public void onCreate() {
		super.onCreate();
		mBookList.add(new Book(1, "Android"));
		mBookList.add(new Book(2, "IOS"));
		Log.d(TAG, "BMS add");
		new Thread(new ServiceWorker()).start();
	}

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

	private void onNewBookArrived(Book book) throws RemoteException {
		mBookList.add(book);

		final int N = mListenerList.beginBroadcast();
		for (int i = 0; i < N; i++) {
		IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
		if (l != null) {
			l.onNewBookArrived(book);
		}
	}
		mListenerList.finishBroadcast();
}

	private class ServiceWorker implements Runnable {
		@Override
		public void run() {
			while (!mIsServiceDestoryed.get()) {
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
				e.printStackTrace();
				}
				int bookId = mBookList.size() + 1;
				Book newBook = new Book(bookId, "new book#" + bookId);
				try {
					onNewBookArrived(newBook);
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

客户端的实现

public class BookManagerActivity extends Activity {

	private static final String TAG = "BookManagerActivity";
	private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

	private IBookManager mRemoteBookManager;

	private Handler mHandler = new Handler(){
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
				case MESSAGE_NEW_BOOK_ARRIVED:
					Log.d(TAG, "receive new book: " + msg.obj);
					break;
				default:
					break;
			}
		}
	};

	private ServiceConnection mConnection = new ServiceConnection() {
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			IBookManager bookManager = IBookManager.Stub.asInterface(service);
			try {
				mRemoteBookManager = bookManager;
				List<Book> list = bookManager.getBookList();
				Log.i(TAG, "onServiceConnected: query book list, list type:" + list.getClass().getCanonicalName());
				Log.i(TAG, "onServiceConnected: query book list:" + list.toString());
				Book newBook = new Book(3, "Android开发艺术探索");
				bookManager.addBook(newBook);
				Log.i(TAG, "add Book:" + newBook);
				List<Book> newList = bookManager.getBookList();
				Log.i(TAG, "onServiceConnected: query book list:" + newList.toString());
				bookManager.registerListener(mOnNewBookArrivedListener);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			mRemoteBookManager = null;
			Log.e(TAG, "binder died.");
		}
	};

	private IOnNewBookArrivedListener mOnNewBookArrivedListener = new
			IOnNewBookArrivedListener.Stub() {

				@Override
				public void onNewBookArrived(Book newBook) throws RemoteException {
					mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
			}
		};


	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_book_manager);
		Intent intent = new Intent(this, BookManagerService.class);
		bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
	}

	@Override
	protected void onDestroy() {
		if (mRemoteBookManager != null
			&& mRemoteBookManager.asBinder().isBinderAlive()) {
			try {
				Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
				mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
		unbindService(mConnection);
		super.onDestroy();
	}
}
在AIDL中使用权限验证功能的方式(保证权限验证成功的一方才能连接远程服务)
  • 在onBind中验证,验证不通过返回null,这样验证失败的客户端直接无法绑定服务。

具体验证方式可以使用permission验证。使用这种验证方式,先在AndroidManifest中声明所需的权限,比如:

<permission
	android:name = "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
	android:protectionLevel="normal">

定义了权限以后,就可以在BookManagerService的onBind方法中做权限验证,如下所示:

public IBinder onBind (Intent intent) {
	int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");

	if(check == PackageManager.PERMISSION_DENIED) {
		teturn null;
	}
	return mBinder;	
} 
  • 在服务端的onTransact方法中进行权限验证,如果验证失败就返回false,这样服务端就不会终止执行AIDL中的方法从而达到保护服务端的效果。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AIDL for HALs是Google在Android 11中引入的一种新的HAL(硬件抽象层)访问方式,旨在代替之前的HIDL。AIDL for HALs的主要优点是稳定性和版本控制。由于AIDL已经存在了很长时间,并且在许多其他地方使用,如Android框架组件之间或应用程序中,因此它具有稳定性支持,并且可以用单一的IPC方式从HAL到框架进程或者应用进程。此外,AIDL还有一个比HIDL更好的版本控制系统。 下面是一个使用AIDL for HALs的简单示例: ```aidl // IMyHalService.aidl package com.example.myhal; interface IMyHalService { int getTemperature(); } ``` ```java // MyHalService.java package com.example.myhal; import android.os.IBinder; import android.os.RemoteException; public class MyHalService extends IMyHalService.Stub { private int mTemperature = 25; @Override public int getTemperature() throws RemoteException { return mTemperature; } public IBinder asBinder() { return this; } } ``` ```java // MyHalServiceManager.java package com.example.myhal; import android.os.ServiceManager; public class MyHalServiceManager { private static final String SERVICE_NAME = "my_hal_service"; public static IMyHalService getService() { IBinder binder = ServiceManager.getService(SERVICE_NAME); if (binder == null) { return null; } return IMyHalService.Stub.asInterface(binder); } } ``` 在这个示例中,我们定义了一个名为IMyHalService的AIDL接口,它有一个getTemperature()方法,用于获取温度。然后我们实现了这个接口,并在MyHalServiceManager中提供了一个静态方法来获取IMyHalService实例。这样,我们就可以在应用程序中使用MyHalServiceManager.getService()方法来获取IMyHalService实例,并调用getTemperature()方法来获取温度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值