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

猜你喜欢

转载自blog.csdn.net/junshangshui/article/details/78554376
今日推荐