天天看点

android开发艺术探索笔记 IPC机制上

IPC简介

IPC是Inter-Process Communication的缩写。含义为进程间通信或者跨进程通信。是指两个进程之间进行数据交换的过程。

线程:按照操作系统中的描述,线程是cpu调度的最小单元,同时线程是一种有限的系统资源。

进程:一般指一个执行单元,在pc和移动设备上指一个程序或者一个应用。

 IPC不是android独有的,像window上就可以通过剪切板等进行进程间通信。android是基于linux内核的移动操作系统,它的进程间通信方式并不能完全继承制linux。它有自己的进程间通信的方式。android中最有特色的进程通信方式是Binder.另外还支持Scoket。

android中的多进程模式

1.开启多进程模式

一般情况下,android中的多进程指一个应用中存在多个进程的情况,暂不讨论多个应用之间的多进程情况。在android中使用多进程只有一种方法,就是在清单文件中对四大组件指定android:process属性。所以我们无法给一个线程或者一个实体类指定其运行的进程。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.lvqueen.myartexplore">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".day01.FirstActivity"
            android:process=":process1"
            />
        <!--:process1是指在当前进程名前附加上当前的包名,是简写。属于当前应用的私有进程,其他应用的组件
        不能和它跑在同一个进程中。
        -->
        <activity android:name=".day01.SecondActivity"
            android:process="com.lvqueen.myartexplore.process1"
            ></activity>
        <!--写完整路径的这种进程,属于全局进程。其他应用可以通过shareUID方式可以和它泡在同一个进程中-->
        <!--android系统会为每一个应用分配UID具有相同UID的应用才能共享数据。两个应用通过ShareUID方式跑在同一个
        进程中要求这两个应用有相同的ShareUID并且签名相同,这种情况下可以互相访问对方的私有数据data目录,组件信息,
        还可以共享内存数据。或者说他们看起来像一个应用的两部分。
        -->
    </application>

</manifest>      

⚠️注意:多进程造成的问题

(1)静态成员和单例模式完全失效:android为每一个应用(进程)分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致了在不同的虚拟机中访问同一个类对象会产生多份副本。并且这两个副本是互不干扰的,在一个进程中修改一个属性指,只会影响当前进程。而对其他进程不会造成任何影响。

(2)线程同步机制完全失效:既然都不是一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步。因为不同进程锁的不适同一个对象。

(3)SharePreferences的可靠性下降:它不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。因为share底层是通过读写XML文件来实现的,并发写显然是可能出现问题的,并发读都有可能出现问题。

(4)Application会多次创建:当一个组件跑在一个新的进程中时,由于系统要创建新的进程同时分配独立的虚拟机,所及这个过程其实就是启动一个应用的过程,因此相当于系统又把这个应用重新启动了一遍,既然重新启动,那么自然就会创建新的Appliction.

IPC基础概念介绍

1.Serializable:实现对象序列化。是java中的序列化接口,使用起来简单,但是花销很大,需要大量的i/o操作。序列化将对象转化为文件,反序列化将文件转换为对象。

使用方式:只要将java类简单的实现Serializable接口就可以了。

注意:在实现Serializable接口的实现类中,有一个SerialVersionUID(public static final long serialVersionUID = 1234567l;)

它用于辅助序列化和反序列化。进行序列化时,系统会把这个值写入文件当中,反序列化时会取出这个值和反序列化的类中的这个值做比对。只有序列化中的这个值和当前类的这个值相同,才能正常被反序列化,否则说明当前类和序列化的类相比发生了某些变化,无法完成正常反序列化。一般来说,我们应该手动指定它的值。如果不手动指定,系统会根据当前类计算一个hash值赋值给serialVersionUID,如果类删除或增加了某个成员变量,就会使这个值发生变法,导致不能正常反序列化了。如果手动指定,当应用升级删除或增加了某个变量后,程序能够最大限度的恢复数据。

/**
 * 序列化,把对象直接写入文件中
 */
private void toSerialize() {
    Student student = new Student();
    try {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
        out.writeObject(student);
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 * 反序列化,从文件中直接拿到对象
 */
private void deSerialize() {
    try {
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cache.txt"));
        Student student = (Student) inputStream.readObject();
        inputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}      

2.Parcelable:实现对象序列化》android中的序列化方式,效率高,但是用复杂。

public class Book  implements Parcelable{

    public int boolId;
    public String bookName;


    /**
     * 进行反序列化,主要下面protected方法
     * @param boolId
     * @param bookName
     */
    public Book(int boolId, String bookName) {
        this.boolId = boolId;
        this.bookName = bookName;
    }
    /**
     * 从序列化后的对象中创建原始对象
     * @param in
     * @return
     */
    protected Book(Parcel in) {
        boolId = in.readInt();
        bookName = in.readString();
//        in.readParcelable(Thread.currentThread().getContextClassLoader())
    }


    /**
     * 返回当前对象的内容描述,如果含有文件描述返回1,否则返回0,基本为0;
     * @return
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 将当前对象写入序列化结构中,除了一版的数据类型,也可以序列化一个序列化的对象
     *  parcel.writeParcelable()序列化一个序列化的对象
     * @param parcel
     * @param i:值为1或0,标记为1时表示当前对象需要作为返回值返回,不能立即释放资源
     *         基本上这个值都为0.
     */
    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(boolId);
        parcel.writeString(bookName);
//        parcel.writeParcelable();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        /**
         * 从序列化后的对象中创建原始对象
         * @param in
         * @return
         */
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        /**
         * 创建制定长度的原始对象数组
         * @param size
         * @return
         */
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}
      

3.Binder的使用和上层原理。

从IPC角度来说Binder是android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,设备驱动是/dev/binder。

从android Framework角度来时,Binder是ServiceManager连接各种Manger和相应的ManagerService的桥梁。

从android应用层来说Binder是客户端和服务器端进行通信的媒介。

android开发中Binder主要用于Service中,包括AIDL(android interface definition language 安卓接口定义语言)和Messenger。普通Service中不涉及进程间通信。Messenger底层为AIDL。

4.aidl文件创建

(在android studio中)例如你要创建的aidl文件在com.exmple.test.myaildtest文件路径下,则选择main-jave-com.exmple.test.myaildtest然后右键-new -AIDL -AIDL File这时候会在与java文件同级创建一个aidl文件夹,下面有个com.exmple.test.myaildtest文件夹,里面有你创建的aidl文件,写上你要添加的一些方法。使用之前rebulid一下,在build-generated-source-aidl-debug-com.exmple.test.myaildtest会生成一个与你创建的aild文件同名的文件,这时候你就可以使用了。

与java文件夹同级下的.aidl文件

⚠️注意:当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那不能在ui线程中发起。另外由于服务端的binder方法运行在binder的线程池中,所以binder方法不管是否耗时,都应该采取同步的方式去实现,因为它已经运行在一个线程中了。

android开发艺术探索笔记 IPC机制上
interface IBookManager {
  List<String> getBookList();
  void addBook(String book);
}      

build下自动生成的文件

public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.lvqueen.myartexplore.aidllearn.IBookManager {
//      Binder的唯一标示,一般用类名
        private static final java.lang.String DESCRIPTOR = "com.lvqueen.myartexplore.aidllearn.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.lvqueen.myartexplore.aidllearn.IBookManager interface,
         * generating a proxy if needed.
         * 用于将服务端binder对象转换成客户端所需的aidl接口类型对象。如果客户端和服务器端运行在统一进程,返回服务端
         * stub对象本身,否则返回stub.proxy
         */
        public static com.lvqueen.myartexplore.aidllearn.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.lvqueen.myartexplore.aidllearn.IBookManager))) {
                return ((com.lvqueen.myartexplore.aidllearn.IBookManager) iin);
            }
            return new com.lvqueen.myartexplore.aidllearn.IBookManager.Stub.Proxy(obj);
        }

        /**
         * 返回当前binder对象
         * @return
         */
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        /**
         * 运行在服务器端的binder线程池中。当客户端发起跨进程请求时,远程请求会通过系统底层封装后由此方法
         * 来处理,服务端通过code确定客户端手请求的目标方法。接着从data中取目标方法所需要的参数,执行目标方法。
         * 之后向reply中写入返回值。如果onTransact返回false,客户端请求会失败。可以用来做权限验证。这样就不能
         * 随便一个进程都能远程调用我们的服务了。
         * @param code
         * @param data
         * @param reply
         * @param flags
         * @return
         * @throws android.os.RemoteException
         */
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<java.lang.String> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeStringList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.lvqueen.myartexplore.aidllearn.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /**
             * 运行在客户端。创建方法说需要的输入型parcel对象_data输出型parcel对象_reply和返回值对象list;
             * 然后把该方法的参数信息写入_data中。接着调用transact方法来发起RPC(远程过程调用)请求,同时当前
             * 线程挂起,然后服务器端的ontransact方法会被调用。直到RPC过程返回后,当前线程继续执行,并从_reply
             * 中取出RPC过程的返回结果,最后返回_reply中的数据。
             *
             * @return
             * @throws android.os.RemoteException
             */
            @Override
            public java.util.List<java.lang.String> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<java.lang.String> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createStringArrayList();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            /**
             * 运行咋看客户端,执行同上,没有返回值,所以不需要从_reply中取出返回值。
             * @param book
             * @throws android.os.RemoteException
             */
            @Override
            public void addBook(java.lang.String book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(book);
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public java.util.List<java.lang.String> getBookList() throws android.os.RemoteException;

    public void addBook(java.lang.String book) throws android.os.RemoteException;
}
      

关于进程间通信的博客

点击打开链接

点击打开链接

点击打开链接

点击打开链接

继续阅读