Binder学习资料整理

来源:互联网 发布:anthrax知乎 编辑:程序博客网 时间:2024/06/07 07:03

Android正常的应用程序里只有一个进程,当然如果有需求是可以一个应用开多个进程的,那就涉及到多进程开发。
首先看下如何开启多进程:
例如某个Activity需要开启多进程,则需要在AndroidManifest.xml中的activity节点中增加android:process属性,例如

<application    android:allowBackup="true"    android:label="@string/app_name"    android:theme="@style/AppBaseTheme" >    <activity        android:name=".FirstActivity"        android:label="@string/app_name" >        <intent-filter>            <action android:name="android.intent.action.MAIN" />            <category android:name="android.intent.category.LAUNCHER" />        </intent-filter>    </activity>    <activity android:name=".SecondActivity"        android:process=":remote"/>    <activity android:name=".ThirdActivity"        android:process="com.example.caroline.remote"/></application>

上面的例子中,当进入应用程序时会进入FirstActivity,在默认的进程中,当打开SecondActivity时,该Activity会在:remote进程中运行,打开ThirdActivity时,该activity会在com.example.caroline.remote中运行。这个里要说明:remote,”:”的含义是在当前进程名前加上包名,所以该进程的全名是com.example.caroline:remote,所以:remote是一种简单的写法,并且这种写法的进程都属于该应用的私有进程,其他的应用组件不可以和他跑在同一个进程中。而不以”:”开头的进程,属于全局进程,其他应用通过shareUID的方法,可以和他跑在一个进程中。

多进程引发的问题

说到多进程就会有一个问题,数据问题。例如对于类的静态变量来说,在一个进程中是全局的,如果在一个进程中的任意地方被修改,那么该进程中所有地方访问到的都是被修改的。但是对于多进程的话,因为每个进程的堆栈不一样,所有这样一个静态变量在多个进程中都会有一份,并且互相不干扰。也就是说在进程A中对静态变量的修改,在进程B中静态变量无变化。但是需要注意的是堆栈是内存中的概念,也就是说多进程中访问内存的话,读到的数据是不一样的,所以如何共享是一个问题!

多进程还会带来以下的问题:

  • 静态成员和单例模式失效
  • 线程同步机制失效
  • SharedPreference的可靠性下降,因为sharedPreference不支持两个进程同时读写。而且系统对sharedPreference的读写有一定的缓存策略,即在内存中会缓存一份sharedPreference文件。所以多进程下是不靠谱的。
  • Application会多次创建:当一个组件跑在一个新的进程中的时候,由于系统要在创建新的进程同时分配独立的虚拟机,所以相当于是启动一个新的应用程序,所以Application会重新创建。

这里写图片描述

虽然不能直接共享内存,但是通过跨进程通信(IPC: Inter-Process Communication),还是可以实现数据交互的。跨进程通信方法很多:
- 通过Intent来传递数据,通过bundle等。
- 共享文件
- 基于Binder的Message和AIDL以及Socket

Serializable && Parcelable

如果通过Binder来跨进程通信的话,如何传递数据(object)是一个问题,需要将数据序列化,那序列化有两个接口,分别是Parcelable和Serializable。

Serializable是Java提供的一个序列化接口,是空接口,为对象提供标准的序列化和反序列化操作。示例实现如下:

public class TestObject implements Serializable{    private static final long serialVersionUID = 8711368828010083044L;

在类中指定序列化号是为了防止类成员变量的变化导致反序列化失败。

Parcelable是Android中的序列化方式,android中有些类已经实现该接口,例如intent,bundle,bitmap等。

Serializable和Parcelable都能实现序列化并且都可用于intent间的数据传递。Serializable是Java中的序列化接口,其使用简单但是开销大,序列化和反序列化需要进行大量的io操作。而parcelable更适用于在Android平台上,缺点就是使用起来比较麻烦,但是效率高,它主要用在内存序列化上,通过Parcelable讲对象序列化到设备或在网络中传输也都可以,但是过程会稍显复杂,因此序列化到设备和网络传输建议使用Serializable。

AIDL && Messenger

AIDL可以跨进程访问其他应用程序,和其他应用程序通讯,那我告诉你,很多技术都可以访问,如广播(应用A在AndroidManifest.xml中注册指定Action的广播)应用B发送指定Action的广播,A就能收到信息,这样也能看成不同应用之间完成了通讯(但是这种通讯是单向的);还如ContentProvider,通过URI接口暴露数据给其他应用访问;但是这种都算不上是应用之间的通讯。

应用层的进程间通信无非Broadcast,Activity,Service,Content Provider四大组件。Broadcast适用于一对多,我这里是一对一(子进程与主进程)的关系,且Broadcast性能较差,所以不用Broadcast。另外Content Provider主要用户本地化数据的通信,我这里主要是内存级别的数据,所以也不用Content Provider。Activity使用Intent进行进程间通信,使用的时候还需要start Activity,且不能连续通信,不能满足需求。剩下的只有Service了,而Service也恰恰是最合适的人选。

使用Service进行进程间通信,一种方法是start Service,这和start Activty一样,不用这种方法。另一种方法是bind service,这种可以连续通信,我们采用这种方式来进行进程间通信。

说到bind service进行进程间通信,大家都会想到AIDL(android interface Definition Language)和Messenger,那它们两个到底有什么异同,哪个更好用,更方便,效率更高呢。可能最让人迷惑的是Android推出来了Messager,它就是完成应用之间的通讯的。那么为什么还要有AIDL呢,官方文档介绍AIDL中有这么一句话:
Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.
第一句最重要,“只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL”,其他情况下你都可以选择其他方法,如使用Messenger,也能跨进程通讯。可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。还是官方文档说的明白,一句话就可以理解为什么要有AIDL。如果选择使用AIDL的话,那么客户端还要处理多线程的问题。

Messenger

看下使用Messenger的例子:

public class RemoteService extends Service {    private Messenger messenger;    public RemoteService() {        messenger = new Messenger(new Handler() {            @Override            public void handleMessage(Message msg) {                Log.d("tag", "RemoteService.handleMessage:" + msg.getData().getString("msg"));                Messenger remoteMessenger = msg.replyTo;                Message msg2 = Message.obtain(null, 1);                Bundle bundle = new Bundle();                bundle.putString("msg", "hi, the message from service.");                msg2.setData(bundle);                try {                    remoteMessenger.send(msg2);                } catch (RemoteException e) {                    e.printStackTrace();                }            }        });    }    @Override    public void onCreate() {        super.onCreate();        Log.d("tag", "onCreate");    }    @Override    public IBinder onBind(Intent intent) {        Log.d("tag", "onBind");        return messenger.getBinder();    }}public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void onClick(View v) {        Intent intent = new Intent(MainActivity.this, RemoteService.class);        bindService(intent, connection, Context.BIND_AUTO_CREATE);    }    private Messenger messenger = new Messenger(new Handler() {        @Override        public void handleMessage(Message msg) {            Log.d("tag", "client.handleMessage:" + msg.getData().getString("msg"));        }    });    private Messenger serviceMessenger;    ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {            serviceMessenger = new Messenger(iBinder);            Toast.makeText(MainActivity.this, "onServiceConnected", Toast.LENGTH_LONG).show();            try {                Message msg = Message.obtain(null, 1);                Bundle data = new Bundle();                data.putString("msg", "hi, the message from client.");                msg.setData(data);                msg.replyTo = messenger;                serviceMessenger.send(msg);            } catch (RemoteException e) {                e.printStackTrace();            }        }        @Override        public void onServiceDisconnected(ComponentName componentName) {            Toast.makeText(MainActivity.this, "onServiceConnected", Toast.LENGTH_LONG).show();        }    };    @Override    protected void onDestroy() {        super.onDestroy();        unbindService(connection);    }}

Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能是一个个处理,如果有大量的请求,Messenger就不合适了。同时Messenger是用来传递消息的,但有时候客户端需要跨进程去调用服务端的方法,这种情况Messenger就无法满足需求,所以还是需要AIDL。而且Messenger的底层实现也是AIDL。

AIDL

所有的AIDL接口都继承自IInterface接口。
注意事项:

  • 另外需要注意的是,当activity绑定service的时候,如果service和activity在一个进程中,那么service和activity就是在一个线程中,所以在service中不要做耗时的操作,以免主线程阻塞,出现ANR。客户端调用远程服务的方法,被调用的方法运行在服务端的binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时的话,就会导致客户端线程长时间的阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,因此如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去调用。由于客户端的onServiceConnected和onServiceDisconnected方法都是在UI线程中,所以不能在这两个方法中调用service耗时方法。
  • 如果service端和client端是在同一个进程的话,那么在两端之间传输的对象都是同一个,也就是说发出端的对象传到接收端还是它自己。如果是多进程的话,那么发出端的对象传到接收端,就会创建一个一模一样的对象,接收端的对象在接收端的内存中,发送端的对象在发送端的内存中,所以这是两个对象。设想一种情况,多进程使用AIDL的情况下如果在service端维护一个列表List<Book> books。然后在client端生成一个Book的实例(书名叫book1)传给service,并加入到books中,那么按理来说此时service的books中应该有一本书,并且书名也叫book1。然后此时client再把刚刚生成的book实例传给后台,判断service中的books是否存在这本书,books.contains(book),然而会发现返回的是false,并不存在这本书。这里会觉得很奇怪,明明刚刚已经添加那本书,怎么可能拿不到。这其实是因为第一次client端将book1传给service的时候,底层创建了一个新的book2,只是book2和book1的书名一样,但其实是两个对象了。所以service的books中持有的其实是book2,然后第二次client端将book1传给service的时候,其实service拿到的是book3,然后通过book3区去books中找,当然是找不到了,因为list的contains方法是调用对象的equals方法,来判断两个对象是否一样。book的父类是object,其默认的equals方法是比较两者内存地址是否一样,所以在contains的时候,会发现内存地址不一样,而返回false。哪有什么办法解决呢?办法当然还是有的啦。就是刚刚说的equals方法。可以重写equals方法,通过比较两本书的bookName是否一样,如果bookName一样,则说明是一本书,不一样则是不一样的书。如果在service端维护的是IInterface实现类的列表话,可以使用RemoteCallbackList<? implements IInterface> interfaces,具体请自行上网查找。

AIDL中需要传递的数据对象,需按照下面的方式:
这里写图片描述
以Book为例,如果Book是需要传输的数据类型,那么首先要创建Book.java,Book类实现Parcelable接口。然后创建Book.aidl文件,在文件中只需要添加parcelable Book;
然后再创建你需要的IInterface接口,例如图中的AIDLInterface接口,如果该接口中用到Book这个类。那么需要导入Book。虽然Book.aidl和AIDLInterface.aidl是在一个package中,但是还需要导入。
如果在AIDLInterface接口中有一个函数,例如

void addBook(in Book b);

记得一定要在Book前加上对该参数的修饰,有三种修饰,分别是in,out,inout。这三种根据你的需求而定。当然三种修饰符它底层付出的代价是不一样的。

需要注意到是Binder是有可能意外死亡的,这往往是由于服务端的进程意外停止了,这时我们需要重新连接服务,有两种办法

  • 给Binder设置DeathRecipient监听,当Binder死亡时,我们会受到BinderDied方法的回调,在BinderDied方法中我们可以重连远程服务。
  • 在onServiceDisconnected方法中重连远程服务。

其他IPC方式,此处不赘述:

  • ContentProvider
  • Socket
0 0
原创粉丝点击