xamarin學習筆記A19(安卓AIDL)

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

發佈了74 篇原創文章 · 獲贊 50 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章