进程间通信,Android面试一天一题

2020-02-16 10:31 来源:未知

现在我们知道了如何传递自定义的对象,那么在两个Activity之前传递对象还要注意什么呢?

问题

  1. Parcelable和Serializable的区别:

Serializalbe会使用反射,序列化和反序列化过程需要大量I/O操作,Parcelable自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在Native内存中,效率要快很多。

  1. Parcelable 和Parcel这两者之间的关系。

Parcelable 接口定义在封送/解封送过程中混合和分解对象的契约。Parcelable接口的底层是Parcel容器对象。Parcel类是一种最快的序列化/反序列化机制,专为Android中的进程间通信而设计。该类提供了一些方法来将成员容纳到容器中,以及从容器展开成员。

  1. 在两个Activity之前传递对象还要注意什么呢?

一定要要注意对象的大小,Intent中的Bundle是在使用Binder机制进行数据传递的,能使用的Binder的缓冲区是有大小限制的(有些手机是2M),而一个进程默认有16个binder线程,所以一个线程能占用的缓冲区就更小了(以前做过测试,大约一个线程可以占用128KB)。所以当你看到“The Binder transaction failed because it was too large.”这类TransactionTooLargeException异常时,你应该知道怎么解决了。因此,使用Intent在Activity之间传递List和Bitmap对象是有风险的。

除了传递基本类型外,传递自定义的对象需要实现Parcelable或者Serializable接口。

序列化

  • 序列化一个实例对象编码成字节流, 存入物理内存/用于传输
  • 反序列化从字节流对象中再次重新构建对象实例。

和JSON XML不同的是 JSON XML 是字符描述型对象, 它们是通用的, 不依赖于任何语言平台

对于初级的程序员来说,只要能抓住老鼠,白猫或者黑猫甚至是小狗都是没有区别的。但对于应用的流畅和体验来说,100毫秒和1000毫秒是有很大区别的。很多程序员眼里无关紧要的差别,最终在用户那儿会被几倍十几倍的放大,老板也会因为用户的投述而斥责你。因为总会有用户在用性能很差的手机,总有用户手机的使用情况很复杂(内存紧张,网络复杂等等),总有用户本人就很奇葩不会按你指定的套路出拳!当你鄙视老板不懂代码的艺术时,老板也会鄙视你不懂用户不懂细节的重要性,活该你一辈子做程序员。

Parcelable 模板

public class Book implements Parcelable {
    private int bookId;
    private String bookName;
    private List<String> mdatas;

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

    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);
        dest.writeStringList(mdatas);
    }
}

binder不是用来跨进程通信的吗?为什么activity间传递bundle要使用binder?

很多Activity并不是在一个进程中,比如你从自己的应用通过Intent打开一个发Email的Activity,你要往这样Activity传一些诸如收件人、主题等信息吧。这样一想Intent的传递肯定就是要跨进程的了.

过程
  • 序列化
    1. 是否替换即将写入流的对象, writeReplace, 比如使用静态内部类代理
    2. 将对象写成流, writeObject,
      • ObjectOutputStream.defaultWriteObject()默认的序列化过程
  • 反序列化
    1. 将流读成对象, readObject
      • 如果序列化时自定义了序列化过程, 这里也需要自定义反序列化过程
    2. 是否替换从流中读出来的对象, readResolve
  • 示例bean

    import java.io.InvalidObjectException;
    import java.io.ObjectInputStream;
    import java.io.Serializable;
    
    class Persion implements Serializable {
    
        public String desc;
        public String name;
    
        public Persion(String desc, String name) {
            this.desc = desc;
            this.name = name;
        }
    
        static class SerializableProxy implements Serializable{
            private String desc;
            private String name;
    
            private SerializableProxy(Persion s) {
                this.desc = s.desc;
                this.name = s.name;
            }
    
            /**
             * 在这里恢复外围类
             * 注意看这里!!!最大的好处就是我们最后得到的外围类是通过构造器构建的!
             */
            private Object readResolve() {
                return new Persion(desc,name);
            }
    
        }
    
        /**
         * 外围类直接替换成静态内部代理类作为真正的序列化对象
         * @return
         */
        private Object writeReplace() {
            return new SerializableProxy(this);
        }
    
        /**
         * 这里主要是为了防止攻击,任何以Persion声明的对象字节流都是流氓!!
         * 因为我在writeReplace中已经把序列化的实例指向了SerializableProxy
         * @param stream
         * @throws InvalidObjectException
         */
        private void readObject(ObjectInputStream stream) throws InvalidObjectException {
            throw new InvalidObjectException("proxy requied!");
        }
    }
    

一定要要注意对象的大小,Intent中的Bundle是在使用Binder机制进行数据传递的,能使用的Binder的缓冲区是有大小限制的,而一个进程默认有16个binder线程,所以一个线程能占用的缓冲区就更小了(以前做过测试,大约一个线程可以占用128KB)。所以当你看到“The Binder transaction failed because it was too large.”这类TransactionTooLargeException异常时,你应该知道怎么解决了。

总结

能使用的Parcelable的地方,请不要贪图简便直接Serializable,实在懒的话也可以试试用插件自动生成Pracelabel的模板代码:android-parcelable-intellij-plugin 试了一下,so easy!谁用谁知道

区别

两种都是用于支持序列化、反序列化话操作。

  1. 两者最大的区别在于存储媒介的不同,Serializable使用IO读写存储在硬盘上,而Parcelable是直接在内存中读写,很明显内存的读写速度通常大于IO读写,所以在Android中通常优先选择Parcelable。
  2. Serializalbe会使用反射,序列化和反序列化过程需要大量I/O操作,Parcelable自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在Native内存中,效率要快很多。
    Pacelable 和 Serializable 序列化的作用是不同的,Pacelable 是为了“传递”,而 Serializable 是为了“储存”。另外 Serializable 之所以慢,是由于为了读取对象中的成员变量而使用的大量的反射,而 Pacelable 的读写都是自己实现的。还有 Serializable 也是可以自己设置不想要序列化的属性的,只需要在该变量前加一个 transient 关键字(static 标记的属性也是不能序列化的,但是 static final 的可以)。

进程

进程有自己的内存地址, 一个进程中的1000地址可能在另一个进程中是10000, java的引用本质上还是内存地址, 如果要传递一个类的实例, 还需要传递方法等等, 方法是独立与类对象存在的, 所以到另一个进程中去引用同一个方法就错了, 还是因为独立内存地址的原因.
Android中Activity之间并不能保证两个Activity在同一个进程中, 比如一个APP调用系统打电话功能, 就是两个进程, 所以需要进程间通信

Serializalbe会使用反射,序列化和反序列化过程需要大量I/O操作,Parcelable自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在Native内存中,效率要快很多。

前言

引用地址 Android面试一天一题(9 Day)

在两个Activity之前传递对象还要注意什么呢?

一定要要注意对象的大小,Intent中的Bundle是在使用Binder机制进行数据传递的,能使用的Binder的缓冲区是有大小限制的(有些手机是2M),而一个进程默认有16个binder线程,所以一个线程能占用的缓冲区就更小了(以前做过测试,大约一个线程可以占用128KB)。所以当你看到“The Binder transaction failed because it was too large.”这类TransactionTooLargeException异常时,你应该知道怎么解决了。

Parcel

是一系列java通过C 调用内存的操作, 将序列化的数据写入共享内存, 实现跨进程通信

  1. 根据这句宏定义得出,Link
#define PAD_SIZE(s) (((s) 3)&~3

内存存放机制和C 的结构体的内存对齐一样, 即读取最小字节位32bit(4字节), 如果高于4字节, 以实际数据类型存放, 但得为4的倍数

  • 占用 32 bit,(<= 32 bit), 例如: boolean, char, int
  • 实际占用字节(> 32 bit), 例如: long, float, String, 数组等

由此可以知道, 当我们写入/读取一个数据时, 偏移量至少为4Byte, 偏移量公式
f(x) = 4 * x (x=0, 1, ...)

  1. writeXXX 和 readXXX 导致的偏移量是共用的, 我们在writeInt(23)后, 此时的dataposition = 4, 读取的时候, 我们需要将偏移量置为0, 再从0开始读取4个字节, 所以需要先 setDataPosition(0), 再readInt().
  2. 如果预分配的空间不够时newSize = ((mDataSize len) * 3)/2;会一次多分配50%;
  3. 对于不同数据的存储不一样

    • 对于普通数据, 使用的是 mData 内存地址,
    • 对于IBinder 或者 FileDescriptor, 使用的是 mObjects 内存地址, 通过 flatten_binder() 和 unflatten_binder()实现, 目的是反序列化时读出的对象就是愿对象而不是 new 出来的新对象

    测试BinderData 但是目前已经不能找不到 BinderData 这个类

public class MyParcelable implements Parcelable { private int mData; public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt; } public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() { public MyParcelable createFromParcel(Parcel in) { return new MyParcelable; } public MyParcelable[] newArray { return new MyParcelable[size]; } }; private MyParcelable(Parcel in) { mData = in.readInt(); }}

Parcelable和Parcle这两者之间的关系?

Parcelable 接口定义在封送/解封送过程中混合和分解对象的契约。Parcelable接口的底层是Parcel容器对象。
Parcel类是一种最快的序列化/反序列化机制,专为Android中的进程间通信而设计。该类提供了一些方法来将成员容纳到容器中,以及从容器展开成员。

Parcelable

因此,使用Intent在Activity之间传递List和Bitmap对象是有风险的。

面试题:两个Activity之间如何传递参数?

过程
  • 序列化
    • 基本类型直接 writeString / writeInt 然后调用 nativeWriteString / nativeWriteInt 用C 操作
    • 包含的子类用 writeToParcel 把类名还是用 nativeWriteString 传给C 操作
  • 反序列化

    • CREATOR

      • createFromParcel 中从流中new出对象和
      return new Pojo(in);
      

那么我们先回答这个题,如何传递参数:

效率及选择

Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据,而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable美高梅网投平台,,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化.

SerializableLink

JVM 虚拟机中的对象, 其内部的状态只存在于内存中, JVM 停止后这些数据就丢失了, 所以考虑到持久化 , 通常是保存在文件系统或者数据库中, 比如 对象映射关系(Object-relational mapping), 对象序列化机制(Object serialization)是Java提供的一种对象持久化方式, 将JVM中的对象和字节数组流之间进行转换

过程简述: Java 的 ObjectOutputStream 类用来持久化一个对象, 通过 writeObject 方法把这个类的对象写到一个文件, 再通过 ObjectInputStream 的readObject 方法把这个对象读出来.

  • 具备 serialVersionUID, 用来标识当前序列化对象的版本, 如果需要本地存储, 建议每一个实现 Serializable 的类都指定 serialVersionUID. 如果没有指定, JVM 会根据类的信息自动生成一个 UID, 我们可以通过 JDK 的 serialver 命令来查看一个 .class 的 UID
  • transient描述的域和类的静态变量不会被序列化 (static修饰的变量会改变, 但那是因为它放在静态区, 而不是因为序列化)Link
  • transient 只能描述变量, 不能描述类和方法(局部变量无法被修饰--类的方法中定义的变量)
  • 如果一个实现了Serializable的类继承自另一个类, 那么这个类必须实现Serializable或者提供一个无参构造函数
  • 反序列化并不是通过构造器创建的,
  • 因为序列化的过程是可见的, 所以EffectiveJava的作者在第77节中希望使用静态内部类防止被攻击

Parcelable 接口定义在封送/解封送过程中混合和分解对象的契约。Parcelable接口的底层是Parcel容器对象。Parcel类是一种最快的序列化/反序列化机制,专为Android中的进程间通信而设计。该类提供了一些方法来将成员容纳到容器中,以及从容器展开成员。

Serializable 完整传递过程

结论: Intent 传递的 Serializable 数据最后还是由 Parcel 传递给 C 操作, 但是 比普通的 Parcel 数据多了调用 Stream 的 I/O 操作
传递是靠这两句

intent.putExtra("myserializabledata", persion);
startActivity(intent);

先看第一句

  1. Intent.putExtra
public Intent putExtra(String name, Serializable value) {
        if (mExtras == null) {
            mExtras = new Bundle();
        }
        //调用了 Bundle.putSerializable
        mExtras.putSerializable(name, value);
        return this;
    }
  1. -> Bundle.putSerializable
@Override
public void putSerializable(@Nullable String key, @Nullable Serializable value) {
    //调用了BaseBundle.putSerializable
    super.putSerializable(key, value);
}
  1. -> BaseBundle.putSerializable
void putSerializable(@Nullable String key, @Nullable Serializable value) {
    unparcel();
    //数据存在 BaseBundle.mMap
    mMap.put(key, value);
}

最后将 Serializable 存在了 BaseBundle 的 mMap.

再看第二句

  1. startActivity 可以理解为最后由 ActivityManager 执行, 而ActivityManager在源码中各版本不一定一致, 先看4.4.4的
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, String profileFile,
            ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);

        ...

        if (options != null) {
            data.writeInt(1);
            //这里调用 Bundle.writeToParcel
            options.writeToParcel(data, 0);
        } else {
            data.writeInt(0);
        }

        ...

        return result;
    }
  1. -> Bundle.writeToParcel
@Override
 public void writeToParcel(Parcel parcel, int flags) {
     final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
     try {
       //调用 BaseBundle.writeToParcelInner
         super.writeToParcelInner(parcel, flags);
     } finally {
         parcel.restoreAllowFds(oldAllowFds);
     }
 }
  1. -> BaseBundle.writeToParcelInner(parcel, flags)
void writeToParcelInner(Parcel parcel, int flags) {
        // Keep implementation in sync with writeToParcel() in
        // frameworks/native/libs/binder/PersistableBundle.cpp.
        final Parcel parcelledData;
        synchronized (this) {
            parcelledData = mParcelledData;
        }
        if (parcelledData != null) {
            if (isEmptyParcel()) {
                parcel.writeInt(0);
            } else {
                int length = parcelledData.dataSize();
                parcel.writeInt(length);
                parcel.writeInt(BUNDLE_MAGIC);
                parcel.appendFrom(parcelledData, 0, length);
            }
        } else {
            // Special case for empty bundles.
            if (mMap == null || mMap.size() <= 0) {
                parcel.writeInt(0);
                return;
            }
            int lengthPos = parcel.dataPosition();
            parcel.writeInt(-1); // dummy, will hold length
            parcel.writeInt(BUNDLE_MAGIC);

            int startPos = parcel.dataPosition();
            // 调用 Parcel.writeArrayMapInternal
            parcel.writeArrayMapInternal(mMap);
            int endPos = parcel.dataPosition();

            // Backpatch length
            parcel.setDataPosition(lengthPos);
            int length = endPos - startPos;
            parcel.writeInt(length);
            parcel.setDataPosition(endPos);
        }
    }
  1. -> Parcel.writeArrayMapInternal(mMap)
void writeArrayMapInternal(ArrayMap<String, Object> val) {
    if (val == null) {
        writeInt(-1);
        return;
    }
    // Keep the format of this Parcel in sync with writeToParcelInner() in
    // frameworks/native/libs/binder/PersistableBundle.cpp.
    final int N = val.size();
    writeInt(N);
    if (DEBUG_ARRAY_MAP) {
        RuntimeException here =  new RuntimeException("here");
        here.fillInStackTrace();
        Log.d(TAG, "Writing "   N   " ArrayMap entries", here);
    }
    int startPos;
    for (int i=0; i<N; i  ) {
        if (DEBUG_ARRAY_MAP) startPos = dataPosition();
        writeString(val.keyAt(i));
        //开始操作 map 中的 value
        writeValue(val.valueAt(i));
        if (DEBUG_ARRAY_MAP) Log.d(TAG, "  Write #"   i   " "
                  (dataPosition()-startPos)   " bytes: key=0x"
                  Integer.toHexString(val.keyAt(i) != null ? val.keyAt(i).hashCode() : 0)
                  " "   val.keyAt(i));
    }
}

这里操作了第一步生成的 mMAp

  1. -> parcel.writeValue
public final void writeValue(Object v) {
       if (v == null) {
           writeInt(VAL_NULL);
       } else if (v instanceof String) {
           writeInt(VAL_STRING);
           writeString((String) v);
       } else if (v instanceof Integer) {
           writeInt(VAL_INTEGER);
           writeInt((Integer) v);
       } else if (v instanceof Map) {
           writeInt(VAL_MAP);
           writeMap((Map) v);
       } else if (v instanceof Bundle) {
           // Must be before Parcelable
           writeInt(VAL_BUNDLE);
           writeBundle((Bundle) v);
       } else if (v instanceof PersistableBundle) {
           writeInt(VAL_PERSISTABLEBUNDLE);
           writePersistableBundle((PersistableBundle) v);
       } else if (v instanceof Parcelable) {
           // IMPOTANT: cases for classes that implement Parcelable must
           // come before the Parcelable case, so that their specific VAL_*
           // types will be written.
           writeInt(VAL_PARCELABLE);
           writeParcelable((Parcelable) v, 0);
       } else {
           Class<?> clazz = v.getClass();
           if (clazz.isArray() && clazz.getComponentType() == Object.class) {
               // Only pure Object[] are written here, Other arrays of non-primitive types are
               // handled by serialization as this does not record the component type.
               writeInt(VAL_OBJECTARRAY);
               writeArray((Object[]) v);
           } else if (v instanceof Serializable) {
               // Must be last
               writeInt(VAL_SERIALIZABLE);
               //对 Serializable 操作
               writeSerializable((Serializable) v);
           } else {
               throw new RuntimeException("Parcel: unable to marshal value "   v);
           }
       }
   }  

在writeValue的最后,处理了 Serializable

  1. -> Parcel.writeSerializable()
public final void writeSerializable(Serializable s) {
        if (s == null) {
            writeString(null);
            return;
        }
        String name = s.getClass().getName();
        writeString(name);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(s);
            oos.close();
            //将Serializable I/O操作后转换成字节流
            writeByteArray(baos.toByteArray());
        } catch (IOException ioe) {
            throw new RuntimeException("Parcelable encountered "  
                "IOException writing serializable object (name = "   name  
                ")", ioe);
        }
    }
  1. -> Parcel.writeByteArray()
public final void writeByteArray(byte[] b) {
     //对流判断
     writeByteArray(b, 0, (b != null) ? b.length : 0);
 }
  1. -> Parcel.nativeWriteByteArray()
public final void writeByteArray(byte[] b, int offset, int len) {
   if (b == null) {
       writeInt(-1);
       return;
   }
   Arrays.checkOffsetAndCount(b.length, offset, len);
   //调用了 native 方法
   nativeWriteByteArray(mNativePtr, b, offset, len);
}

所以,在能使用的Parcelable的地方,请不要贪图简便直接Serializable,实在懒的话也可以试试用插件自动生成Pracelabel的模板代码:android-parcelable-intellij-plugin

Intent

Intent 的 bundle 使用Binder机制进行数据传递, 能使用Binder的缓冲区有大小限制, 有些手机是2M
一个进程默认有16个 Binder线程, 所以一个线程所能占用的缓冲区更小了(大约一个线程128KB), 所以当出现The Binder transaction failed because it was too large, 说明数据太大.
因此Intent传递List和Bitmap对象是存在风险的

参考

  • 进程与内存地址
  • Android中Parcel的分析以及使用
  • 如何理解 java.io.Serializable 接口
  • 我对java中Serializable接口的理解
  • Android Binder 分析——数据传递者(Parcel)

做为面试官,紧接着可以问:除了传递基本类型外,如何传递自定义的对象呢?

查看 serialVersionUID 的方法
  1. .dat方法查询 [Link](https://link.jianshu.com?t=http://bbs.csdn.net/topics/392093574) 不推荐
  2. serialver 命令 Link
    cd 到 java 文件目录, java文件直接 javac 类名.class编译, 并 serialver 类名, android 需要将文件删掉包名, 并没有父类或父类也跟着复制过来

不同类型的数据不一定差据这么大,但却很直观的展示了Pacelable比Serializable高效。

有两个成员
uint8_t* mData; //用来存储序列化流数据,可以把它理解成共享内存
size_t* mObjects;  //用来存储IBinder和FileDescriptor

引用和IBinder的序列化方式不一样

美高梅网投平台 1

有时面试官还可以追问一下:Parcelable和Parcle这两者之间的关系。

还有一个要注意的:因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化。之前我有过一次,将Android的PackageInfo进行持久化到数据库,结果用户升级Android系统后,再从数据库解封PackageInfo时应用就Crash了。

这个问题就是想引出Android的Parcelable。一般很多面试者都有用过传递实现了Serializable接口的自定义对象的经验,因为这个很简单,加句代码就搞定了。而Parcelable的实现要多一些代码,典型的写法如下:

使用Intent的Bundle协带参数,就是我们常用的Intent.putExtra方法。

有人比较过它们两个的效率差别:

在Android应用中,Activity占有极其重要的地位,Activity间的跳转更是加常便饭。即然跳转不可避免,那么在两个Activity之间传递参数就是一个常见的需求。大多数时候,我们也就传递一些简单的int,String类型的数据,实际中也有看到传递List和Bitmap的。

那我们为什么要考察对方会不会用Parcelable呢?先看一下这Parcelable和Serializable的区别:

面试官可以就这个问题再展开,看面试者如何解决。

TAG标签:
版权声明:本文由美高梅网投平台发布于美高梅简介,转载请注明出处:进程间通信,Android面试一天一题