(每次學習一點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