[读书笔记]intent.putExtra的使用与原理分析

版权声明:本文为博主原创文章,允许转载,但转载必须注明出处。 https://blog.csdn.net/DayDayPlayPhone/article/details/52579577

在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<Student> CREATOR = new Parcelable.Creator<Student>() {
        @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<Student> CREATOR = new Parcelable.Creator<Student>() {
        @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<String, Object>(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<String, Object>(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/欢迎大家访问!评论!

阅读更多

没有更多推荐了,返回首页