(每次学习一点xamarin就做个学习笔记和视频来加深记忆巩固知识)
如有不正确的地方,请帮我指正。
AIDL简介
AIDL(Android Interface Definition Language)翻译为安卓接口定义语言,用于IPC(Inter-Process Communication)进程间通信。
在xamarin学习笔记A08(安卓广播)中使用了Broadcast进行了跨进程通信,但是广播接收器是不允许开启线程的,所以在onReceive()方法中不能进行耗时操作,否则会报错,所以广播一般是用来打开其它组件,例如启动一个Service或者在状态栏创建一个Notification等。
在xamarin学习笔记A11(安卓ContentProvider)中使用ContentProvider实现了跨进程通信,但它主要用于持久化数据的共享。
而AIDL配合Service一起可以让两个进程持续的通信,AIDL效率也比上面的高。
AIDL个人认为
个人认为AIDL语言只是用来帮助生成跨进程通信的代码的,帮助程序员少写代码,帮助程序员生成正确的代码。没有AIDL也可以手写,不过太麻烦。
通信过程
安卓中大部分跨进程通信都是基于Binder机制。Binder翻译出来是“连接器”的意思。由于进程之间不能直接通信,所以需要“连接器”来帮助。
通过下面这张图来简单了解下跨进程通信过程。
如上图所示,例如有一个A19B程序,它提供BookService,允许传入一个bookId来返回给别人一个Book对象。还有一个A19程序,它想访问这个Service,那么大概的流程是这样的:
A. 首先A19B这个程序安装到安卓中后,会在ServiceManage中注册BookService服务。
B. A19这个程序的MainActivity通过this.BindService()来得到IBinder对象,即得到了连接BookService的“连接器”。BindService方法会触发很多内部复杂的操作,比如Binder程序把A19程序传递过来的请求的服务名去SerivceManage中查询对应的服务,然后创建一个指向该服务的“连接器”对象IBinder以供A19使用。
C. 有了“连接器”对象IBinder,就可以通过此连接器来访问远程BookService服务了,可以调用GetBook()方法来得到Book对象了。
那么具体该如何做呢?
1.首先两个程序都新建一个一样的Book类且必须实现了IParcelabler接口。
public class Book : Java.Lang.Object, IParcelable
{
public int BookId { get; set; }
public string BookName { get; set; }
public double BookPrice { get; set; }
public Book()
{
}
//打包数据
public void WriteToParcel(Parcel dest, [GeneratedEnum] ParcelableWriteFlags flags)
{
dest.WriteInt(BookId);
dest.WriteString(BookName);
dest.WriteDouble(BookPrice);
}
public int DescribeContents()
{
return 0; //不用管,填0即可
}
private static readonly MyParcelableCreator<Book> _creator = new MyParcelableCreator<Book>(GetBook);
//解包数据
private static Book GetBook(Parcel parcel)
{
Book book = new Book();
book.BookId = parcel.ReadInt();
book.BookName = parcel.ReadString();
book.BookPrice = parcel.ReadDouble();
return book;
}
[ExportField("CREATOR")]
public static MyParcelableCreator<Book> GetCreator()
{
return _creator;
}
}
2.其次两边都新建一个Book.aidl文件,内容如下
package A19B;
parcelable Book;
这个aidl文件定义了这是个parcelable(可序列化)的Book,它属于A19B这个包。
3.然后两边都新建一个IBook.aidl文件,内容如下
package A19B;
import A19B.Book;
interface IBook
{
Book GetBook(in int bookId);
}
此aild文件定义了一个IBook接口,里面有一个GetBook()方法,因为它使用了Book类,所以得import A19B.Book;。
4.设置这个两个aidl文件的生成操作,如下图
编译项目后会在“obj\Debug\aidl”目录下生成Book.cs,IBook.cs跨进程通信代码。(个人认为生成的Book.cs没啥用,我也没用到)
Book.cs代码如下
// This file is automatically generated and not supposed to be modified.
using System;
using Boolean = System.Boolean;
using String = System.String;
using List = Android.Runtime.JavaList;
using Map = Android.Runtime.JavaDictionary;
namespace A19B
{
}
IBook.cs代码如下
namespace A19B
{
public interface IBook : global::Android.OS.IInterface
{
global::A19B.Book GetBook(int bookId);
}
public abstract class IBookStub : global::Android.OS.Binder, global::Android.OS.IInterface, A19B.IBook
{
const string descriptor = "A19B.IBook";
public IBookStub()
{
this.AttachInterface(this, descriptor);
}
public static A19B.IBook AsInterface(global::Android.OS.IBinder obj)
{
if (obj == null)
return null;
var iin = (global::Android.OS.IInterface)obj.QueryLocalInterface(descriptor);
if (iin != null && iin is A19B.IBook)
return (A19B.IBook)iin;
return new Proxy(obj);
}
public global::Android.OS.IBinder AsBinder()
{
return this;
}
protected override bool OnTransact(int code, global::Android.OS.Parcel data, global::Android.OS.Parcel reply, int flags)
{
switch (code)
{
case global::Android.OS.BinderConsts.InterfaceTransaction:
reply.WriteString(descriptor);
return true;
case TransactionGetBook:
{
data.EnforceInterface(descriptor);
int arg0 = default(int);
arg0 = data.ReadInt();
var result = this.GetBook(arg0);
reply.WriteNoException();
if (result != null) { reply.WriteInt(1); result.WriteToParcel(reply, global::Android.OS.ParcelableWriteFlags.ReturnValue); } else reply.WriteInt(0);
return true;
}
}
return base.OnTransact(code, data, reply, flags);
}
public class Proxy : Java.Lang.Object, A19B.IBook
{
global::Android.OS.IBinder remote;
public Proxy(global::Android.OS.IBinder remote)
{
this.remote = remote;
}
public global::Android.OS.IBinder AsBinder()
{
return remote;
}
public string GetInterfaceDescriptor()
{
return descriptor;
}
public global::A19B.Book GetBook(int bookId)
{
global::Android.OS.Parcel __data = global::Android.OS.Parcel.Obtain();
global::Android.OS.Parcel __reply = global::Android.OS.Parcel.Obtain();
global::A19B.Book __result = default(global::A19B.Book);
try
{
__data.WriteInterfaceToken(descriptor);
__data.WriteInt(bookId);
remote.Transact(IBookStub.TransactionGetBook, __data, __reply, 0);
__reply.ReadException();
__result = __reply.ReadInt() != 0 ? (global::A19B.Book)global::Android.OS.Bundle.Creator.CreateFromParcel(__reply) : null;
}
finally
{
__reply.Recycle();
__data.Recycle();
}
return __result;
}
}
internal const int TransactionGetBook = global::Android.OS.Binder.InterfaceConsts.FirstCallTransaction + 0;
public abstract global::A19B.Book GetBook(int bookId);
}
}
生成的IBookStub这个名字中的Stub很形象,Stub翻译为“存根”,例如,平时我们去寄快递时双方会各留一张存根。
其中特别要注意是的这地方生成的是错的
global::Android.OS.Bundle.Creator.CreateFromParcel(__reply)
应改为
global::A19B.Book.GetCreator().CreateFromParcel(__reply)
我用是VS2017 15.4.0版本,可能在下个新版本会解决掉这个BUG吧。
不过没关系,把这个文件的代码全部复制到剪切版,然后自已新建一个IBook.cs文件,还不行,得先清理解决方案,不然前面自动生成的IBook.cs文件还在,导致不能新建,同时还得把两个aidl文件的生成操作改为“无”。
新建后粘贴代码,修改那句错了的地方,并把这个IBook.cs文件复制到A19项目里,不需做任何修改,命名空间都不要改。如果没有这个BUG,则不需要这么麻烦,两个项目都会根据aidl文件自动生成跨进程通信代码。
这样就完成了跨进程通信代码的生成这一步了。
5.在A19B程序中新建一个BookService服务
[Service(Exported =true, Enabled =true)]
[IntentFilter(new string[] {"com.A19B.BookService"})]
public class BookService : Service
{
public override IBinder OnBind(Intent intent)
{
return new BookBinder();
}
private class BookBinder : IBookStub
{
public override Book GetBook(int bookId)
{
if (bookId == 1)
{
Book book = new Book();
book.BookId = 1;
book.BookName = "C#高级编程";
book.BookPrice = 88.88;
return book;
}
else return null;
}
}
}
这里定义了一个BookBinder类来继承生成的抽象类IBookStub,实现GetBook就可以了。然后在OnBind()方法中实例化此类的对象返回给外部使用。
6.在A19项目的MainActivity中访问BookService,上主要代码
public class MainActivity : AppCompatActivity
{
private TextView _textView;
private BookServiceConnection conn;
//省略其它代码
private void BindRemoteService()//绑定远程服务
{
Intent intent = new Intent("com.A19B.BookService");
if (intent != null)
{
if (conn == null)
{
conn = new BookServiceConnection();
}
this.BindService(intent, conn, Bind.AutoCreate);
}
}
private void InvokeRemoteService()//调用远程服务
{
if (conn != null)
{
Book book = conn.IBook.GetBook(1);
if(book!=null)
_textView.Text = string.Format("BookId={0} BookName={1} BookPrice={2}", book.BookId, book.BookName, book.BookPrice);
}
}
}
public class BookServiceConnection : Java.Lang.Object, IServiceConnection
{
public IBook IBook { get; private set; }
public void OnServiceConnected(ComponentName name, IBinder service)
{
IBook = IBookStub.AsInterface(service);
}
}
}
需要定义一个BookServiceConnection类并实现IServiceConnection接口,因为在调用this.BindService()方法时需要一个实现了此接口的对象,成功绑定远程服务后会回调OnServiceConnected方法把“连接器”对象IBinder传过来。
因为“连接器”对象实现了IBook接口,所以可以通过AsInterface转换,然后存到public IBook IBook变量中以供使用。
最后通过Book book = conn.IBook.GetBook(1);调用了远程服务获得了Book对象。表面上看像是直接调用了BookSerive服务,实际内部是通过Binder驱动来调用的。
需要注意
在Android5.0及以上系统中需要显式声明Intent才能启动Service。
//从隐式声明的Intent中创建一个显式声明的Intent(在Android5.0及以上系统中需要显式声明Intent才能启动Service)
private Intent CreateExplicitFromImplicitIntent(Context context, Intent implicitIntent)
{
PackageManager pm = context.PackageManager;
//查出所有的能匹配这个隐式意图的服务列表
IList<ResolveInfo> resolveInfo = pm.QueryIntentServices(implicitIntent, 0);
if (resolveInfo == null || resolveInfo.Count != 1)
{
return null;
}
ResolveInfo serviceInfo = resolveInfo[0];
string packageName = serviceInfo.ServiceInfo.PackageName;//取出包名
string className = serviceInfo.ServiceInfo.Name;//取出服务名
ComponentName component = new ComponentName(packageName, className);//用包名和服务名来创建一个ComponentName对象
//拿隐式意图对象implicitIntent作为构造参数,来创建一个新的显示的意图
Intent explicitIntent = new Intent(implicitIntent);
explicitIntent.SetComponent(component);//设置显示意图的组件名对象
return explicitIntent;
}
private void BindRemoteService()//绑定远程服务
{
Intent intent = new Intent("com.A19B.BookService");
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)//(在Android5.0及以上系统中需要显式声明Intent才能启动Service)
{
intent = CreateExplicitFromImplicitIntent(this, intent);
}
if (_bookServiceConnection == null)
{
_bookServiceConnection = new BookServiceConnection();
}
this.BindService(intent, _bookServiceConnection, Bind.AutoCreate);
Toast.MakeText(this,"绑定远程服务", ToastLength.Short).Show();
}
代码和视频在我上传的CSDN资源中http://download.csdn.net/download/junshangshui/10121804