首页 > 学院 > 开发设计 > 正文

IPC机制

2019-11-08 00:36:25
字体:
来源:转载
供稿:网友

简介

    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)@Overridepublic int describeContents() {    return 0;}//将当前对象写入序列化结构中@Overridepublic 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);        }    }}@Overridepublic 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"/>    在服务端进行权限验证

@Overridepublic 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 */@Overridepublic 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或者"星号/星号" */@Overridepublic String getType(Uri uri) {    return null;}@Overridepublic 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


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表