1) 应用某些模块因为特殊需求需要运行在单独进程中。如消息推送,使消息推送进程与应用进程能单独存活,消息推送进程不会因为应用程序进程 crash 而受影响。 2) 为加大一个应用可使用的内存,需要多进程来获取多份内存空间。
给四大组件(Activity、Service、Receiver、ContentProvider)在 AndroidMainfest 中指定 android:process 属性指定。
(1) 静态成员和单例失效,数据同步失败; Android 会为每一个应用 / 每一个进程分配一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,这就导致不同虚拟机中访问同一个类对象会产生多个副本。 (2) 线程同步机制失效; 因为不同进程不是同一块内存,不同进程锁的不是同一对象。 (3) SharedPreferences 可靠性下降; SharedPreferences 底层是通过读 / 写 XML 文件实现的,并发写可能会出问题,所以它不支持多个进程同时去执行写操作,否则会导致一定几率的数据丢失。 (4) Application 会创建多次; 当一个组件跑在一个新进程中,系统会给它重新分配独立虚拟机,这其实就是启动一个应用的过程,故运行在不同进程中的组件属于不同的虚拟机和不同的 Application。
Intent 和 Binder 传输数据时,或是对象持久化转存 / 通过网络传输给其他客户端时,需要使用 Parcelable 或 Serializable 将对象转换成可以传输的形式。 (1) Serializable 接口 Serializable 是 Java 提供的一个序列化接口,它是一个空接口,想要某个类实现序列化,只需要相应类实现 Serializable 接口即可。Serializable 是借助 ObjectOutputStream 和 ObjectInputStream 实现对象的序列化和反序列化 一般在相应类实现 Serializable 类中还会定义一个 final long 型的 serialVersionUID,不用它也能实现对象的序列化,它是用来辅助反序列化的。序列化时会把当前类的 serialVersionUID 写入序列化文件中,当反序列化系统会检测文件中的 serialVersionUID,看它是否和当前类的 serialVersionUID 一致,如果一致说明序列化的类的版本和当前类的版本相同,这时可反序化成功;否则说明当前类和序列化的类相比发生了变换,如增加或减少某个成员变量,这时无法正常反序列化。 (2) Parcelable 接口 在序列化过程中需要实现的功能有: 1) 序列化:由 writeToParcel 方法完成,最终通过 Parcel 中的一系列的 write 方法完成; 2) 反序列化:由 CREATOR 完成,内部标识了如何创建序列化对象和数组,最终通过 Parcel 的一系列 read 方法来完成反序列化; 3) 内容描述符:由 describeContents 方法来完成,几乎所有情况下该方法都返回 0, 仅当当前对象中存在文件描述符时返回 1。 (3) 两者区别 Serializable 是 Java 中序列化接口,使用简单但开销大,序列和反序列化需要大量 I/O 操作; Parcelable 是 Android 中特有的序列化接口,使用复杂但开销小,效率高,是 Android 推荐的序列化方法; Parcelable 主要用在内存序列化上, 如果将对象序列化到存储设备或将对象序列化后通过网络传输,过程会比较复杂,建议使用 Serializable。
Binder 是什么?
在一个进程中启动另一个进程的 Activity, Service, Receiver 组件时,可以使用 Bundle 附加上需要传递的消息给远程进程,并通过 Intent 发送出去。
两个进程通过读 / 写同一个文件来交换数据,还可以序列化一个对象到文件系统中,从另一个进程中恢复这个对象。它的实现原理是通过 ObjectOutputStream 将文件写入文件中,再通过 ObjectInputSteam 将文件恢复。通过文件共享的方式有一定局限性,如并发读 / 写,读出的内容可能不是最新的,并发写就可能导致数据混乱。因此尽量避免并发写这种操作,或考虑用线程同步来限制多个线程的写操作。文件共享适合在对数据要求不高的进程之间通信。 SharedPreferences 是 Android 提供的一种轻量级存储方案,它通过键值对方式存储数据,底层它是采用 XML 来存储键值对。由于系统对 SharedPreferences 的读写有一定的缓存策略,即在内存中会有一份 SharedPreferences 文件的缓存,在多进程模式下,系统对它的读 / 写就变得不可靠,当面对高并发读 / 写访问时,SharedPreferences 会有很大几率会丢失数据。因此不建议在进程间通信中使用 SharedPreferences。
Messenager 可以在不同进程中传递 Message 对象,在 Message 中放入我们要传递的数据。它的底层实现是 AIDL,下面是 Messenger 的两个构造方法:
- public Messenger(Handler target) {
- mTarget = target.getIMessenger();
- }public Messenger(IBinder target) {
- mTarget = IMessenger.Stub.asInterface(target);
- }
不管是 IMessenger 还是 Stub.asInterface,这种使用方法都表明它的底层是 AIDL,它一次只处理一个请求,不存在线程同步问题。 实现步骤: (1) 服务端进程 1) 定义一个 Service 用于客户端的绑定,建立一个 Handler,在 handleMessage 里处理客户端发送过来的消息。
- //用ServiceHandler接收并处理来自于客户端的消息
- private class ServiceHandler extends Handler{
- @Override
- public void handleMessage(Message msg) {if(msg.what == RECEIVE_MESSAGE_CODE){
- Bundle data = msg.getData();if(data !=null){
- String str = data.getString("msg");
- }//通过Message的replyTo获取到客户端自身的Messenger,
- //Service可以通过它向客户端发送消息clientMessenger = msg.replyTo;if(clientMessenger !=null){
- Message msgToClient = Message.obtain();
- msgToClient.what = SEND_MESSAGE_CODE;//可以通过Bundle发送跨进程的信息Bundle bundle =newBundle();
- bundle.putString("msg","你好,客户端,我是MyService");
- msgToClient.setData(bundle);try{
- clientMessenger.send(msgToClient);
- }catch(RemoteException e){
- e.printStackTrace();
- Log.e("DemoLog","向客户端发送信息失败: "+ e.getMessage());
- }
- }
- }
- }
- }
2) 通过 Handler 创建一个 Messenger 对象
- //serviceMessenger是Service自身的Messenger,其内部指向了ServiceHandler的实例
- //客户端可以通过IBinder构建Service端的Messenger,从而向Service发送消息,
- //并由ServiceHandler接收并处理来自于客户端的消息
- privateMessenger serviceMessenger =newMessenger(newServiceHandler());
3) 在 Service 的 onBind 中通过 Messenger.getBinder() 返回底层的 Binder 对象。
- @Override
- publicIBinderonBind(Intent intent) {
- Log.i("DemoLog","MyServivce -> onBind");//获取Service自身Messenger所对应的IBinder,并将其发送共享给所有客户端
- returnserviceMessenger.getBinder();
- }
4) 注册服务,让其运行在单独进程中
- ".MyService"android:enabled="true"android:process=":remote">
(2) 客户端进程 1) 绑定服务端的 Service
- privateServiceConnection conn =newServiceConnection() {@Override
- public void onServiceConnected(ComponentName name, IBinder binder) {//客户端与Service建立连接Log.i("DemoLog","客户端 onServiceConnected");//我们可以通过从Service的onBind方法中返回的IBinder初始化一个指向Service端的MessengerserviceMessenger =newMessenger(binder);
- isBound =true;
- Message msg = Message.obtain();
- msg.what = SEND_MESSAGE_CODE;//此处跨进程Message通信不能将msg.obj设置为non-Parcelable的对象,应该使用Bundle
- //msg.obj = "你好,MyService,我是客户端";Bundle data =newBundle();
- data.putString("msg","你好,MyService,我是客户端");
- msg.setData(data);//需要将Message的replyTo设置为客户端的clientMessenger,
- //以便Service可以通过它向客户端发送消息msg.replyTo = clientMessenger;try{
- Log.i("DemoLog","客户端向service发送信息");
- serviceMessenger.send(msg);
- }catch(RemoteException e) {
- e.printStackTrace();
- Log.i("DemoLog","客户端向service发送消息失败: "+ e.getMessage());
- }
- }@Override
- public void onServiceDisconnected(ComponentName name) {//客户端与Service失去连接serviceMessenger =null;
- isBound =false;
- Log.i("DemoLog","客户端 onServiceDisconnected");
- }
- };
- Intent intent =newIntent();
- ComponentName componentName =newComponentName(packageName, serviceNmae);
- intent.setComponent(componentName);try{
- Log.i("DemoLog","客户端调用bindService方法");
- bindService(intent, conn, BIND_AUTO_CREATE);
- }catch(Exception e){
- e.printStackTrace();
- Log.e("DemoLog", e.getMessage());
- }
- }
绑定成功后用服务端返回的 IBinder 对象创建一个 Messenger,通过它向服务端发送 Message 消息。 如果需要服务端给客户端发送消息,需要在 Handler 的 handleMessage 方法里,根据客户端发送过来的 Message.replyTo 获取到客户端的 Messenger 对象,就可以向客户端发送消息了。同时客户端需要在服务连接的 onServiceConnected 方法中,将客户端的 Messenger 对象通过 Message.replyTo 给收到服务端发送过来的 Message 对象,这样就实现双方通信。
(1) 先新建一个 Book.java 实现 Parcelable 接口,表示一个图书的信息类;
- importandroid.os.Parcel;importandroid.os.Parcelable;/**
- * Book.java
- */
- public class Book implements Parcelable{
- privateString name;private intprice;public Book(){}publicStringgetName() {returnname;
- }public void setName(String name) {this.name = name;
- }public int getPrice() {returnprice;
- }public void setPrice(intprice) {this.price = price;
- }public Book(Parcel in) {
- name = in.readString();
- price = in.readInt();
- }public static finalCreator CREATOR =newCreator() {@Override
- publicBookcreateFromParcel(Parcel in) {return newBook(in);
- }@Override
- publicBook[]newArray(intsize) {return newBook[size];
- }
- };@Override
- public int describeContents() {return 0;
- }@Override
- public void writeToParcel(Parcel dest,intflags) {
- dest.writeString(name);
- dest.writeInt(price);
- }/**
- * 参数是一个Parcel,用它来存储与传输数据
- * @paramdest
- */
- public void readFromParcel(Parcel dest) {//注意,此处的读值顺序应当是和writeToParcel()方法中一致的name = dest.readString();
- price = dest.readInt();
- }//方便打印数据
- @Override
- publicStringtoString() {return "name : "+ name +" , price : "+ price;
- }
- }
(2) 新建一个 Book.aidl,表示 Book 类在 AIDL 中的声明;
- // Book.aidl
- //这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
- //注意:Book.aidl与Book.java的包名应当是一样的
- //注意parcelable是小写parcelable Book;
(3) 新建一个 IBookManger.aidl 接口,里面包含相应的方法;
- // IBookManger.aidl
- //导入所需要使用的非默认支持数据类型的包
- importcom.lypeer.ipcclient.Book;
- interface IBookManger {//所有的返回值前都不需要加任何东西,不管是什么数据类型List getBooks();
- Book getBook();
- }
该方法保存后,会在 gen 目录下生成一个 IBookManger.java 类,它是系统为 BookManger.aidl 生成的 Binder 类,继承 android.os.IInterface,它自己也还是个接口。 它包括以下内容: 1) 定义了一个 String 型的 DESCRIPTOR,Binder 的唯一标识; 2) asInterface(android.os.IBinder obj) 方法,将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型对象,当客户端和服务端处于同一进程,直接返回服务端的 Stub 对象本身,否则返回系统封装后的 Stub.proxy 对象。 3) asBinder 方法:返回当前 Binder 对象。 4) onTransact 方法:运行在服务端的 Binder 线程池中,当客户端发起远程请求,远程请求会通过系统封装后交由此方法处理,该方法原型为 public Boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags), 服务端通过 code 可以确定客户端请求的是哪一个方法,从 data 中取出目标方法所需的参数,执行相应方法后,将返回值写入 reply 中,如果此方法返回 false, 客户端会请求失败。 5) IBookManger.aidl 接口中声明的方法的代理实现,此方法运行在客户端,当客户端调用此方法时,首先需要创建此方法需要的输入类型 Parcel 对象_data, 输出类型 Parcel 对象_reply 和返回值对象,接着将参数信息写入_data 中(若有参数的话),再调用 transact 方法发起 RPC(远程过程调用),当前线程挂起,服务端的 onTransact 方法会被调用,直到 RPC 过程返回后,当前线程继续执行,并从_reply 中取出 RPC 过程的返回结果;最后返回_reply 中的数据。 6) 声明了 IBookManger.aidl 中声明的方法; 7) 声明了相应的整型 id 分别用于标识声明的方法,该 id 用于标识在 transact 过程中判断客户端请求的到底是哪个方法; 8) 声明了一个类部类 Stub,继承 android.os.Binder 实现 IBookManager.aidl 中类接口,当客户端和服务端处于同一进程,方法调用不会走跨进程的 transact 过程,若位于不同进程,则由 Stub 的内部代理 Proxy,调用跨进程 transact 过程。 需要注意的是:客户端发起请求时,当前线程会挂起直至服务端返回数据,若方法是一个耗时操作,不能在 UI 线程中发起此远程请求。服务端的 Binder 方法运行在 Binder 线程池中,所以不管操作是否耗时,都应采用同步的方法去实现。 (4)linkToDeath unlinkToDeath 由于 Binder 运行在服务端进程中,如果服务端进程由于某种原因终止,这时客户端服务端的 Binder 连接断裂,会导致远程调用失败。但客户端很可能不知道,解决此问题的办法,利用 Binder 提供的两个配对方法 linkToDeath 和 unlinkToDeath,通过 linkToDeath 可以给 Binder 设置一个死亡代理,当 Binder 被终止时,我们会接收到通知,这时可通过重新发起请求恢复连接。 实现方法: 1) 先声明一个 DeathRecipient 对象,它是一个接口,内部只有一个方法 binderDied, 我们需要实现该方法,当 Binder 死亡时,系统回调 binderDied 方法,我们可以移出之前 binder 代理并重新建立远程服务。
- privateIBinder.DeathRecipient mDeathRecipient =newIBinder.DeathRecipient() {@Override
- public void binderDied() {if(mBookManager ==null) {return;
- }
- mBookManger.asBinder().unlinkToDeath(mDeathRecipient,0);
- mBookManger =null;//重新绑定远程服务}
- };
2) 在客户端绑定远程服务成功后,给 binder 设置死亡代理;
- mService = IBookManager.Stub.asInterface(binder);
- binder.linkToDeath(mDeathRecipient,0);
(1)Serivce 建立 我们需要新建一个 Service,称为 AIDLService,代码如下:
- /**
- 1. 服务端的AIDLService.java
- */
- public class AIDLService extends Service{
- public finalString TAG =this.getClass().getSimpleName();//包含Book对象的list
- privateList mBooks =newArrayList<>();//由AIDL文件生成的IBookManager
- private finalIBookManager.Stub mBookManager =newIBookManager.Stub() {@Override
- publicListgetBooks()throwsRemoteException {synchronized(this) {
- Log.e(TAG,"invoking getBooks() method , now the list is : "+ mBooks.toString());if(mBooks !=null) {returnmBooks;
- }return newArrayList<>();
- }
- }@Override
- public void addBook(Book book)throwsRemoteException {synchronized(this) {if(mBooks ==null) {
- mBooks =newArrayList<>();
- }if(book ==null) {
- Log.e(TAG,"Book is null in In");
- book =newBook();
- }//尝试修改book的参数,主要是为了观察其到客户端的反馈book.setPrice(2333);if(!mBooks.contains(book)) {
- mBooks.add(book);
- }//打印mBooks列表,观察客户端传过来的值Log.e(TAG,"invoking addBooks() method , now the list is : "+ mBooks.toString());
- }
- }
- };@Override
- public void onCreate() {super.onCreate();
- Book book =newBook();
- book.setName("Android开发艺术探索");
- book.setPrice(28);
- mBooks.add(book);
- }@Nullable
- @Override
- publicIBinderonBind(Intent intent) {
- Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));returnmBookManager;//在onBinder中返回服务端的Binder对象}
- }
(2) 注册服务
- "aidl.AIDLService"android:process=":remote">
- /**
- 2. 客户端的AIDLActivity.java
- */
- public class AIDLActivity extends AppCompatActivity{
- //由AIDL文件生成的Java类
- privateIBookManager mBookManager =null;//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
- private booleanmBound =false;//包含Book对象的list
- privateList mBooks;@Override
- protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_aidl);
- }/**
- * 按钮的点击事件,点击之后调用服务端的addBookIn方法
- *
- * @paramview
- */
- public void addBook(View view) {//如果与服务端的连接处于未连接状态,则尝试连接
- if(!mBound) {
- attemptToBindService();
- Toast.makeText(this,"当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();return;
- }if(mBookManager ==null)return;
- Book book =newBook();
- book.setName("APP研发录In");
- book.setPrice(30);try{
- mBookManager.addBook(book);//通过服务端的Binder对象调服务端方法Log.e(getLocalClassName(), book.toString());
- }catch(RemoteException e) {
- e.printStackTrace();
- }
- }/**
- * 尝试与服务端建立连接
- */
- private void attemptToBindService() {
- Intent intent =newIntent();
- intent.setAction("aidl.AIDLService");
- intent.setPackage("com.xx.packagename");
- bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);//绑定服务}@Override
- protected void onStart() {super.onStart();if(!mBound) {
- attemptToBindService();
- }
- }@Override
- protected void onStop() {super.onStop();if(mBound) {
- unbindService(mServiceConnection);
- mBound =false;
- }
- }privateServiceConnection mServiceConnection =newServiceConnection() {@Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.e(getLocalClassName(),"service connected");
- mBookManager = IBookManager.Stub.asInterface(service);
- mBound =true;if(mBookManager !=null) {try{
- mBooks = mBookManager.getBooks();//调用服务端的getBooks方法Log.e(getLocalClassName(), mBooks.toString());
- }catch(RemoteException e) {
- e.printStackTrace();
- }
- }
- }@Override
- public void onServiceDisconnected(ComponentName name) {
- Log.e(getLocalClassName(),"service disconnected");
- mBound =false;
- }
- };
- }
通过获取服务端的 Binder 对象,就可以和服务端进行通信了。
(1) 跨进程 listener 接口 如果在服务端定声明了接口,客户端进行注册和反注册,会抛出异常,无法反注册,因为在多进程中,Binder 会把客户端传过来的对象重新转化成一个新对象,这样虽然客户端注册和反注册使用的是同一个对象,但是通过 Binder 传递到服务端却变成两个对象,对象的跨进程传输本质都是反序列化过程。 可以使用 RemoteCallbackList,它是系统专门用来删除跨进程 listener 接口。它实现原理利用底层 Binder 对象是同一个,只要遍历服务端所有 listener, 找出和解注册 listener 具有相同 Binder 对象的服务端 listener,并把它删除掉。同时 RemoteCallbackList 还有一个作用,当客户端进程终止,它会移除客户端所注册的所有 listener. (2) 权限验证 在注册服务时添加 permission 权限验证,或是在 onTransact 方法中进行权限验证; (3) AIDL 访问流程总结: 先创建一个 Service 和一个 AIDL 接口,再创建一个类继承自 AIDL 接口中的 Stub 类并实现 Stub 中的抽象方法;在 Service 的 onBind 方法中返回这个对象,再在客户端就可以绑定服务端 service,建立连接后就可以访问远程服务端的方法了。
和 Messenger 一样,ContentProvider 底层同样是 Binder. 系统预置了很多 ContentProvider,如通讯录、日程表等,只需通过 ContentResolver 的 query/update/insert/delete 就可以跨进程访问这些信息。具体实现步骤如下: 1) 新建一个继承自系统 ContentProvider 的 Provider,并重写 onCreate, query, getType, insert, delete, update 六个方法。 这六个进程运行在 ContentProvider 的进程中,除了 onCreate 由系统回调运行在主线程中,其他五个方法运行在 Binder 线程池中。 2) 注册 Provider
- ".provider.XXProvider"android:authorities="com.xx.xx.provider"android:permission="com.xx.PROVIDER"android:process=":provider">
3) 在另一个进程中访问我们定义的 ContentProvider
- Uri uri = Uri.parse("content://com.xx.xx.provider");
- getContentResolver().query(uri,null,null,null,null);
- getContentResolver().query(uri,)
4) ContentProvider 数据源发生变化时,可通过 ContentResolver 的 notifyChange 方法来通知外界数据发生改变,外界可通过 ContentResolver 的 registerContentObserver 方法来注册观察者,通过 unregisterContentObserver 方法来解除观察者。
Socket 分为流式套接字和用户数据报套接字,分别是对应于网络的传输控制层中的 TCP/UDP TCP:面向连接的协议,稳定的双向通信功能,连接需要 "三次握手",提供了超时重传机制,具有很高的稳定性; UDP:面向无连接协议,不稳定的单向通信功能,也可提供双向通信功能。效率高,不能保证数据一定能正确传输。 实现步骤: 1) 服务端在新建一个 Service,并建立 TCP 服务
- @Override
- public void onCreate() {newThread(newTcpServer()).start();//阻塞监听客户端连接
- super.onCreate();
- }@Override
- publicIBinderonBind(Intent intent) {throw null;
- }private class TcpServer implements Runnable{
- @Override
- public void run() {
- ServerSocket serverSocket;try{//监听8688端口serverSocket =newServerSocket(8688);
- }catch(IOException e) {return;
- }while(!isServiceDestroyed) {try{// 接受客户端请求,并且阻塞直到接收到消息
- finalSocket client = serverSocket.accept();newThread() {@Override
- public void run() {try{//有消息接收到,新开一个线程进行处理消息responseClient(client);
- }catch(IOException e) {
- e.printStackTrace();
- }
- }
- }.start();
- }catch(IOException e) {
- e.printStackTrace();
- }
- }
- }
- }private void responseClient(Socket client)throwsIOException {// 用于接收客户端消息,将客户端的二进制数据流格式转成文本格式BufferedReader in =newBufferedReader(newInputStreamReader(client.getInputStream()));// 用于向客户端发送消息PrintWriter out =newPrintWriter(newBufferedWriter(newOutputStreamWriter(client.getOutputStream())),true);
- out.println("您好,我是服务端");while(!isServiceDestroyed) {
- String str = in.readLine();//通过数据流的readLine,可直接变成文本格式Log.i("moon","收到客户端发来的信息"+ str);if(TextUtils.isEmpty(str)) {//客户端断开了连接Log.i("moon","客户端断开连接");break;
- }
- String message ="收到了客户端的信息为:"+ str;// 从客户端收到的消息加工再发送给客户端out.println(message);
- }
- out.close();//关闭流in.close();
- client.close();
- }
2) 注册服务
- ".SocketServerService"android:process=":remote"/>
3) 客户端先建立连接服务端 socket
- Socket socket =null;while(socket ==null) {try{//选择和服务器相同的端口8688socket =newSocket("localhost",8688);
- mClientSocket = socket;
- mPrintWriter =newPrintWriter(newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())),true);
- }catch(IOException e) {
- SystemClock.sleep(1000);
- }
- }
4) 客户端和服务端通信,通过 while 循环不断去读取服务器发送过来的消息
- // 接收服务器端的消息BufferedReader br =newBufferedReader(newInputStreamReader(socket.getInputStream()));while(!isFinishing()) {finalString msg = br.readLine();if(msg !=null) {
- runOnUiThread(newRunnable() {@Override
- public void run() {
- tv_message.setText(tv_message.getText() +"\n"+"服务端:"+ msg);
- }
- }
- );
- }
- }
5) 客户端在 onCreate 中单开线程启动连接服务
- Intent service =newIntent(this, SocketServerService.class);
- startService(service);newThread() {@Override
- public void run() {
- connectSocketServer();
- }
- }.start();
来源: http://blog.csdn.net/smileiam/article/details/70194042