美文网首页
ipc跨进程通信

ipc跨进程通信

作者: 名字_都被占了 | 来源:发表于2018-05-08 15:37 被阅读0次
    先来了解一下进程和线程的概念及关系,线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。最简单的情况下,一个进程可以只有一个线程,即主线程,在Android里面主线程也叫UI线程,在UI线程里才能操作界面元素。很多时候一个进程需要执行大量耗时的任务,如果这些任务放在主线程中去执行就会造成界面无法响应,在Android中有一个特殊的名字叫做ANR,即应用无响应。
    1:进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。

    小提示:通过在terminal中输入adb shell ps com.wenxi.biandianzhan来查看某个程序下进程的详细情况,在这里com.wenxi.biandianzhan是applicationId,注意如果process的属性中包含:那么就可以用该命令查看该程序开启的所有包含:的进程,因为:表示该进程是私有进程,但如果不包含:,那么用该命令是无法查看不包含:的进程的。

    2:实现跨进程通信的方式有很多,比如通过Intent来传递数据,共享文件和SharePreferences,基于Binder的Messenger和AIDL以及Socket等。
    3:Serializable序列化:如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋给serialVersionUID,这个时候当前类的serialVersionUID就和序列化的数据中的serialVersionUID不一致,于是反序列化失败,程序就会挂掉,但是当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败,当我们增加或删除某个成员变量后,我们的反序列化过程仍然能够成功,程序能够最大程度的恢复数据。但是如果类结果发生了非常规性改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。另外注意一下:静态成员变量属于类不属于对象,所以不会参与序列化过程;其次用transient关键字修饰的成员变量不参与序列化过程。
    Android Studio类中实现Serializable自动生成serialVersionUID

    1、File -> Settings... -> Editor -> Inspections -> Serialization issues[在java类目下] -> Serializable class without 'serialVersionUID'(

    选中)
    2、进入实现了Serializable中的类,选中类名,Alt+Enter弹出提示,然后直接导入完成

    public class MainActivity extends AppCompatActivity {
        private Button write;
        private Button read;
        private ObjectOutputStream objectOutputStream;
        private ObjectInputStream objectInputStream;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.mainactivity);
            write = findViewById(R.id.write);
            read = findViewById(R.id.read);
            write.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        objectOutputStream = new ObjectOutputStream(new FileOutputStream("/storage/emulated/0/序列化.txt"));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    UserInfo userInfo = new UserInfo(2, "xiaofu", "nv");
                    try {
                        objectOutputStream.writeObject(userInfo);
                        objectOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
            read.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        objectInputStream = new ObjectInputStream(new FileInputStream("/storage/emulated/0/序列化.txt"));
                        UserInfo userInfo = (UserInfo) objectInputStream.readObject();
                        Toast.makeText(MainActivity.this, userInfo.toString(), Toast.LENGTH_SHORT).show();
                        objectInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    
    public class UserInfo implements Serializable {
        private static final long serialVersionUID = -8310875428029640329L;
    //serialVersionUID用系统生成的,那时候自己写了一个serialVersionUID,发现读取不到修改后的值。
        private int id;
        private String name;
        private String sex;
        private String address;
    //在没有该字段的情况下将object对象写入文件,然后在serialVersionUID没有变动的情况下,新增了address字段,然后再读取文件中的内容,仍然可以读
    
    出其他字段来。
    
        public UserInfo() {
        }
    
        public UserInfo(int id, String name, String sex) {
            this.id = id;
            this.name = name;
            this.sex = sex;
        }
    
        @Override
        public String toString() {
            return "UserInfo{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", sex='" + sex + '\'' +
                    '}';
        }
    }
    
    4:Parcelable序列化:Parcelable序列化功能由writeToParcel方法来完成,最终通过Parcel中的一系列write方法来完成;反序列化功能由CREATOR来完成,其内部标明了如何创建序列化对象(createFromParcel)和数组,并通过Parcel的一系列read方法来完成反序列化过程。系统也为我们提供l许多实现了Parcelable接口的类,比如Intent,Bundle,Bitmap等,同时List和Map也可以序列化,前提是它们里面的每个元素都是可序列化的。既然Parcelable和Serializable都是实现序列化并且都可用于Intent间的数据传递,那么二者之间该如何选取呢?Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量I/O操作。而Parcelable是Android中的序列化方式,因此更适合用在Android平台上,它的缺点是使用起来稍微麻烦,但是它的效率很高,这是Android推荐的序列化方式,因此我们要首选Parcelable。Parcelable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,因此在这两种情况下建议大家使用Serializable。以上就是Parcelable和Serializable的区别。
    5:跨进程通信之AIDL:利用AIDL实现服务的跨进程通信,注意当给service加了process属性后,也就是说这个service运行在另外一个进程中,不是普通的service了,普通的service中的Binder不涉及进程间通信,所以这时在activity中的ServiceConnection就得不到Binder对象了,这时候就需要用到aidl生成的Binder对象才能进行跨进程通信,或者是用Messenger进行跨进程通信,因为Messenger的底层也是aidl。另外在AIDL文件中,并不是所有的数据类型都是可以使用的,AIDL文件支持的数据类型如下:基本数据类型,String和CharSequence,List(只支持ArrayList,里面每个元素都必须能够被AIDL支持),Map(只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value),Parcelable(所有实现了Parcelable接口的对象),AIDL(所有的AIDL接口本身也可以在AIDL文件中使用)。以上6种数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来。不管是否在一个包内,都要手动import。另外一个需要注意的地方是,如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为parcelable类型。除此之外,AIDL中除了基本数据类型,其他类型的参数必须标上方向:in,out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。最后,AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。温馨提示:当客户端是另外一个应用时,我们可以直接把整个AIDL包和包中的文件复制到客户端工程中,AIDL的包结构,在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序也就无法正常运行。一个工程和两个工程的多进程本质是一样的,两个工程的情况,除了需要复制AIDL接口所相关的包到客户端,其他完全一样。
    以下是创建aidl的流程(创建的是关于实体类的aidl):

    第一步,先创建Entity实体类

    public class Entity implements Parcelable {
        ...
    }
    

    第二步,创建Entity.aidl,注意这时系统会提示名字不能重复(因为实体类的名字也是Entity),你可以先随便起个名字,然后再改成和实体类名字一样了,注意最后必须和实体类的名字一样,因为Entity.aidl就是对Entity.java在aidl中的申明。

    package com.example.liang.arlvyou.lanjiazai;
    parcelable Entity;
    

    第三步,创建EntityManager.aidl,具体注意事项看下面备注

    package com.example.liang.arlvyou.lanjiazai;
    import com.example.liang.arlvyou.lanjiazai.Entity;//实体类要手动导入
    interface EntityManager {
    void addEntity(in Entity entity);//方法中的参数要注明是in还是out还是inout
    String getName();//另外注意addEntity和getName默认是运行在Binder的线程池中,因此可以直接在这两个方法中执行耗时的任务。
    }
    

    第四步,make project,大功告成,系统会自动生成EntityManager.java。

    示例代码如下:

    ...
     serviceConnection=new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    //Toast.makeText(MainActivity.this, ((MyService.MyBinder) service).getName(), Toast.LENGTH_SHORT).show();//普通service
                    try {
                        Toast.makeText(MainActivity.this, (EntityManager.Stub.asInterface(service)).getName(), Toast.LENGTH_SHORT).show();//
    
    跨进程service
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
    
                }
            };
    ...
    
    public class MyService extends Service {
        private EntityManager.Stub stub=new EntityManager.Stub() {
            @Override
            public void addEntity(Entity entity) throws RemoteException {
    
            }
    
            @Override
            public String getName() throws RemoteException {
                return "我是跨进程服务binder";
            }
        };
       // private MyBinder myBinder=new MyBinder();
        public MyService() {
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return stub;
           // return myBinder;
        }
       /* class MyBinder extends Binder {//本地服务
            public String getName(){
                return "我是服务binder";
            }
        }*/
    }
    
    ...
     <service
                android:process=":hello.android"//加:表示当前进程是私有进程,可以通过adb shell ps com.wenxi.biandianzhan查看到该进程
                android:name=".lanjiazai.MyService"
                android:enabled="true"
                android:exported="true">
            </service>
    ...
    
    6:跨进程通信之Bundle:四大组件中的三大组件(Activity,Service,Receiver)都是支持在Intent中传递Bundle数据的,即使三大组件不在同一个进程中,也是可以利用Bundle进行数据的传递的,当我们在一个进程中启动了另个进程的Activity,Service和Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。
    7:跨进程通信之使用文件共享:共享文件也是一种不错的进程间通信的方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
    8:跨进程通信之Messenger:Messenger的底层实现是AIDL,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端不存在并发执行的情形。小提示:Message中能使用的载体有what,arg1,arg2,Bundle以及replyTo。Message的另一个字段object在同一个进程中是很实用的,但是在进程间通信的时候,仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输,这就意味着我们自定义的Parcelable对象是无法通过object字段来传输的。好在我们还有Bundle,Bundle中可以支持大量的数据类型,其中就包括我们自定义的Parcelable对象。

    示例代码如下(只能是客户端发送消息给服务器):

    ...
    serviceConnection=new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    Messenger messenger=new Messenger(service);//看源码可知这里messenger相当于指向new Messenger(myHandler);,然后调用send方法
    
    ,就可以将数据发送到另外一个进程中myHandler的handleMessage方法的参数中。
                    Message message=new Message();
                    message.what=110;
                    Bundle bundle=new Bundle();
                    bundle.putString("zhi","我是Messenger传递过来的");
                    message.setData(bundle);
                    try {
                        messenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
    
                }
            };
    ...
    

    另外一个进程中的MyService文件如下

    public class MyService extends Service {
        private MyHandler myHandler = new MyHandler();
        private Messenger messenger = new Messenger(myHandler);
    
        public MyService() {
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return messenger.getBinder();
        }
    
        private static class MyHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 110:
                        Log.d("MyService", msg.getData().getString("zhi"));
                        break;
                }
            }
        }
    }
    

    示例代码如下(客户端和服务器可以互发消息):

    ...
    private MyHandler myHandler = new MyHandler();
    private Messenger mMessenger = new Messenger(myHandler);
    private static class MyHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                Log.d("MainActivity", msg.getData().getString("zhi"));
            }
        }
    ...
    serviceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    Messenger messenger = new Messenger(service);
                    Message message = new Message();
                    message.what = 110;
                    Bundle bundle = new Bundle();
                    bundle.putString("zhi", "我是Messenger传递过来的");
                    message.setData(bundle);
                    message.replyTo=mMessenger;//注意这句代码得加上
                    try {
                        messenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
    
                }
            };
    ...
    

    就改一下MyService文件中的MyHandler类中的handleMessage方法就行

    ...
    private static class MyHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 110:
                        Log.d("MyService", msg.getData().getString("zhi"));
                        Messenger m=msg.replyTo;
                        Message mm=new Message();
                        Bundle b=new Bundle();
                        b.putString("zhi","我是服务端,我收到了");
                        mm.setData(b);
                        try {
                            m.send(mm);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        break;
                }
            }
        }
    ...
    
    9:ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,从这一点来看,它天生就适合进程间通信。和Messenger一样,ContentProvider的底层实现同样也是Binder。ContentProvider中除了onCreate方法运行在主线程里,其他五个方法(增删改查,getType)均运行在Binder线程池中。

    具体代码请查看:https://www.jianshu.com/p/917f983c4228

    10:Socket称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能。在性能上UDP具有更好的效率,其缺点是不保证数据一定能够正确传输,尤其是在网络拥塞的情况下。
    使用Socket来进行通信要声明INTERNET和ACCESS_NETWORK_STATE这两个权限。

    具体的代码请查看https://github.com/liang1075963999/bluetooth-and-wifi-tongxin

    相关文章

      网友评论

          本文标题:ipc跨进程通信

          本文链接:https://www.haomeiwen.com/subject/qdqwrftx.html