美文网首页
2.3 IPC基础概念介绍(二)

2.3 IPC基础概念介绍(二)

作者: 武安长空 | 来源:发表于2016-06-15 17:17 被阅读69次

    1. Binder简介

    Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式。Binder还可以理解为一种虚拟的物理设备,其设备驱动是/dev/binder,该通信方式在Linux中没有。从Android Framework(框架层) 角度来说,Binder是ServiceManager连接各种Manager(ActivityManager,WindowManager,等等)和相应的ManagerService的桥梁;从android应用层来讲,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,包括普通服务和AIDL的服务。
    Android开发中,Binder主要用于在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,所以比较简单,无法触及Binder核心,而Messenger(信息员)的底层其实是AIDL,所以这里选择用AIDL来分析Binder的工作机制。

    2. 普通Service实现

    public class BookService extends Service {
        BookBinder bookBinder;
    
        @Override
        public void onCreate() {
            super.onCreate();
            bookBinder = new BookBinder();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return bookBinder;
        }
        // service里的方法,这里是private,对外提供接口BookListenner
        private class BookBinder extends Binder implements BookListnner {
            @Override
            public void bookPrint() {
                Log.e("aaa", "book print");
            }
    
        }
    }
    
    public interface BookListnner {
        void bookPrint();
    }
    

    activity中绑定service并调用service中的方法

    public class BookActivity extends AppCompatActivity {
        BookListnner bookListnner;
        ServiceConnection serviceConnection;
        Intent intent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_book);
            intent = new Intent(this, BookService.class);
            serviceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    // 绑定成功后
                    if (service != null) {
                        bookListnner = (BookListnner) service;
                    }
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
                    // 解绑成功后
                }
            };
            // 绑定服务
            bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
    
            findViewById(R.id.click).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    bookListnner.bookPrint();
                }
            });
    
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
             // 解绑两次也会出错,这里trycatch包一下
            try {
                unbindService(serviceConnection);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 启动服务-startService
      服务生命周期:onCreate
    • 停止服务-stopService
      服务生命周期:onDestroy
    • 绑定服务-bindService
      服务生命周期:onCreate-onBind
    • 解绑服务-unbindService
      服务生命周期:onUnBind-onDestroy
    • 如果只是绑定服务,Activity销毁时要解绑服务。
    • 如果启动服务和绑定服务都执行了,Activity销毁时,也要解绑服务,这时因为服务被startService启动过,不会随着解绑而销毁。除非显性执行stopService,否则一直在后台运行。
    • 服务调用Activity方法使用广播方式。

    3. 编写Book.java,Book.aidl,IBookManager.aidl文件

    public class Book implements Parcelable {
        private int bookId;
        private String bookName;
        // getter setter parcelable...
    }
    
    // Book.aidl
    package qingfengmy.developmentofart._2activity.aidl;
    
    parcelable Book;
    
    
    • Book.aidl文件和Book.java要在同一目录下。
    • 用Android studio新建的aidl要求interface的名字唯一,因为之前已经建过Book.java,所以Book.aidl无法创建,可以先写成IBook.aidl,然后再重命名。
    • 新建的aidl文件是带interface的,注意这里的Book.aidl没有interface声明。
    • Book.aidl是Book类在aidl中的声明,这样别的aidl类就可以引用它。
    // IBookManager.aidl
    package qingfengmy.developmentofart._2activity.aidl;
    
    // Declare any non-default types here with import statements
    import qingfengmy.developmentofart._2activity.aidl.Book;
    interface IBookManager {
         List<Book> getBookList();
         void addBook(in Book book);
    }
    
    • IBookManager.aidl和Book.aidl在同一目录下。
    • IBookManager.aidl引用了Book,尽管同一目录,也要写import语句,否则报错如下:
    > java.lang.RuntimeException:
    com.android.ide.common.process.ProcessException:
    org.gradle.process.internal.ExecException: 
    Process 'command 'F:\android-sdk\build-tools\23.0.3\aidl.exe'' finished with non-zero exit value 1
    
    • Book.aidl没有对应的java文件生成。
    • IBookManager.aidl有对应的java文件生成其路径是
    F:\workspace4sdutio\DevelopmentOfArt
    \app\build\generated\source\aidl\debug
    \qingfengmy\developmentofart\_2activity\aidl\IBookManager.java
    

    4. aidl生成的java文件分析

    public interface IBookManager extends android.os.IInterface {
        //...
    }
    

    IBookManager是个interface,并且实现了IInterface接口。所有可以在Binder中传输的接口都需要继承interface这个接口。

    public java.util.List<qingfengmy.developmentofart._2activity.aidl.Book> getBookList() throws android.os.RemoteException;
    
    public void addBook(qingfengmy.developmentofart._2activity.aidl.Book book) throws android.os.RemoteException;
    

    定义了两个方法,就是我们在aidl中定义的方法。

    static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    

    声明了两个int型的方法ID,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。

    public static abstract class Stub extends android.os.Binder implements qingfengmy.developmentofart._2activity.aidl.IBookManager {
    ...
    }
    

    一个叫做Stub的内部类,这个Stub(存根)就是一个Binder类,当客户端和服务端位于同一进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub内部代理Proxy来完成。

    private static final java.lang.String DESCRIPTOR = "qingfengmy.developmentofart._2activity.aidl.IBookManager";
    

    DESCRIPTOR:Binder的唯一标识,一般用当前Binder的类名表示,比如本例中的"qingfengmy.developmentofart._2activity.aidl.IBookManager";

    public static qingfengmy.developmentofart._2activity.aidl.IBookManager asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof qingfengmy.developmentofart._2activity.aidl.IBookManager))) {
            return ((qingfengmy.developmentofart._2activity.aidl.IBookManager) iin);
        }
        return new qingfengmy.developmentofart._2activity.aidl.IBookManager.Stub.Proxy(obj);
    }
    

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

    @Override
    public android.os.IBinder asBinder() {
        return this;
    }
    

    返回当前的Binder对象,IInterface接口中的唯一方法。

    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_getBookList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<qingfengmy.developmentofart._2activity.aidl.Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                qingfengmy.developmentofart._2activity.aidl.Book _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = qingfengmy.developmentofart._2activity.aidl.Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }
    

    这个方法运行在服务端中的线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法原型为

    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
    

    服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数,然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值。
    如果此方法返回false,那么客户端会请求失败,因此我们可以利用这个特性来做权限验证,毕竟我们不希望随便一个进程都能远程调用我们的服务。

    private static class Proxy implements qingfengmy.developmentofart._2activity.aidl.IBookManager {
        private android.os.IBinder mRemote;
    
        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }
    
        @Override
        public android.os.IBinder asBinder() {
            return mRemote;
        }
    
        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }
    }
    

    Proxy是Stub的内部类,如果是跨进程的话,客户端拿到的Binder会是proxy。这里的方法在客户端调用。

    private static class Proxy implements qingfengmy.developmentofart._2activity.aidl.IBookManager {
    
        @Override
        public java.util.List<qingfengmy.developmentofart._2activity.aidl.Book> getBookList() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<qingfengmy.developmentofart._2activity.aidl.Book> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(qingfengmy.developmentofart._2activity.aidl.Book.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
    }
    

    getBookList方法所需要的输入型Parcel对象_data,输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中;接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。

    @Override
    public void addBook(qingfengmy.developmentofart._2activity.aidl.Book book) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if ((book != null)) {
                _data.writeInt(1);
                book.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }
    

    这个方法也在客户端运行,同上面的方法。只是addBook没有返回值所以不需要从_reply中取结果。
    注意:首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务器进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。

    5. 同一个进程客户端调用服务端

    public class BookService extends Service {
        BookBinder bookBinder;
    
        @Override
        public void onCreate() {
            super.onCreate();
            bookBinder = new BookBinder();
            Log.e("aaa", "onCreate");
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            Log.e("aaa", "onBind");
            return bookBinder;
        }
    
        private class BookBinder extends IBookManager.Stub {
    
            @Override
            public List<Book> getBookList() throws RemoteException {
                Log.e("aaa", "getBookList");
                return null;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                Log.e("aaa", "addBook");
            }
        }
    }
    
    
    public class BookActivity extends AppCompatActivity {
        IBookManager iBookManager;
        ServiceConnection serviceConnection;
        Intent intent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_book);
            intent = new Intent(this, BookService.class);
            serviceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    if (service != null) {
                        iBookManager = IBookManager.Stub.asInterface(service);
                    }
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
    
                }
            };
            
            bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
    
            findViewById(R.id.click).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        iBookManager.getBookList();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 解绑两次也会出错,这里trycatch包一下
            try {
                unbindService(serviceConnection);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    

    6. 客户端和服务端不在一个进程

    首先把BookActivity的进程改成子进程

    <activity android:name="._2activity.BookActivity" android:process=":remote"></activity>
    

    结果正常执行。
    接着改成全局进程。

    <activity android:name="._2activity.BookActivity" android:process="qingfengmy.developmentofart.remote"></activity>
    

    也可以。可能是因为显性绑定服务的原因,这里改成隐式绑定服务。

    <service android:name="._2activity.BookService">
        <intent-filter>
            <action android:name="qingfengmy.developmentofart.bookService"></action>
            <category android:name="android.intent.category.DEFAULT"></category>
        </intent-filter>
    </service>
    
    intent = new Intent();
    intent.setAction("qingfengmy.developmentofart.bookService");
    

    绑服务的时候直接挂掉。报错如下:

    Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit
    

    这里说的是不能隐式启动Service,这是5.0以后加上的,那么我们修改如下:

    intent = new Intent();
    intent.setAction("qingfengmy.developmentofart.bookService");
    intent.setPackage("qingfengmy.developmentofart");
    

    结果无论BookActivity的进程改成什么,都可以成功链接service并调用service的方法。

    7. 跨应用调用该服务

    首先Book和aidl的包路径要和服务里的一样

    qingfengmy.developmentofart._2activity.aidl.Book
    qingfengmy/developmentofart/_2activity/aidl/Book.aidl
    qingfengmy/developmentofart/_2activity/aidl/IBookManager.aidl
    

    其他和同应用的写法一样。原service没做任何其他配置。至于为什么Service没有配置android:exported="true"也可以被其他应用启动。那是因为exported属性,在四大组件没有配置intent-filter时,其值默认为false。在四大组件配置intent-filter时,其值默认为true。也就是上面的实现,隐式启动Service时,exported的值都是true。如果改成false,跨应用将不可调用。而同应用里的不同进程,还是可以启动和绑定Service。

    相关文章

      网友评论

          本文标题:2.3 IPC基础概念介绍(二)

          本文链接:https://www.haomeiwen.com/subject/qtrpdttx.html