在android中用intent传递数据是非常常见的操作,我们一般会用intent.putExtra()这个方法来放入自己要传递的数据,然后再另一个地方使用getxxx()来获取,其中intent.putExtra()的传参类型有很多种:

Intent  putExtra(String name, String[] value)Intent  putExtra(String name, Parcelable value)Intent  putExtra(String name, long value)Intent  putExtra(String name, boolean value)Intent  putExtra(String name, double value)Intent  putExtra(String name, Parcelable[] value)Intent  putExtra(String name, char value)Intent  putExtra(String name, int[] value)Intent  putExtra(String name, int value)Intent  putExtra(String name, double[] value)               Intent  putExtra(String name, short value)                  Intent  putExtra(String name, long[] value)                     Intent  putExtra(String name, boolean[] value)                  Intent  putExtra(String name, short[] value)                    Intent  putExtra(String name, String value)                     Intent  putExtra(String name, Serializable value)                   Intent  putExtra(String name, float[] value)                    Intent  putExtra(String name, Bundle value)                     Intent  putExtra(String name, byte[] value)                     Intent  putExtra(String name, CharSequence value)                   Intent  putExtra(String name, char[] value)                     Intent  putExtra(String name, byte value)                   Intent  putExtras(Intent src)                   Intent  putExtras(Bundle extras)

可以看到其可以传递的包括基本数据类型(含基本数据类型的数组)、String(包含数组)、Parcelable(包含数组)、Serializable、Bundle、CharSequence、Intent几种类型的数据。我们点进去看下具体实现:

   public Intent putExtra(String name, String value) {        if (mExtras == null) {            mExtras = new Bundle();        }        mExtras.putString(name, value);        return this;    }
    public Intent putExtra(String name, int value) {        if (mExtras == null) {            mExtras = new Bundle();        }        mExtras.putInt(name, value);        return this;    }
    public Intent putExtra(String name, boolean value) {        if (mExtras == null) {            mExtras = new Bundle();        }        mExtras.putBoolean(name, value);        return this;    }

我们点开了三个Intent putExtra(String name, String value),Intent putExtra(String name, int value),Intent putExtra(String name, boolean value)发现其实原理都一样,他们都通过new Bundle.putxxx()来实现的,也就是说传进来的这些数据都是通过Bundle这个容器来装然后传递,在Intent类里面维护了一个Bundle对象mExtras,如果intent已经携带了Bundle对象,那么直接向里面存储数据,否则就新建一个Bundle对象, 点开 mExtras.putxxx()方法我们会发现,

 public void putxxxx(@Nullable String key, value) {        unparcel();        mMap.put(key, value);    }

其实在BaseBundle里面维护了一个ArrayMap

ArrayMap<String, Object> mMap = null;

我们intent的put操作和get操作就是对Bundle里面的ArrayMap进行mMap.put(key, value);mMap.get(key);操作,那Bundle到底是个什么东东呢?我们要实现intent传递对象改怎么做呢?Bundle我们可以看作是一个存储可传输的数据的容器,什么是可传输的呢?我的理解是像基本数据类型本身就可以直接转换为字节流,所以是可传输的,还有就是实现实现序列化接口(String本身就实现了Serializable接口),在android中实现序列化接口有两种方式

  • 实现Serializable接口
  • 实现Parcelable接口
    其中实现Serializable接口在javase就已经支持,而Parcelable是Android特有的功能,效率比实现Serializable接口高。那他们有什么区别?

  • 作用

Serializable的作用是为了保存对象的属性到本地文件、数据库、网络流、rmi以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
从上面的设计上我们就可以看出优劣了。

  • 效率及选择

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

总结:所以在传递对象时对于需要传递的对象的序列化选择可以加以区分,需要数据持久化的建议实现Serializable接口,只在内存间数据传输时推荐使用Parcelable。

  • 编程实现
    -实现Serializable接口
    对于对于Serializable,类只需要实现Serializable接口,并提供一个序列化版本id(serialVersionUID)即可。,有人又会问了id(serialVersionUID)干啥的?
    (Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。添加serialVersionUID使得在序列化时保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。)
    下面一个bean是实现Serializable接口的一个样例:
/** * ============================================================================= * Copyright (c) 2016 yuxin All rights reserved. * Packname com.jju.yuxin.disanzhou * Created by yuxin. * Created time 2016/9/18 0018 下午 10:29. * Version   1.0; * Describe : * History: * ============================================================================== */public class Person implements Serializable{    private static final long serialVersionUID = 1L;    private int id;    private String name;    public Person() {    }    public Person(int id, String name) {        this.id = id;        this.name = name;    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

intent数据的传递:

 // 封装数据        Person p = new Person();        p.setId(320840);        p.setName("小伙子");        Intent i = new Intent(MainActivity.this, FirstActivity.class);        i.putExtra("Person", p);        startActivity(i);    

接收数据:

Person p = (Person)getIntent().getSerializableExtra("Person");              System.out.println("身份证"+p.getId());       System.out.println("姓名"+p.getName()); 

-实现Parcelable接口
实现Parcelable接口稍微复杂一些,但效率更高,下面是一个实现Parcelable接口的一个样例:

/** * ============================================================================= * Copyright (c) 2016 yuxin All rights reserved. * Packname com.jju.yuxin.disanzhou * Created by yuxin. * Created time 2016/9/18 0018 下午 9:47. * Version   1.0; * Describe : * History: * ============================================================================== */public class Student implements Parcelable {    private int id;    private String name;    public Student() {    }    public Student(int id, String name) {        this.id = id;        this.name = name;    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(this.id);        dest.writeString(this.name);    }    protected Student(Parcel in) {        this.id = in.readInt();        this.name = in.readString();    }    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {        @Override        public Student createFromParcel(Parcel source) {            return new Student(source);        }        @Override        public Student[] newArray(int size) {            return new Student[size];        }    };}

intent数据的传递:

 // 封装数据        Student s = new Student();        s.setId(320840);        s.setName("小伙子");        Intent i = new Intent(MainActivity.this, FirstActivity.class);        i.putExtra("Student", s);        startActivity(i); 

数据的接收:

Student s = (Student)getIntent().getParcelableExtra("Student");              System.out.println("学号:"+s.getId());       System.out.println("姓名:"+s.getName()); 

我们发现在实现Parcelable接口比较复杂在于要实现的几个方法不清楚是什么意思,我们来看下:

    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(this.id);        dest.writeString(this.name);    }    protected Student(Parcel in) {        this.id = in.readInt();        this.name = in.readString();    }    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {        @Override        public Student createFromParcel(Parcel source) {            return new Student(source);        }        @Override        public Student[] newArray(int size) {            return new Student[size];        }    };}

我们可以看到在方法中有很多个Parcel对象。这个类是用来封装数据的容器,封装后的数据可以通过Intent或IPC传递,除了基本类型外,只有实现了Parcelable接口的类才能放入parcel中,也就是说实现了Parcelable接口后可以把数据打包成Parcel对象对象来传递,并且只有实现了Parcelable接口的类才能放入parcel中,那实现了Parcelable接口的对象是如何打包和读取的呢?我们会发现两个方法

  • public void writeToParcel(Parcel dest, int flags);
    这个方法就是传入一个空的Parcel对象,然后将我们要存储的对象的属性写进Parcel中去(注意写入顺序,因为在读取是时要按照写入顺序来读取)
 @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(this.id);        dest.writeString(this.name);    }

还有一个就是:
- public Student createFromParcel(Parcel source)
从方法名我们就可以看出他是从Parcel对象中获取值来创建一个我们需要传递的的Student 对象

 @Override        public Student createFromParcel(Parcel source) {            return new Student(source);        }

它这调用了Student的构造方法

  protected Student(Parcel in) {        this.id = in.readInt();        this.name = in.readString();    }

也就是说对于对象传递时需要打包成Parcel对象传递,而打包和解包的过程都是我们自己来写的。

题外话:对于实现Parcelable接口实现方法十分容易写错,如果你用android studio的话,安装个*Android
Parcelable code generator*插件在你要实现Parcelable接口的bean上按住
alt+insert选择Parcelable,就可以快速生成Parcelable代码

总结上面所有intent的数据传递的重写的方法,我们会发现都是将数据放入Bundle中然后传递的,那Bundle究竟是什么呢?他是怎么工作的?在Android 系统中所有进程间通信不是基于Binder机制嘛?而允许数据在进程间传递不是基于Parcel的吗?关Bundle和Bundle里面的ArrayMap什么事?我们来看下之前我们一直忽略的一个方法, unparcel();在所有map的putxxx()之前我们都会看到这个方法

 public void putxxxx(@Nullable String key, value) {        unparcel();        mMap.put(key, value);    }

点开unparcel()

    /**     * If the underlying data are stored as a Parcel, unparcel them     * using the currently assigned class loader.     */    /* package */ synchronized void unparcel() {        if (mParcelledData == null) {            if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))                    + ": no parcelled data");            return;        }        if (mParcelledData == EMPTY_PARCEL) {            if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))                    + ": empty");            if (mMap == null) {                mMap = new ArrayMap(1);            } else {                mMap.erase();            }            mParcelledData = null;            return;        }        int N = mParcelledData.readInt();        if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))                + ": reading " + N + " maps");        if (N < 0) {            return;        }        if (mMap == null) {            mMap = new ArrayMap(N);        } else {            mMap.erase();            mMap.ensureCapacity(N);        }        mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);        mParcelledData.recycle();        mParcelledData = null;        if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))                + " final map: " + mMap);    }

我们会发现BaseBundle不光维护了一个ArrayMap还有,Parcel对象
,那BaseBundle是如何将传进来的数据传给Parcel的呢?我们又看到了一个方法void writeToParcelInner(Parcel parcel, int flags)这方法就类似与我们自己实现Parcelable接口里面实现的打包方法public void writeToParcel(Parcel dest, int flags)

 /**     * Writes the Bundle contents to a Parcel, typically in order for     * it to be passed through an IBinder connection.     * @param parcel The parcel to copy this bundle to.     */    void writeToParcelInner(Parcel parcel, int flags) {        if (mParcelledData != null) {            if (mParcelledData == EMPTY_PARCEL) {                parcel.writeInt(0);            } else {                int length = mParcelledData.dataSize();                parcel.writeInt(length);                parcel.writeInt(BUNDLE_MAGIC);                parcel.appendFrom(mParcelledData, 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(mMap);            int endPos = parcel.dataPosition();            // Backpatch length            parcel.setDataPosition(lengthPos);            int length = endPos - startPos;            parcel.writeInt(length);            parcel.setDataPosition(endPos);        }    }

在 parcel.writeArrayMapInternal(mMap);时候将map数据写入了Parcel,有打包的过程,那一定就有解包的过程

  /**     * Reads the Parcel contents into this Bundle, typically in order for     * it to be passed through an IBinder connection.     * @param parcel The parcel to overwrite this bundle from.     */    void readFromParcelInner(Parcel parcel) {        int length = parcel.readInt();        if (length < 0) {            throw new RuntimeException("Bad length in parcel: " + length);        }        readFromParcelInner(parcel, length);    }    private void readFromParcelInner(Parcel parcel, int length) {        if (length == 0) {            // Empty Bundle or end of data.            mParcelledData = EMPTY_PARCEL;            return;        }        int magic = parcel.readInt();        if (magic != BUNDLE_MAGIC) {            //noinspection ThrowableInstanceNeverThrown            throw new IllegalStateException("Bad magic number for Bundle: 0x"                    + Integer.toHexString(magic));        }        // Advance within this Parcel        int offset = parcel.dataPosition();        parcel.setDataPosition(offset + length);        Parcel p = Parcel.obtain();        p.setDataPosition(0);        p.appendFrom(parcel, offset, length);        if (DEBUG) Log.d(TAG, "Retrieving "  + Integer.toHexString(System.identityHashCode(this))                + ": " + length + " bundle bytes starting at " + offset);        p.setDataPosition(0);        mParcelledData = p;    }

总总结:我们会发现Intent的参数传递是先将参数传给一个ArrayMap,然后再将ArrayMap打包成一个Parcel在进程间传递,在实现Parcelable接口时我们知道,打包和解包是我们自己定义的,其实是一个将值复制给Parcel对象并在获取的时候解包的过程,所以Intent的参数传递后其实已经不是原来的那个参数了,传过来的只是一个复制品。

我的博客网站:http://huyuxin.top/欢迎大家访问!评论!

更多相关文章

  1. Android(安卓)循环ListView
  2. Android用代码实现EditText不可编辑
  3. Android的两种数据存储方式分析(一)
  4. Android学习之使用RadioGroup与RadioButton实现单选效果
  5. Android统计EditText的字母数字以及汉字的统计方法
  6. Android实现数据存储技术
  7. 浅谈Java中Collections.sort对List排序的两种方法
  8. mybatisplus的坑 insert标签insert into select无参数问题的解决
  9. python起点网月票榜字体反爬案例

随机推荐

  1. App优化之提升你的App启动速度之实例挑战
  2. android 使用getDrawable和BitmapFactory
  3. android studio导入项目报错Plugin with
  4. Android(安卓)AIDl来实现进程间通讯
  5. android开发EditText输入时弹出数字输入
  6. perl 解析 android 字符串资源的 %1$s 匹
  7. 浅析Android中的visibility属性
  8. Android(安卓)mdpi hdpi xhdpi xxhdpi
  9. android ActivityGroup认识
  10. android 中如何获取camera当前状态