美文网首页
Intent数据传递原理

Intent数据传递原理

作者: gczxbb | 来源:发表于2018-06-04 17:10 被阅读44次

通常,我们启动一个组件时,通过下面的代码实现。

Intent intent = new Intent(this,XxxActivity.class);
intent.putExtra(key,value);

Bundle bundle = new Bundle();
bundle.putInt(key,value);
bundle.putString(key,value);
intent.putExtras(bundle);

startActivity(intent);

实现组件跳转,Intent携带被启动的目标组件信息,此外,它还可以实现组件之间的数据传递,包括简单数据和复杂复杂数据类型。Intent的数据传递涉及进程间通信,复杂数据类型必须实现Parcelable或Serializable接口。在Android平台组件间数据传递时,Parcelable效率高于Serializable,Serializable产生的临时变量会引起频繁GC,在数据持久化存储本地或网络时常用。本文主要介绍一下Intent的数据传递原理。


数据存储结构

从上面的示例代码中看出,有两种方式将数据写入Intent。
第一种是Intent的putExtra方法,一对普通的key和value。value可以是int,short,float,String简单类型。也可以是xxxBean复杂实体类型。
第二种是Intent的putExtras方法,一个Bundle对象,它封装多对key和value。

public Intent putExtra(String name, int value) {
    //mExtras是内部Bundle。
    if (mExtras == null) {
        mExtras = new Bundle();
    }
    mExtras.putInt(name, value);
    return this;
}

将简单的键值对key、value存入内部Bundle,具体来说是Bundle的ArrayMap中,它是一种Map数据结构。下面是Bundle的putInt方法。

ArrayMap<String, Object> mMap = null;

public void putInt(String key, int value) {
    unparcel();
    mMap.put(key, value);
}

Bundle继承BaseBundle类,这是BaseBundle中的方法。unparcel方法,将BaseBundle内部Parcel(mParcelledData)解析到mMap中。在Parcel数据读取时使用。组件传递自定义Bundle内部mParcelledData是空,所以,暂不必理会。
ArrayMap存储Intent传入的键值对。当value是复杂类型时,{key:XxxBean}

public Intent putExtra(String name, Parcelable value) {
    if (mExtras == null) {
        mExtras = new Bundle();
    }
    mExtras.putParcelable(name, value);
    return this;
}

与简单类型一样,键值对也是存入Bundle的ArrayMap中。value是实现Parcelable接口的XxxBean实体。下面是Bundle的putParcelable方法。

public void putParcelable(String key, Parcelable value) {
    unparcel();
    mMap.put(key, value);
    mFdsKnown = false;
}

再看一下第二种,Intent的putExtras方法。

public @NonNull Intent putExtras(@NonNull Bundle extras) {
    if (mExtras == null) {
        mExtras = new Bundle();
    }
    mExtras.putAll(extras);
    return this;
}

和第一种方法类似,也是调用内部Bundle的方法。Bundle#putAll方法,将入参Bundle的ArrayMay合并到Intent中Bundle的ArrayMap中。

总之,Intent传递的数据存储位置在内部Bundle的ArrayMap数据结构中。

Intent内部数据存储结构图.jpg

Parcelable接口。

public interface Parcelable {
  
    public int describeContents();
    public void writeToParcel(Parcel dest, int flags);
    
    public interface Creator<T> {
        public T createFromParcel(Parcel source);
        public T[] newArray(int size);
    }
   
    public interface ClassLoaderCreator<T> extends Creator<T> {
        public T createFromParcel(Parcel source, ClassLoader loader);
    }
}

复杂类型必须实现Parcelable接口,才可被写入Parcel。

public class Book implements Parcelable {
    private String bookName;
    private int publishDate;    
    public Book() {
    }
    //set/get方法。

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeString(bookName);
        out.writeInt(publishDate);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }

        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }
    };

    public Book(Parcel in) {
        bookName = in.readString();
        publishDate = in.readInt();
    }
}

Parcelable#writeToParcel方法,将实体类字段写入Parcel对象,创建一个CREATOR,它实现createFromParcel方法,从Parcel中读取实体类字段,赋值给新创建的XxxBean实体。
注意,向Parcel写入数据的XxxBean实体和从Parcel读取数据的实体是两个不同对象。


数据传递原理

下面通过Activity组件跳转为例,分析一下,当数据被写入Intent内部Bundle的ArrayMap时如何实现传递,先看数据写入。
发起者请求启动Activity组件,代理对象ActivityManagerProxy的startActivity方法,Binder通信方式向system_service进程的Ams服务发送请求。

public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
                           ...) throws RemoteException {
    //池中创建两个Parcel对象
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    ...
    data.writeString(callingPackage);
    //用于写入自己传递的数据
    intent.writeToParcel(data, 0);
    data.writeStrongBinder(resultTo);
    ....//写入数据到data后,远程通信
    mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
    int result = reply.readInt();
    reply.recycle();
    data.recycle();
    return result;
}

首先,创建两个Parcel对象,一个用于写入,另一个用于接收。将传输的系统参数,例如callingPackage,resolvedType等,写入Parcel对象(包括String、int、Binder类型),调用各自类型的Parcel#writeXxx方法
然后,将Intent内部数据,例如mAction,mFlags,以及App组件间传递的自定义数据,(包括简单类型和复杂类型),调用Intent的writeToParcel方法,写入Parcel对象。
最后,利用BinderProxy的transact方法将数据发送出去。

public void writeToParcel(Parcel out, int flags) {
    //向Parcel写入Intent中的其他数据,如mAction字符串,mType、mFlags等
    ...
    //App组件间传递自定义数据封装在Bundle中。
    out.writeBundle(mExtras);
}

App组件传递的自定义数据封装在Intent内部Bundle的ArrayMap数组。调用Parcel的writeBundle方法,入参就是Intent内部Bundle。

public final void writeBundle(Bundle val) {
    if (val == null) {
        writeInt(-1);
        return;
    }
    val.writeToParcel(this, 0);
}

Bundle的writeToParcel方法,入参this是写入Parcel对象。Bundle#writeToParcelInner方法,最后,调用Parcel#writeArrayMapInternal方法,将Bundle内部ArrayMap数据写入Parcel。

void writeArrayMapInternal(ArrayMap<String, Object> val) {
    final int N = val.size();
    ...
    for (int i=0; i<N; i++) {
        writeString(val.keyAt(i));
        writeValue(val.valueAt(i));
    }
}

将App组件传递自定义字段数据的每项key与value,分别写入Parcel。
Parcel#writeString方法,写入key,Parcel#writeValue方法,写入value。简单数据的value对应Parcel的writeXxxx方法。复杂数据的value对应Parcel的writeParcelable方法。下面是ArrayMap中的Parcelable复杂数据类型写入。

public final void writeParcelable(Parcelable p, int parcelableFlags) {
    if (p == null) {
        writeString(null);
        return;
    }
    writeParcelableCreator(p);
    p.writeToParcel(this, parcelableFlags);
}

调用实体类XxxBean的writeToParcel,我们在定义实体类实现Parcelable接口时,实现了writeToParcel方法,将XxxBean中的每个字段writeXxx写入Parcel。

组件间传递数据写入Parcel.jpg

再看一下数据读取。
当Activity组件启动后,在onCreate方法,我们可以通过Intent的一系列getXxx方法获取自定义传递数据。

getIntent().getStringExtra(key);
getIntent().getIntExtra(key,defaultValue);
getIntent().getParcelableExtra(key);

这些方法都类似,从Intent内部的Bundle获取数据。下面以复杂类型数据为例。看一下Intent的getParcelableExtra方法获取XxxBean实例对象(实现Parcelable)的过程。

public <T extends Parcelable> T getParcelableExtra(String name) {
    return mExtras == null ? null : mExtras.<T>getParcelable(name);
}

调用Bundle#getParcelable方法,从内部Bundle的ArrayMap读取。

public <T extends Parcelable> T getParcelable(@Nullable String key) {
    unparcel();//解析底层Parcel数据,写入Map
    Object o = mMap.get(key);
    try {
        return (T) o;
    } catch (ClassCastException e) {
        typeWarning(key, o, "Parcelable", e);
        return null;
}

首先,BaseBundle#unparcel方法,如果ArrayMap是空,在这里会初始化。
然后,根据键值key从ArrayMap中读取value。注意,必须unparcel,否则ArrayMap是没有数据的。

synchronized void unparcel() {
    synchronized (this) {
        final Parcel parcelledData = mParcelledData;
        //Bundle内部parcelledData是空时,不用解析
        if (parcelledData == null) {
            return;
        }
        //Bundle内部parcelledData无数据时,不用解析
        if (isEmptyParcel()) {
            if (mMap == null) {
                mMap = new ArrayMap<>(1);
            } else {
                mMap.erase();
            }
            mParcelledData = null;
            return;
        }
        int N = parcelledData.readInt();
        ArrayMap<String, Object> map = mMap;
        //Map为空时创建。
        try {
            parcelledData.readArrayMapInternal(map, N, mClassLoader);
        } catch (BadParcelableException e) {
        } finally {
            mMap = map;
            parcelledData.recycle();
            mParcelledData = null;
        }
    }
}

前面介绍过,在Bundle#putParcelable时也会调用该方法。这里,BaseBundle内部的mParcelledData将不再是空,Parcel#readArrayMapInternal方法,将它的数据解析到ArrayMap结构中。
在解析一次完成后,finally方法会释放Parcel并置空它,因此,Intent多次getXxx时,下一次将判空后不会解析,直接使用ArrayMap。

void readArrayMapInternal(ArrayMap outVal, int N,
        ClassLoader loader) {
    int startPos;
    while (N > 0) {
        String key = readString();//从Parcel读取key
        Object value = readValue(loader);//从Parcel读取value
        outVal.append(key, value);//写入ArrayMap 
        N--;
    }
    outVal.validate();
}

遍历数量N,从Parcel中读取key与value,写入ArrayMap,其中,key是String类型,利用readValue方法读取value。value的类型是Object,简单类型在BaseBundle的getXxx方法中,系统已经帮我们做了转换,Parcelable类型需要我们自己转换成自定义的实体类型。
Parcel#readValue方法,首先读取int字段,判断value的类型,简单类型、Parcelable或Serializable类型,然后,根据类型,找到Parcel#readXxx方法读取。下面看一下Parcel的readParcelable方法。

public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
    Parcelable.Creator<?> creator = readParcelableCreator(loader);
    if (creator == null) {
        return null;
    }
    if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
        Parcelable.ClassLoaderCreator<?> classLoaderCreator =
                (Parcelable.ClassLoaderCreator<?>) creator;
        return (T) classLoaderCreator.createFromParcel(this, loader);
    }
    return (T) creator.createFromParcel(this);
}

根据ClassLoader,查找XxxBean中实现的Creator,然后,调用它的createFromParcel方法,从上面Book代码中可知,创建一个XxxBean实体,传入Parcel,读取Parcel中的实体字段赋值。
再回到上面的readArrayMapInternal方法,将该实体与key一起存入ArrayMap中。

总之,通过unparcel方法,将Parcel的各类型数据全部解析,存储到ArrayMap数据结构。

在分析数据读取时,我们是直接从onCreate方法介入的,这时,Intent、Bundle以及内部Parcel已经存在了,那么他们是何时初始化的呢?
当Ams服务进程利用ApplicationThreadProxy代理回调App进程时,在App进程,将调用ApplicationThreadNative的onTransact方法。

public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
    switch (code) {
    case SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION:
        //创建Intent。
        Intent intent = Intent.CREATOR.createFromParcel(data);
        IBinder b = data.readStrongBinder();
        int ident = data.readInt();
        ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
        ...
        //该方法在ActivityThread中定义的ApplicationThread实体实现。
        scheduleLaunchActivity(intent, b, ident, info, ....);
        return true;
    }
}

ApplicationThreadNative继承Binder,在Binder#execTransact方法,创建两个Parcel对象,在上面的参数data中,已经保存了Ams进程的传递数据。
Intent本身也实现了Parcelable接口,通过内部CREATOR的createFromParcel方法,创建一个新Intent对象。
因此,Intent在Ams进程回调App进程时,发起App进程启动组件生命周期之前已经创建完毕,在ApplicationThread的scheduleLaunchActivity方法传递给App进程主线程。

public static final Parcelable.Creator<Intent> CREATOR
            = new Parcelable.Creator<Intent>() {
    public Intent createFromParcel(Parcel in) {
        return new Intent(in);
    }
};

构造方法,传入携带数据的Parcel。

protected Intent(Parcel in) {
    readFromParcel(in);
}

调用Intent的readFromParcel方法。

public void readFromParcel(Parcel in) {
    setAction(in.readString());
    mType = in.readString();//从Parcel解析数据到Intent内部
    mFlags = in.readInt();
    mPackage = in.readString();
    ...
    mExtras = in.readBundle();//App组件间传递自定义数据
}

根据不同的数据类型,Parcel#readXxx方法,将数据解析,并赋值到新Intent内部,数据包括基本数据和组件间自定义数据Bundle(同写入一致),即mExtras。Parcel#readBundle方法,解析出内部Bundle。

public final Bundle readBundle(ClassLoader loader) {
    ...
    final Bundle bundle = new Bundle(this, length);
    ...
    return bundle;
}

创建Bundle对象,BaseBundle构造方法,根据读取的数据长度,创建ArrayMap对象。

BaseBundle(Parcel parcelledData, int length) {
    readFromParcelInner(parcelledData, length);
}

将Parcel本身作为参数传入。

private void readFromParcelInner(Parcel parcel, int length) {
    if (length == 0) {
        mParcelledData = EMPTY_PARCEL;
        return;
    }
    ...
    Parcel p = Parcel.obtain();
    p.setDataPosition(0);
    ..
    p.setDataPosition(0);
    mParcelledData = p;
}

创建一个新Parcel,将传入的Parcel底层数据合并,最后,将新Parcel赋值Bundle内部mParcelledData

到这里,前面的疑问已经解决了,这个过程我们创建了Intent,它内部的Bundle,以及BaseBundle内部的Parcel。有了这些,就可以再次解析Parcel我们自定义的数据到ArrayMap。

组件间传递数据读取Parcel.jpg

总结

1,组件间利用Intent传递数据,本质是将数据全部写入Parcel。
App应用层将数据交给Intent,其实是保存在Intent内部Bundle的ArrayMap数据结构中。
2,传输时,创建一个Parcel,框架层Intent#writeToParcel的本质是Intent内部Bundle的ArrayMap存储结构,遍历解析每项数据(包括key和value)写入Parcel。
3,复杂Parcelable类型实体传递,本质是在写入String类型的key后,再将实体中每个字段,按照类型writeXxx依次写入Parcel
4,Activity组件启动#onCreate方法,利用getIntent方法,获取的Intent是新建的对象,与发起者组件在startActivity方法传入的Intent是两个不同对象。
5,新Intent对象在当Ams进程回调App进程时创建,在Activity实例化后,通过attach方法,将它赋值到Activity内部,即mIntent,getIntent获取到的就是它。
6,Intent实现Parcelable接口,内部变量从Parcel读取初始化,包括readBundle方法读取内部Bundle。
7, BaseBundle对象创建时,初始化内部Parcel,Bundle#unparcel方法,数据解析交给ArrayMap结构的内部Parcel正是它(mParcelledData)。
8,Parcel是可以跨进程传递的类型,基本数据结构在底层Parcel存储。


任重而道远

相关文章

网友评论

      本文标题:Intent数据传递原理

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