IPC机制笔记

什么是IPC?

IPC是Inter-Process Communication的缩写,含义为进程间通信或跨进程通信。

一般一个应用程序只有一个进程,至少有一个线程,即主线程,在Android中,主线程也叫UI线程,用于更新UI,如果进行耗时的操作,导致界面无法响应,就会出现ANR(Application not responding),所以要把操作放在子线程。

Android中的多进程模式

开启多进程模式:

  1. 在AndroidManifest.xml中,为activity设置android:process=”:remote”(remote可以随便起),这种方式进程名为:”程序包名:remote”

  2. 设置android:process=”com.ice.android.remote”,这种方式启动的进程名就是填入的字符串,不会附加包名。

    第一种的为应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。

    第二种属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中

序列化

Serializable接口

此接口是Java自带的。

该接口实现起来非常简单,只需要在类中加入一个属性即可:private static final long serialVersionUID = 1L;,序列化过程:

        User user = new User(20,"李斌");
        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)+"user");
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
            out.writeObject(user);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

反序列化过程类似,从文件读入Object即可。

但是两个对象不是同一个对象,只是属性的值一样而已。

serialVersionUID是用来辅助序列化与反序列过程的,只有序列化数据中的serialVersionUID与当前类的serialVersionUID相同才能正常地被反序列化。

如果不指定该值,那么会自动根据当前类的结构自动生成hash值当做serialVersionUID,反序列化的时候也会生成hash值进行比较,所以我们改动了类的结构就无法成功反序列化,因为hash值会变化。因为如此,所以我们应该手动指定serialVersionUID,这样就算改动了类的结构,还是能最大限度的反序列化。

Parcelable接口

此接口是Android特有的。

package com.ice.androiddevart;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}

writeToParcel方法进行,protected Book(Parcel in)进行反序列化。

区别

Parcelable和Serializable都能实现序列化并且都可以用于Intent间传递数据。但是Serializable开销较大,需要大量的I/O操作。而Parcelable是Android中特有的序列化方式,效率较高。

Parcelable主要用在内存序列化上。而Serializable主要用于将对象序列化到存储设备或用于网络传输。

Binder

暂时跳过

Android中的IPC方式

使用Bundle

        Intent intent = new Intent("");
        Bundle bundle = new Bundle();
        bundle.putSerializable("key", serializable_object);
        bundle.putParcelable("key",parcelable_object);
        intent.putExtras(bundle);

四大组件中的三大组件(没有广播)都支持在Intent中传递Bundle数据。

使用文件共享

实现Serializable接口,将对象序列化写入文件,然后在其他进程读取即可。

但是在并发读写的时候是有问题的,所以适合在对数据同步要求不高的进程之间通信。

使用Messenger

使用步骤:

服务端:

  1. 创建一个MessengerHandler,用于接受客户端发送过来的消息。

       private static class MessengerHandler extends Handler{
           @Override
           public void handleMessage(Message msg) {
               switch (msg.what){
                   case FROM_CLIENT:
                       Log.d(TAG, "handleMessage: get message from client" + msg.getData());
                       break;
               }
           }
       }
  2. 通过上面的Handler创建一个Messenger对象:

    private final Messenger mMessenger = new Messenger(new MessengerHandler());

  3. 在Service的onBind中返回Messenger的底层Binder。

客户端:

  1. 绑定Service,绑定成功后用服务器端返回的IBinder对象创建一个Messenger,通过它向服务器发送数据。

    private ServiceConnection mConnection = new ServiceConnection() {
           @Override
           public void onServiceConnected(ComponentName name, IBinder service) {
               mServiceMessenger = new Messenger(service);
               Message message = Message.obtain(null, MessengerService.FROM_CLIENT);
               Bundle data = new Bundle();
               data.putString("msg","Hello this is client");
               message.setData(data);
               try {
                   mServiceMessenger.send(message);
               } catch (RemoteException e) {
                   e.printStackTrace();
               }
           }
           @Override
           public void onServiceDisconnected(ComponentName name) {
    
           }
       };
  2. 如果服务器要向客户端通信,那么在客户端要创建一个Handler对象,然后创建一个客户端的Messenger对象,将此对象通过Message对象的replyTo参数传递给服务端即可。

Messenger的工作方式是串行的,只能一个消息一个消息的处理,如果大量的并发请求,就不合适了。

而且Messenger无法调用服务端的方法。要实现方法的调用,要使用AIDL。

Messenger本质上也是AIDL,只不过系统做了很好的封装。

使用AIDL

AIDL:Android Interface Definition Language

服务端:

  1. 将服务端要暴露的接口写在AIDL文件中:

    interface IbookManager{
       List<Book> getBookList();
       void addBook(in Book book);
    }
  2. 在服务器端实现方法:

       private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    
       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);
           }
       };

    并通过onBind方法返回Binder对象

在客户端通过服务端返回的Binder对象转化成AIDL接口所属的类型,接着调用方法即可。

private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IbookManager bookManager = IbookManager.Stub.asInterface(service);
            try{
                List<Book> list = bookManager.getBookList();
                Log.d(TAG, "onServiceConnected: book list class:" + list.getClass().getCanonicalName());
                Log.d(TAG, "onServiceConnected: book list :" + list.toString());
            }catch (RemoteException e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

注意:

在AIDL文件中支持的数据类型:

  • 基本的数据类型
  • String和CharSequence
  • List:只支持ArrayList,里面的每个元素都必须被AIDL支持
  • Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
  • Parcelable:所有实现了Parcelable接口的对象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

自定义的Parcelable对象和AIDL对象必须要显示的import,就算位于同一个包下面也要导入。

如果AIDL文件中用到了自定义的Parcelable对象,那么要新建一个与之同名的AIDL文件,并声明为Parcelable类型:

package com.ice.androiddevart;

parcelable Book;

普通方法能够工作的很好,但是如果采用了观察者模式,进行监听回调,那么再注册与解除的时候会出现问题,即使在Activity中,listener是同一个对象,但是在Service中,listener由于经过了序列化与反序列化,所以对象以及不再是同一个了,无法通过判断是否是同一个对象而解除监听。要使用RemoteCallbackList来进行注册与解除,原理是通过listener底层的Binder对象来判断是否是同一个listener,因为底层的Binder对象是不会变的。

在远程访问服务端的方法时,方法是在Binder线程池中运行的,所以服务端的方法不需要再套一个子线程,应该在客户端调用的时候套子线程,避免ANR。

ContentProvider

底层依然采用Binder,但是由于封装的很好了,所以使用起来特别方便。

  1. 自定义一个ContentProvider,重写方法crud onCreate,getType方法即可:

    其中onCreate运行在主线程,其他运行在Binder线程池。

  2. 在xml文件中声明provider:

           <provider
               android:name=".provider.BookProvider"
               android:authorities="com.ice.androiddevart.provider" #通过这个访问  名字要唯一
               android:process=":provider" />
  3. 在Activity中访问:

    Uri uri = Uri.parse("content://com.ice.androiddevart.provider");
    getContentResolver().query(uri,null,null,null,null);

contentProvider用来对其他应用进行增删改查是十分简单的,但是如果自己定义contentProvider的话,要做好线程同步的工作!

Socket通信

暂时跳过

猜你喜欢

转载自blog.csdn.net/q1242027878/article/details/75098387
今日推荐