IPC机制

简介

    IPC是 Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
    进程和线程是包含与被包含的关系。


Android中的多进程模式

1. 在Android中创建多进程的方法:
        ① 通过JNI在native层去fork一个新的进程(不常用)
        ② 给四大组件添加属性 android:process = "进程名" 就可以开启多进程模式,如果没有指定这个属性,那么默认进程名是包名
    
2. 给进程命名:
        ① android:process = ": 名字" ,这是一种简写的方法,完整的进程名为 "包名+名字";是属于当前应用的私有进程,其他应用的组件不可以和它跑到同一个进程中

<service
    android:name=".services.BookManagerService"
    android:enabled="true"
    android:exported="true"
    android:process=":myaidl" />
       

        ② android:process = "全名" ,属于全局进程,其它的应用通过ShareUID方法可以和它跑到同一个进程中。
   

3. 一般来说,使用多进程会造成如下几方面的问题:
        ① 静态成员和单例模式完全失效:
                Android为每个进程都会分配一个独立的虚拟机,这导致在不同的虚拟机中访问同一个类的对象会产生多个副本
        ② 线程同步机制完全失效
                Android为每个进程都会分配一个独立的虚拟机(内存)
        ③ SharedPreferences的可靠性下降
                底层是通过XML文件来实现的,并发读写都可能出问题
        ④ Application会创建多次
                运行在不同进程的组件是属于不同的虚拟机和Application的


Android中的序列化机制

        Serializable是java中的序列化接口,其使用起来简单但是开销很大,序列化、反序列化过程都需要大量的I/O操作
        Parceable是Adnroid中的序列化接口,效率很高,就是使用起来麻烦一点

一、Serializable接口:

    ① 为该类指定 private static final long serialVersionUID = 465465464l;
        作用:当版本升级后,可能删除或者添加了某个成员变量,这个时候我们仍然能够反序列化成功,程序能够最大限度的恢复数据,相反,如果不指定serialVersionUID,
              程序则会挂掉。
    ② 采用ObjectOutputStream、ObjectInputStream实现序列化和反序列化操作:

File file = new File(Environment.getExternalStorageDirectory() , "test.text");
//序列化
UsersS usersS = new UsersS(20 , "xiaoming" , true);
try {
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
    out.writeObject(usersS);
    out.close();
} catch (IOException e) {
    e.printStackTrace();
}

//反序列化
try {
    ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
    UsersS usersS1 = (UsersS) in.readObject();
    in.close();
    LogUtils.e("反序列化得到的名字是:" + usersS1.getName());
} catch (IOException e) {
    e.printStackTrace();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

二、Parceable接口:

    ① 实现这个接口可以实现序列化,并可以通过Intent和Binder传递:

//返回当前对象的描述(一般返回0)
@Override
public int describeContents() {
    return 0;
}
//将当前对象写入序列化结构中
@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(age);
    dest.writeString(name);
    dest.writeInt(isMale ? 1 : 0);
    dest.writeParcelable(usersS , 0);//users另外是一个序列化的类的对象;flag一般是0
}
public static final Creator<UsersP> CREATOR = new Creator<UsersP>() {
    //从序列化后的对象中创建原始对象
    @Override
    public UsersP createFromParcel(Parcel in) {
        return new UsersP(in);
    }
    //创建指定长度的原始对象数组
    @Override
    public UsersP[] newArray(int size) {
        return new UsersP[size];
    }
};
protected UsersP(Parcel in) {
    age = in.readInt();
    name = in.readString();
    isMale = in.readInt() == 1;
    in.readParcelableArray(Thread.currentThread().getContextClassLoader());//反序列化过程需要传入当前线程的上下文类加载器
}


 Bundle

        可以在Bundle上附加需要传输的信息并通过Intent发送出去

    Intent intent = new Intent();
    Bundle bundle = new Bundle();
    intent.putExtra("1" , bundle);


 文件共享

        适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读、写的问题

    例子:在两个进程中分别进行序列化和反序列化一个java类,存在问题:
               一个序列化、一个反序列化,那么反序列化出来的可能不是最新的;
               如果两个同时进行序列化就有可能出现更严重的问题;


Messenger(翻译为“信使”,对AIDL做了封装)

        ① 通过它可以在不同的进程中传递Message对象,在Message中可以放入我们需要传递的数据
        ② 两个构造函数表明它的底层实现是AIDL:
            public Messenger(Handler handler){
                mTarget = target.getIMessenger();
            }
            public Messenger(IBinder target){
                mTarget = IMessenger.Stub.asInterface(target);
            }
        ③ 一次只处理一个请求,在服务端不需要考虑线程同步的问题。
        ④ Messenger是串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,
            那么Messenger就不太合适了。
        ⑤ Messenger的主要作用是为了传递消息,很多时候我们很可能需要跨进程调用服务端的方法,这种情形Messenger就无法做到了,aidl可以。

下面通过一个例子来加深理解:

客户端通过bindService()来启动服务,然后根据返回的IBinder对象得到Messenger对象:

private ServiceConnection conn = new ServiceConnection(){
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //通过返回的IBinder对象,得到一个Messenger对象
        Messenger messenger = new Messenger(service);
        Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
        Bundle bundle = new Bundle();
        bundle.putString("msg" , "this is test");
        msg.setData(bundle);
        //将客户端的Messenger通过Message的replyTo参数传递给服务端
        msg.replyTo = mMessenger;
        try {
            messenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        //断开连接会触发此方法
    }
};

服务端接收到客户端传来的信息,并返回数据:

private final Messenger messenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case MyConstants.MSG_FROM_CLIENT:
                LogUtils.e("服务端:" + msg.getData().getString("msg"));
                //通过Message的replyTo得到客户端传递过来的Messenger对象
                Messenger replyTo = msg.replyTo;
                Message replyMsg = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
                Bundle bundle = new Bundle();
                bundle.putString("reply" , "this is reply");
                replyMsg.setData(bundle);
                try {
                    replyTo.send(replyMsg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                super.handleMessage(msg);
        }
    }
}
@Override
public IBinder onBind(Intent intent) {
    //绑定成功,返回Messenger对象底层的Binder
    return messenger.getBinder();
}

然后再在客户端通过handler来进行处理:

switch (msg.what){
    case MyConstants.MSG_FROM_SERVICE:
        LogUtils.e("服务端返回回来的消息:" + msg.getData().getString("reply"));
        break;



AIDL


一、创建AIDL文件:
    ① 自定义的Parcelable对象和AIDL对象必须要显示import进来,不管是否位于同一个包
    ② aidl接口只支持方法,不支持静态常量
    ③ aidl中除了基本的数据类型,其他类型的参数必须标上方向:in 、out或者inout。其中:
           in: 表示输入型的参数
           out:表示输出型的参数
           inout:表示输入输出型的参数
    ④ 如果aidl中用到了自定义的Parcelable对象,那么就必须新建一个和它同名的AIDL文件,并且在其中申明它为 Parcelable
    ⑤ build--->Rebuild project 生成对应的java文件,java文件路径 build/generated/source/aidl/debug/XX.java
    
    
二、保持客户端、服务端的aidl文件一致:
    AIDL的包结构在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类。如果类的完整路径不一样,就无法成功反序列化


三、问题:
    ① 添加单个元素集合(list)
        CopyOnWriteArrayList:支持并发的读、写(会存在多个线程同时访问的情形);相对应的ConcurrentHashMap

/**
 * CopyOnWriteArrayList:
 *      支持并发的读、写(会存在多个线程同时访问的情形);相对应的ConcurrentHashMap
 */
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    ② 添加注册注销接口集合(map):
              RemoteCallbackList:是系统专门提供的用于删除跨进程listener的接口,其内部实现了自动同步的功能。
              int N = RemoteCallbackList.beginBroadcast();
              RemoteCallbackList.finishBroadcast();必须配对使用

/**
 * RemoteCallbackList: 是系统专门提供的用于删除跨进程listener的接口,其内部实现了自动同步的功能。
 *         int N = RemoteCallbackList.beginBroadcast();
 *         RemoteCallbackList.finishBroadcast();必须配对使用
 */
private RemoteCallbackList<OnNewBookListener> mListeners = new RemoteCallbackList<>();
    ② Binder意外死亡需要重新连接:
            binderDied():默认在子线程执行
            onServiceDisconnected():在默认主线程执行

四、添加aidl的访问权限:
    在客户端和服务端都添加自定义的权限
    <permission android:name="zidingyiquanxian"
        android:protectionLevel="normal"/>
    在服务端进行权限验证

@Override
public IBinder onBind(Intent intent) {
    int check = checkCallingOrSelfPermission("自定义权限");
    if(check == PackageManager.PERMISSION_DENIED){
        //权限拒绝,直接返回null
    }
    return mBinder;
}

五、配置 builder.gradle文件:

//把src/main/aidl文件也作为Java.srcDirs, resources.srcDirs;否则找不到自定义的类
sourceSets {
    main {
        manifest.srcFile 'src/main/AndroidManifest.xml'
        java.srcDirs = ['src/main/java', 'src/main/aidl']
        resources.srcDirs = ['src/main/java', 'src/main/aidl']
        aidl.srcDirs = ['src/main/aidl']
        res.srcDirs = ['src/main/res']
        assets.srcDirs = ['src/main/assets']
    }
}


ContentProvider

这里以数据库的存储方式的例子来加深理解:

创建一个contentprovider:

/**
 * 定义单独的uri和uri_code,并关联在一起。当外界请求时可以根据请求的uri得到对应的uri_code,从而知道访问哪一张表
 */
private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
    matcher.addURI(AUTHORITIES , "book" , BOOK_URI_CODE);
    matcher.addURI(AUTHORITIES , "users" , USERS_URI_CODE);
}

/**
 * 代表contentprovider的创建,一般做初始化操作,运行在主线程,其他的方法运行在Binder线程池中
 * @return
 */
@Override
public boolean onCreate() {
    context = getContext();
    ProviderSqliteOpenHelper providerSqliteOpenHelper = new ProviderSqliteOpenHelper(context , ProviderSqliteOpenHelper.DB_NAME , null , 1);
    db = providerSqliteOpenHelper.getWritableDatabase();
    return true;
}
/**
 * @param uri Uri请求的路径
 * @return 请求对应的MIME类型(媒体类型),如果不关心这个选项,可以直接返回null或者"星号/星号"
 */
@Override
public String getType(Uri uri) {
    return null;
}

@Override
public Uri insert(Uri uri, ContentValues values) {
    String tableName = getTableName(uri);
    if(tableName != null){
        db.insert(tableName, null, values);
        context.getContentResolver().notifyChange(uri , null);
        return uri;
    }
    return null;
}
public String getTableName(Uri uri){
    String table = null;
    switch (matcher.match(uri)){
        case BOOK_URI_CODE:
            table = ProviderSqliteOpenHelper.BOOK_TABLE_NAME;
            break;
        case USERS_URI_CODE:
            table = ProviderSqliteOpenHelper.USER_TABLE_NAME;
            break;
    }
    return table;
}

在另外一个进程中访问contentprovider:

Cursor cursor = contentResolver.query(bookUri, null, null, null, null);
if (cursor.moveToFirst()){
    int index = cursor.getColumnIndex("name");
    String name = cursor.getString(index);
    LogUtils.e("查询到的数据:name = " + name);
    while (cursor.moveToNext()){
        int index1 = cursor.getColumnIndex("name");
        String name1 = cursor.getString(index);
        LogUtils.e("查询到的数据:name = " + name1);
    }
}
cursor.close();


Socket(“套接字”)

        ① 流式套接字(网络传输控制层中的TCP协议):面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性。
            Socket必须在发送数据之前与目的地的Socket取得连接,一旦连接建立了,Socket就可以使用一个流接口进行打开、读写以及关闭操作。并且,所有发送的数据在另一端都会以相同的顺序被接收。
        ② 用户数据报套接字(UDP协议):是无连接的,提供不稳定的单向通信功能(目的地址和要发送的内容),当然UDP也能实现双向通信功能。

1、Socket的构造方法
  Java在包java.NET中提供了两个类Socket和ServerSocket,分别用来表示双向连接的Socket客户端和服务器端。

  Socket的构造方法如下:

  (1)Socket(InetAddress address, int port);

  (2)Socket(InetAddress address, int port, boolean stream);

  (3)Socket(String host, int port);

  (4)Socket(String host, int port, boolean stream);

  (5)Socket(SocketImpl impl);

  (6)Socket(String host, int port, InetAddress localAddr, int localPort);

  (7)Socket(InetAddress address, int port, InetAddrss localAddr, int localPort);

  ServerSocket的构造方法如下:

  (1)ServerSocket(int port);

  (2)ServerSocket(int port, int backlog);

  (3)ServerSocket(int port, int backlog, InetAddress bindAddr);

  其中,参数address、host和port分别是双向连接中另一方的IP地址、主机名和端口号;参数stream表示Socket是流Socket还是数据报Socket;参数localAddr和localPort表示本地主机的IP地址和端口号;SocketImpl是Socket的父类,
    既可以用来创建ServerSocket,也可以用来创建Socket。

2、输入流和输出流

  Socket提供了方法getInputStream()和getOutPutStream()来获得对应的输入流和输出流,以便对Socket进行读写操作,这两个方法的返回值分别是InputStream和OutPutStream对象。

  为了便于读写数据,我们可以在返回的输入输出流对象上建立过滤流,如PrintStream、InputStreamReader和OutputStreamWriter等。

3、关闭Socket

  可以通过调用Socket的close()方法来关闭Socket。在关闭Socket之前,应该先关闭与Socket有关的所有输入输出流,然后再关闭Socket。

例子:

客户端与服务端建立连接:

public void connectTcp(){
        while (mSocket == null){
            //和tcp服务建立连接
            try {
                mSocket = new Socket("localhost" , 8688);
                printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream())) , true);
                isConnected = true;
            } catch (IOException e) {
                e.printStackTrace();
                SystemClock.sleep(1000);
                isConnected = false;
            }
            if(isConnected) {
                //接收服务端的消息
                try {
                    br = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
                    while (!SocketTcpActivity.this.isFinishing()){
                        String msg = br.readLine();
                        if(msg != null){
                            Message message = Message.obtain();
                            Bundle bundle = new Bundle();
                            bundle.putString("serverMsg" , msg);
                            message.setData(bundle);
                            message.what = MyConstants.ACCPT_SERVER_MSG;
                            mHandler.sendMessage(message);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        br.close();
        printWriter.close();
        mSocket.close();

再次发送消息:

printWriter.println(msg);

在服务端开一个线程监听客户端的连接请求:

public void run() {
    try {
        serverSocket = new ServerSocket(8688);
    } catch (IOException e) {
        e.printStackTrace();
        return;
    }
    while (!isServiceDied){
        try {
            //指定端口实例化一个ServerSocket,并调用ServerSocket的accept()方法在等待客户端连接期间造成阻塞。
            final Socket socket = serverSocket.accept();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    responseClient(socket);
public void responseClient(Socket socket){
    try {
        //接收客户端的消息
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        //用于向客户端发送消息
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
        while (!isServiceDied){
            String str = in.readLine();
            if(str == null){
                //客户端传来的数据为空(可以做一些断开连接、不回复消息等操作)
                break;
            }
            int i = new Random().nextInt(strMsg.length);
            String msg = strMsg[i];
            out.println(packMessage(msg));

        }
        out.close();
        in.close();
        socket.close();


选择合适的IPC机制



完整demo路径

http://download.csdn.net/detail/fanghana/9762302

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值