通常,我们启动一个组件时,通过下面的代码实现。
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数据结构中。

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。

再看一下数据读取。
当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。

总结
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存储。
任重而道远
网友评论