美文网首页
android中的IPC方式

android中的IPC方式

作者: 最最最最醉人 | 来源:发表于2017-01-09 11:38 被阅读141次

    使用Bundle

    四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的。
    所以我们在一个进程中启动了另外一个进程的Activity、Service、Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。

    Intent intent = new Intent(BundleFirstActivity.this, BundleSecondActivity.class);
    intent.putExtra("User", new User("haha", 222));
    startActivity(intent);
    

    特殊场景:
    当A进程计算出来了一个结果,需要传输给B进程,但是传输的数据不支持放入Bundle中,因此无法通过Intent来传输。这个时候我们可以通过Intent启动进程B的一个Service组件(比如IntentService),让Service在后台计算,计算完成后再启动B进程中真要需要的目标组件。

    使用文件共享

    共享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。

    需要注意的是并发情况下的读/写,可能造成数据错乱。
    可能有人会想到SharedPreferences,但是SharedPreferences是个特例。从本质上来说,SharedPreferences也属于文件的一种,但是由于系统对它的读/写有一定的缓存策略,即在内存中会有一个SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问,SharedPreferences有很大几率会丢失数据,因此,不建议在进程间通信中使用SharedPreferences。
    每个应用的SharedPreferences文件都可以在当前包所在的data目录下查看到。一般来说,它的目录位于/data/data/package name/share_prefs目录下。

    使用Messenger

    Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。

    由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。正是因为Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适 了。同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了。

    使用AIDL(Android Interface Defined Language(安卓接口定义语言))

    如上所说Messenger无法完成跨进程调用服务端的方法,而且不能处理并发请求。但是我们可以使用AIDL来实现跨进程的方法调用。AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装从而方便上层的调用而已。

    1.服务端
    服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

    2.客户端
    首先需要绑定服务端的Service,然后将服务端返回的Binder对象转成AIDL接口所属类型,最后调用AIDL中的方法。

    3.AIDL接口的创建
    默认情况下,AIDL 支持下列数据类型:

    • Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)
    • String
    • CharSequence
    • List
      List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 可选择将 List 用作“通用”类(例如,List<String>)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
    • Map
      Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。
    • Parcelable
      所有实现了Parcelable接口的对象。

    注意: 如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。

    // Book.aidl
    package com.whyalwaysmea.ipc;
    
    parcelable Book;
    

    4.远程服务端Service的实现

    public class BookManagerService extends Service {
    
        // CopyOnWriteArrayList支持并发读/写
        // 因为AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形
        // 所以我们要在AIDL方法中处理线程同步
        private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
        private Binder mBinder = new IBookManager.Stub(){
    
            @Override
            public List<Book> getBookList() throws RemoteException {
                return mBookList;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                mBookList.add(book);
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
        }
    }
    

    5.客户端的实现
    首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。

    public class BookManagerActivity extends AppCompatActivity {
    
        private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IBookManager bookManager = IBookManager.Stub.asInterface(service);
                try {
                    List<Book> bookList = bookManager.getBookList();
                    System.out.println("book  list type : " + bookList.getClass().getCanonicalName());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_book_manager);
            Intent intent = new Intent(this, BookManagerService.class);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(mConnection);
            super.onDestroy();
        }
    }
    

    可能有人会发现,在Service中,我们返回的数据类型是CopyOnWriteArrayList,但是到了Activity中,接收到了却是List。这是为什么呢?

    AIDL中能够使用的List只有ArrayList,但是我们这里却使用了CopyOnWriteArrayList(注意它并不是继承自ArrayList)。这是因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端。

    RemoteCallbackList

    RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,因为所有的AIDL接口都继承自IInterface接口。

    注意

    客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞,如果这个客户端线程是UI线程的话,就会导致客户端ANR。所以,如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程的耗时方法。

    同理,当远程服务端需要调用客户端的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池。所以,我们同样不可以在服务端中调用客户端的耗时方法。

    Binder是可能意外死亡的,由于服务端进程意外停止了,这时我们需要重新连接服务。

    1. 给Binder设备DeathRecipient监听
    2. 在onServiceDisconnected中重连远程服务。

    两者的区别是:onServiceDisconnected在客户端的UI线程中被回调,而binderDied在客户端的Binder线程池中被回调。

    权限验证

    默认情况下,我们的远程服务任何人都可以连接,但这以你哥哥不是我们愿意看到的,所以我们必须加入权限验证功能。

    1.在onBind中进行验证,验证不通过就直接返回null。比如使用permission验证

    <permission
            android:name="com.whyalwaysmea.permission.ACCESS_BOOK_SERVICE"
            android:protectionLevel="normal" />
    
    public IBinder onbind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.whyalwaysmea.permission.ACCESS_BOOK_SERVICE");
        if(check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mBinder;
    }        
    

    如果我们自己的应用想绑定到服务上,只需要在它的AndroidMenifest文件中采用如下方式使用permission

    <uses-permission android:name="com.whyalwaysmea.permission.ACCESS_BOOK_SERVICE" />
    

    2.我们可以在服务端的onTransact方法中进行权限验证,如果验证失败就返回false,这样服务端就不会终止执行AIDL中的方法从而达到保护客户端的效果。这里可以验证的方式很多,比如permission,Uid,Pid等验证。

    使用ContentProvider

    ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,它的底层实现同样也是Binder。

    系统预置了许多ContentProvider,比如通讯录、日程表等,要跨进程访问这些信息,只需要通过ContentProvider的query、update、insert和delete方法即可。

    关于自定义ContentProvider的过程:

    1.创建一个自定义的ContentProvider,名字就叫BookProvider,只需要继承ContentProvider类并实现六个抽象方法即可:onCreate、query、update、insert、delete和geType。除了onCreate由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在Binder线程池中。

    2.注册ContentProvider。

    <provider
        // ContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider
        android:authorities="com.whyalwaysmea.ipc.provider.BookProvider"
        android:name=".provider.BookProvider"
        android:permission="com.whyalwaysmea.provider"  // 权限
        android:process=":provider"/>
    

    3.通过外部应用访问BookProvider。

    注意

    1. ContentProvider主要以表格的形式来组织数据,并且可以包含多个表;
    2. ContentProvider还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider;
    3. ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行;
    4. 要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者;
    5. query,update,insert,delete四大方法是存在多线程并发访问的,因此方法内部要做好线程同步。

    使用Socket

    Binder连接池

    当项目规模很大的时候,创建很多个Service是不对的做法,因为service是系统资源,太多的service会使得应用看起来很重,所以最好是将所有的AIDL放在同一个Service中去管理。
    整个工作机制是:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。

    选择合适的IPC方式

    选择合适的IPC方式选择合适的IPC方式

    相关文章

      网友评论

          本文标题:android中的IPC方式

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