Android之Parcelable解析
http://www.cnblogs.com/abinxm/archive/2011/11/16/2250949.html
http://www.cnblogs.com/renqingping/archive/2012/10/25/Parcelable.html
什么是Parcelable以及用法可以从上面两篇文章了解一二,本文关注其背后的实现机制是什么?
- * Interface for classes whose instances can be written to
- * and restored from a {@link Parcel}. Classes implementing the Parcelable
- * interface must also have a static field called <code>CREATOR</code>, which
- * is an object implementing the {@link Parcelable.Creator Parcelable.Creator}
- * interface.
如上,摘自Parcelable注释:如果想要写入Parcel或者从中恢复,则必须implements Parcelable并且必须有一个static field 而且名字必须是CREATOR....
好吧,感觉好复杂。有如下疑问:
1、Parcelable是干啥的?为什么需要它?
2、Parcel又是干啥的?
3、如果是写入Parcel中、从Parcelable中恢复,那要Parcelable岂不是“多此一举”?
下面逐个回答上述问题:
1、Parcelable是干啥的?从源码看:
- public interface Parcelable {
- ...
- public void writeToParcel(Parcel dest, int flags);
- ...
简单来说,Parcelable是一个interface,有一个方法writeToParcel(Parcel dest, int flags),该方法接收两个参数,其中第一个参数类型是Parcel。看起来Parcelable好像是对Parcelable的一种包装,从实际开发中,会在方法writeToParcel中调用Parcel的某些方法,完成将对象写入Parcelable的过程。
为什么往Parcel写入或恢复数据,需要继承Parcelable呢?我们看Intent的putExtra系列方法:
往Intent中添加数据,无法就是添加以上各种类型,简单的数据类型有对应的方法,如putExtra(String, String),复杂一点的有putExtra(String, Bundle),putExtra(String, Serializable)、putExtra(String, Bundle)、putExtra(String, Parcelable)、putExtra(String, Parcelable[])。现在想想,如果往Intent里添加一个我们自定义的类型对象person(Person类的实例),咋整?总不能用putExtra(String,person)吧?为啥,类型不符合啊!如果Person没有基础任何类,那它不可以用putExtra的任何一个方法,比较不存在putExtra(String, Object)这样一个方法。
那为了可以用putExtra方法,Person就需要继承一个可以用putExtra方法的类(接口),可以继承Parcelable——继承其他类(接口)也没有问题。
现在捋一捋:为了使用putExtra方法,需要继承Parcelable类——事实上,还有更深的含义,且看后面。
2、Parcel又是干啥的?前面说过,继承了Parcelable接口的类,如果不是抽象类,必须实现方法 writeToParcel,该方法有一个Parcel类型的参数,Parcel源码:
- public final class Parcel {
- ...
- public static Parcel obtain() {
- final Parcel[] pool = sOwnedPool;
- synchronized (pool) {
- Parcel p;
- for (int i=0; i<POOL_SIZE; i++) {
- p = pool[i];
- if (p != null) {
- pool[i] = null;
- if (DEBUG_RECYCLE) {
- p.mStack = new RuntimeException();
- }
- return p;
- }
- }
- }
- return new Parcel(0);
- }
- ...
- public final native void writeInt(int val);
- public final native void writeLong(long val);
- ...
Parcel是一个final不可继承类,其代码很多,其中重要的一些部分是它有许多native的函数,在writeToParcel中调用的这些方法都直接或间接的调用native函数完成。
现在有一个问题,在public void writeToParcel(Parcel dest, int flags)中调用dest的函数,这个dest是传入进来的,是形参,那实参在哪里?没有看到有什么地方生成了一个Parcel的实例,然后调用writeToParcel啊??那它又不可能凭空出来。现在回到Intent这边来,看看它的内部做了什么事:
- Intent i = new Intent();
- Person person = new Person();
- i.putExtra("person", person);
- i.setClass(this, SecondeActivity.class);
- startActivity(i);
为了简单说明情况,我写了如上的代码,就不解释了。看看putExtra做了什么事情,看源码:
- public Intent putExtra(String name, Parcelable value) {
- if (mExtras == null) {
- mExtras = new Bundle();
- }
- mExtras.putParcelable(name, value);
- return this;
- }
这里调用的putExtra的第二个参数是Parcelable类型的,也印证了前面必须要类型符合(这里多说一句,面向对象的六大原则里有一个非常非常重要的“里氏替换”原则,子类出现的地方可以用父类代替,这样所有继承了Parcelable的类都可以传入这个putExtra中)。原来这里用到了Bundle类,看源码:
- public void putParcelable(String key, Parcelable value) {
- unparcel();
- mMap.put(key, value);
- mFdsKnown = false;
- }
mMap是一个Map。看到这里,原来我们传入的person被写入了Map里面了。这个Bundle也是继承自Parcelable的。其他putExtra系列的方法都是调用这个mMap的put。为什么要用Bundle的类里的Map?统一管理啊!所有传到Intent的extra我都不管,交给Bundle类来管理了,这样Intent类就不会太笨重(面向对象六大原则之迪米特原则——我不管你怎么整,整对了就行)。看Bundle源码:
- public final class Bundle implements Parcelable, Cloneable {
- private static final String LOG_TAG = "Bundle";
- public static final Bundle EMPTY;
//Bundle类一加载就生成了一个Bundle实例- static {
- EMPTY = new Bundle();
- EMPTY.mMap = Collections.unmodifiableMap(new HashMap<String, Object>());
- }
- /* package */ Map<String, Object> mMap = null;
- /* package */ Parcel mParcelledData = null;
看到这里,还是没有发现Parcel实例在什么地方生成,继续往下看,看startActivity这个方法,找到最后会发现最终启动Activity的是一个ActivityManagerNative类,查看对应的方法:
- public int startActivity(IApplicationThread caller, 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实例
- Parcel reply = Parcel.obtain(); //又生成了一个Parcel实例
- data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeStrongBinder(caller != null ? caller.asBinder() : null);
- intent.writeToParcel(data, 0);
- data.writeString(resolvedType);
- data.writeStrongBinder(resultTo);
- data.writeString(resultWho);
- data.writeInt(requestCode);
- data.writeInt(startFlags);
- data.writeString(profileFile);
- if (profileFd != null) {
- data.writeInt(1);
- profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- } else {
- data.writeInt(0);
- }
- if (options != null) {
- data.writeInt(1);
- options.writeToParcel(data, 0);
- } else {
- data.writeInt(0);
- }
- mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
- reply.readException();
- int result = reply.readInt();
- reply.recycle();
- data.recycle();
- return result;
- }
千呼万唤的Parcel对象终于出现了,这里生成了俩Parcel对象:data和reply,主要的是data这个实例。obtain是一个static方法,用于从Parcel池(pool)中找出一个可用的Parcel,如果都不可用,则生成一个新的。每一个Parcel(Java)都与一个C++的Parcel对应。
- public static Parcel obtain() {
- final Parcel[] pool = sOwnedPool;
- synchronized (pool) {
- Parcel p;
- for (int i=0; i<POOL_SIZE; i++) {
- p = pool[i];
- if (p != null) {
- pool[i] = null;
- if (DEBUG_RECYCLE) {
- p.mStack = new RuntimeException();
- }
- return p;
- }
- }
- }
- return new Parcel(0);
- }
- 在intent.writeToParcel(data, 0)里,查看源码:
- public void writeToParcel(Parcel out, int flags) {
- out.writeString(mAction);
- Uri.writeToParcel(out, mData);
- out.writeString(mType);
- out.writeInt(mFlags);
- out.writeString(mPackage);
- ComponentName.writeToParcel(mComponent, out);
- if (mSourceBounds != null) {
- out.writeInt(1);
- mSourceBounds.writeToParcel(out, flags);
- } else {
- out.writeInt(0);
- }
- if (mCategories != null) {
- out.writeInt(mCategories.size());
- for (String category : mCategories) {
- out.writeString(category);
- }
- } else {
- out.writeInt(0);
- }
- if (mSelector != null) {
- out.writeInt(1);
- mSelector.writeToParcel(out, flags);
- } else {
- out.writeInt(0);
- }
- if (mClipData != null) {
- out.writeInt(1);
- mClipData.writeToParcel(out, flags);
- } else {
- out.writeInt(0);
- }
- out.writeBundle(mExtras); //终于把我们自定义的person实例送走了
- }
看到这里Parcel实例终于生成了,但是我们重写的从Parcelable接口而来的writeToParcel这个方法在什么地方被调用呢?从上面的Intent中的out.writeBundle(mExtras)-->writeBundle(Bundle val)-->writeToParcel(Parcel parcel, int flags)-->writeMapInternal(Map<String,Object> val)-->writeValue(Object v)-->writeParcelable(Parcelable p, int parcelableFlags)(除了out.writeBundle(mExtras)这个方法,其他的方法都是在Bundle和Parcel里面调来调去的,真心累!):
- public final void writeParcelable(Parcelable p, int parcelableFlags) {
- if (p == null) {
- writeString(null);
- return;
- }
- String name = p.getClass().getName();
- writeString(name);
- p.writeToParcel(this, parcelableFlags);//调用自己实现的方法
- }
OK,终于出来了。。。到这里,写入的过程已经出来了。
那如何恢复呢?这里用到的是我们自己写的createFromParcel这个方法,从一个Intent中恢复person:
- Intent i= getIntent();
- Person p = i.getParcelableExtra("person");
调啊调,调到这个:
- public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
- String name = readString();
- if (name == null) {
- return null;
- }
- Parcelable.Creator<T> creator;
- synchronized (mCreators) {
- HashMap<String,Parcelable.Creator> map = mCreators.get(loader);
- if (map == null) {
- map = new HashMap<String,Parcelable.Creator>();
- mCreators.put(loader, map);
- }
- creator = map.get(name);
- if (creator == null) {
- try {
- Class c = loader == null ?
- Class.forName(name) : Class.forName(name, true, loader);
- Field f = c.getField("CREATOR");
- creator = (Parcelable.Creator)f.get(null);
- }
- catch (IllegalAccessException e) {
- Log.e(TAG, "Class not found when unmarshalling: "
- + name + ", e: " + e);
- throw new BadParcelableException(
- "IllegalAccessException when unmarshalling: " + name);
- }
- catch (ClassNotFoundException e) {
- Log.e(TAG, "Class not found when unmarshalling: "
- + name + ", e: " + e);
- throw new BadParcelableException(
- "ClassNotFoundException when unmarshalling: " + name);
- }
- catch (ClassCastException e) {
- throw new BadParcelableException("Parcelable protocol requires a "
- + "Parcelable.Creator object called "
- + " CREATOR on class " + name);
- }
- catch (NoSuchFieldException e) {
- throw new BadParcelableException("Parcelable protocol requires a "
- + "Parcelable.Creator object called "
- + " CREATOR on class " + name);
- }
- if (creator == null) {
- throw new BadParcelableException("Parcelable protocol requires a "
- + "Parcelable.Creator object called "
- + " CREATOR on class " + name);
- }
- map.put(name, creator);
- }
- }
- if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
- return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader);
- }
- return creator.createFromParcel(this); //调用我们自定义的那个方法
- }
最后终于从我们自定义的方法中恢复那个保存的实例。
Android之Parcelable解析的更多相关文章
- Android Service完全解析,关于服务你所需知道的一切(下)
转载请注册出处:http://blog.csdn.net/guolin_blog/article/details/9797169 在上一篇文章中,我们学习了Android Service相关的许多重要 ...
- android源码解析(十七)-->Activity布局加载流程
版权声明:本文为博主原创文章,未经博主允许不得转载. 好吧,终于要开始讲讲Activity的布局加载流程了,大家都知道在Android体系中Activity扮演了一个界面展示的角色,这也是它与andr ...
- 【转】Android Service完全解析,关于服务你所需知道的一切(下) ---- 不错
原文网址:http://blog.csdn.net/guolin_blog/article/details/9797169 转载请注册出处:http://blog.csdn.net/guolin_bl ...
- Android中Parcelable序列化总结
在使用Parcelable对android中数据的序列化操作还是比较有用的,有人做过通过对比Serializable和Parcelable在android中序列化操作对象的速度比对,大概Parcela ...
- 《Android源代码设计模式解析》读书笔记——Android中你应该知道的设计模式
断断续续的,<Android源代码设计模式解析>也看了一遍.书中提到了非常多的设计模式.可是有部分在开发中见到的几率非常小,所以掌握不了也没有太大影响. 我认为这本书的最大价值有两点,一个 ...
- [转]Android Service完全解析,关于服务你所需知道的一切
目录(?)[+] Android Service完全解析,关于服务你所需知道的一切(上) 分类: Android疑难解析2013-10-31 08:10 6451人阅读 评论(39) 收藏 举报 ...
- 【Android开发精要笔记】Android组件模型解析
Android组件模型解析 Android中的Mashup 将应用切分成不同类别的组件,通过统一的定位模型和接口标准将他们整合在一起,来共同完成某项任务.在Android的Mashup模式下,每个组件 ...
- Android Service完全解析,关于服务你所需知道的一切(下) (转载)
转自:http://blog.csdn.net/guolin_blog/article/details/9797169 转载请注册出处:http://blog.csdn.net/guolin_blog ...
- Android Service完全解析,关于服务你所需知道的一切(上)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11952435 相信大多数朋友对Service这个名词都不会陌生,没错,一个老练的A ...
随机推荐
- Linux系统诊断必备技能之三:查看信息系统常用命令
一.概述 Linux操作系统的学习中,CLI下进行操作,需要掌握大量命令,Linux的命令有很多,对于命令的学习大家记住只能是熟能生巧,所以现在把日常使用命令为大家罗列一部分,仅供参考. 二.常用命令 ...
- Redis之哨兵机制(sentinel)——配置详解及原理介绍
说到Redis不得不提哨兵模式,那么究竟哨兵是什么意思?为什么要使用哨兵呢? 接下来一一为您讲解: 1.为什么要用到哨兵 哨兵(Sentinel)主要是为了解决在主从(master-slave)复制架 ...
- 解决宝塔面板没有命令行问题 && 查看宝塔面板项目环境
# 宝塔面板没有命令行,无法查看错误输出 利用ssh.比如xshell,MObaxtern .输入ip,username,password就可以进入服务器的命令行. # 查看项目的环境 服务器默认的p ...
- Spring(三) Spring IOC 初体验
Web IOC 容器初体验 我们还是从大家最熟悉的 DispatcherServlet 开始,我们最先想到的还是 DispatcherServlet 的 init() 方法.我们发现在 Dispath ...
- μC/OS-III---I笔记13---中断管理
中断管理先看一下最常用的临界段进入的函数:进入临界段 OS_CRITICAL_ENTER() 退出临界段OS_CRITICAL_EXIT()他们两个的宏是这样的. 在使能中断延迟提交时: #if OS ...
- CSS Grid & Flex poster PDF 海报定制
CSS Grid & Flex poster PDF 海报定制 CSS 手工实现 导出 SVG / PNG 导出 PDF 打印,定制海报 refs https://css-tricks.com ...
- TypeScript 4.1 Quick Start Tutorials
TypeScript 4.1 Quick Start Tutorials TypeScript 4.1 快速上手教程 https://typescript-41-quick-start-tutoria ...
- Learning JavaScript with MDN (call, apply, bind)
Learning JavaScript with MDN (call, apply, bind) call, apply, bind Object.prototype.toString() 检测 js ...
- GitHub Classroom
GitHub Classroom GitHub Education https://classroom.github.com/classrooms https://classroom.github.c ...
- js function call hacker
js function call hacker you don't know javascript function https://developer.mozilla.org/en-US/docs/ ...