http://www.cnblogs.com/abinxm/archive/2011/11/16/2250949.html

http://www.cnblogs.com/renqingping/archive/2012/10/25/Parcelable.html

什么是Parcelable以及用法可以从上面两篇文章了解一二,本文关注其背后的实现机制是什么?

  1. * Interface for classes whose instances can be written to
  2. * and restored from a {@link Parcel}. Classes implementing the Parcelable
  3. * interface must also have a static field called <code>CREATOR</code>, which
  4. * is an object implementing the {@link Parcelable.Creator Parcelable.Creator}
  5. * interface.

如上,摘自Parcelable注释:如果想要写入Parcel或者从中恢复,则必须implements Parcelable并且必须有一个static field 而且名字必须是CREATOR....

好吧,感觉好复杂。有如下疑问:

1、Parcelable是干啥的?为什么需要它?

2、Parcel又是干啥的?

3、如果是写入Parcel中、从Parcelable中恢复,那要Parcelable岂不是“多此一举”?

下面逐个回答上述问题:

1、Parcelable是干啥的?从源码看:

  1. public interface Parcelable {
  2. ...
  3. public void writeToParcel(Parcel dest, int flags);
  4. ...

简单来说,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源码:

  1. public final class Parcel {
  2. ...
  3. public static Parcel obtain() {
  4. final Parcel[] pool = sOwnedPool;
  5. synchronized (pool) {
  6. Parcel p;
  7. for (int i=0; i<POOL_SIZE; i++) {
  8. p = pool[i];
  9. if (p != null) {
  10. pool[i] = null;
  11. if (DEBUG_RECYCLE) {
  12. p.mStack = new RuntimeException();
  13. }
  14. return p;
  15. }
  16. }
  17. }
  18. return new Parcel(0);
  19. }
  20. ...
  21. public final native void writeInt(int val);
  22.  
  23. public final native void writeLong(long val);
  24.  
  25. ...

Parcel是一个final不可继承类,其代码很多,其中重要的一些部分是它有许多native的函数,在writeToParcel中调用的这些方法都直接或间接的调用native函数完成。

现在有一个问题,在public void writeToParcel(Parcel dest, int flags)中调用dest的函数,这个dest是传入进来的,是形参,那实参在哪里?没有看到有什么地方生成了一个Parcel的实例,然后调用writeToParcel啊??那它又不可能凭空出来。现在回到Intent这边来,看看它的内部做了什么事:

  1. Intent i = new Intent();
  2. Person person = new Person();
  3. i.putExtra("person", person);
  4. i.setClass(this, SecondeActivity.class);
  5. startActivity(i);

为了简单说明情况,我写了如上的代码,就不解释了。看看putExtra做了什么事情,看源码:

  1. public Intent putExtra(String name, Parcelable value) {
  2. if (mExtras == null) {
  3. mExtras = new Bundle();
  4. }
  5. mExtras.putParcelable(name, value);
  6. return this;
  7. }

这里调用的putExtra的第二个参数是Parcelable类型的,也印证了前面必须要类型符合(这里多说一句,面向对象的六大原则里有一个非常非常重要的“里氏替换”原则,子类出现的地方可以用父类代替,这样所有继承了Parcelable的类都可以传入这个putExtra中)。原来这里用到了Bundle类,看源码:

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

mMap是一个Map。看到这里,原来我们传入的person被写入了Map里面了。这个Bundle也是继承自Parcelable的。其他putExtra系列的方法都是调用这个mMap的put。为什么要用Bundle的类里的Map?统一管理啊!所有传到Intent的extra我都不管,交给Bundle类来管理了,这样Intent类就不会太笨重(面向对象六大原则之迪米特原则——我不管你怎么整,整对了就行)。看Bundle源码:

  1. public final class Bundle implements Parcelable, Cloneable {
  2. private static final String LOG_TAG = "Bundle";
  3. public static final Bundle EMPTY;

  4.   //Bundle类一加载就生成了一个Bundle实例
  5. static {
  6. EMPTY = new Bundle();
  7. EMPTY.mMap = Collections.unmodifiableMap(new HashMap<String, Object>());
  8. }
  9. /* package */ Map<String, Object> mMap = null;
  10.  
  11. /* package */ Parcel mParcelledData = null;

看到这里,还是没有发现Parcel实例在什么地方生成,继续往下看,看startActivity这个方法,找到最后会发现最终启动Activity的是一个ActivityManagerNative类,查看对应的方法:

  1. public int startActivity(IApplicationThread caller, Intent intent,
  2. String resolvedType, IBinder resultTo, String resultWho, int requestCode,
  3. int startFlags, String profileFile,
  4. ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
  5. Parcel data = Parcel.obtain(); //在这里生成了Parcel实例
  6. Parcel reply = Parcel.obtain(); //又生成了一个Parcel实例
  7. data.writeInterfaceToken(IActivityManager.descriptor);
  8. data.writeStrongBinder(caller != null ? caller.asBinder() : null);
  9. intent.writeToParcel(data, 0);
  10. data.writeString(resolvedType);
  11. data.writeStrongBinder(resultTo);
  12. data.writeString(resultWho);
  13. data.writeInt(requestCode);
  14. data.writeInt(startFlags);
  15. data.writeString(profileFile);
  16. if (profileFd != null) {
  17. data.writeInt(1);
  18. profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  19. } else {
  20. data.writeInt(0);
  21. }
  22. if (options != null) {
  23. data.writeInt(1);
  24. options.writeToParcel(data, 0);
  25. } else {
  26. data.writeInt(0);
  27. }
  28. mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
  29. reply.readException();
  30. int result = reply.readInt();
  31. reply.recycle();
  32. data.recycle();
  33. return result;
  34. }

千呼万唤的Parcel对象终于出现了,这里生成了俩Parcel对象:data和reply,主要的是data这个实例。obtain是一个static方法,用于从Parcel池(pool)中找出一个可用的Parcel,如果都不可用,则生成一个新的。每一个Parcel(Java)都与一个C++的Parcel对应。

  1. public static Parcel obtain() {
  2. final Parcel[] pool = sOwnedPool;
  3. synchronized (pool) {
  4. Parcel p;
  5. for (int i=0; i<POOL_SIZE; i++) {
  6. p = pool[i];
  7. if (p != null) {
  8. pool[i] = null;
  9. if (DEBUG_RECYCLE) {
  10. p.mStack = new RuntimeException();
  11. }
  12. return p;
  13. }
  14. }
  15. }
  16. return new Parcel(0);
  17. }
  1. intent.writeToParcel(data, 0)里,查看源码:
  1. public void writeToParcel(Parcel out, int flags) {
  2. out.writeString(mAction);
  3. Uri.writeToParcel(out, mData);
  4. out.writeString(mType);
  5. out.writeInt(mFlags);
  6. out.writeString(mPackage);
  7. ComponentName.writeToParcel(mComponent, out);
  8.  
  9. if (mSourceBounds != null) {
  10. out.writeInt(1);
  11. mSourceBounds.writeToParcel(out, flags);
  12. } else {
  13. out.writeInt(0);
  14. }
  15.  
  16. if (mCategories != null) {
  17. out.writeInt(mCategories.size());
  18. for (String category : mCategories) {
  19. out.writeString(category);
  20. }
  21. } else {
  22. out.writeInt(0);
  23. }
  24.  
  25. if (mSelector != null) {
  26. out.writeInt(1);
  27. mSelector.writeToParcel(out, flags);
  28. } else {
  29. out.writeInt(0);
  30. }
  31.  
  32. if (mClipData != null) {
  33. out.writeInt(1);
  34. mClipData.writeToParcel(out, flags);
  35. } else {
  36. out.writeInt(0);
  37. }
  38.  
  39. out.writeBundle(mExtras); //终于把我们自定义的person实例送走了
  40. }

看到这里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里面调来调去的,真心累!):

  1. public final void writeParcelable(Parcelable p, int parcelableFlags) {
  2. if (p == null) {
  3. writeString(null);
  4. return;
  5. }
  6. String name = p.getClass().getName();
  7. writeString(name);
  8. p.writeToParcel(this, parcelableFlags);//调用自己实现的方法
  9. }

OK,终于出来了。。。到这里,写入的过程已经出来了。

那如何恢复呢?这里用到的是我们自己写的createFromParcel这个方法,从一个Intent中恢复person:

  1. Intent i= getIntent();
  2. Person p = i.getParcelableExtra("person");

调啊调,调到这个:

  1. public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
  2. String name = readString();
  3. if (name == null) {
  4. return null;
  5. }
  6. Parcelable.Creator<T> creator;
  7. synchronized (mCreators) {
  8. HashMap<String,Parcelable.Creator> map = mCreators.get(loader);
  9. if (map == null) {
  10. map = new HashMap<String,Parcelable.Creator>();
  11. mCreators.put(loader, map);
  12. }
  13. creator = map.get(name);
  14. if (creator == null) {
  15. try {
  16. Class c = loader == null ?
  17. Class.forName(name) : Class.forName(name, true, loader);
  18. Field f = c.getField("CREATOR");
  19. creator = (Parcelable.Creator)f.get(null);
  20. }
  21. catch (IllegalAccessException e) {
  22. Log.e(TAG, "Class not found when unmarshalling: "
  23. + name + ", e: " + e);
  24. throw new BadParcelableException(
  25. "IllegalAccessException when unmarshalling: " + name);
  26. }
  27. catch (ClassNotFoundException e) {
  28. Log.e(TAG, "Class not found when unmarshalling: "
  29. + name + ", e: " + e);
  30. throw new BadParcelableException(
  31. "ClassNotFoundException when unmarshalling: " + name);
  32. }
  33. catch (ClassCastException e) {
  34. throw new BadParcelableException("Parcelable protocol requires a "
  35. + "Parcelable.Creator object called "
  36. + " CREATOR on class " + name);
  37. }
  38. catch (NoSuchFieldException e) {
  39. throw new BadParcelableException("Parcelable protocol requires a "
  40. + "Parcelable.Creator object called "
  41. + " CREATOR on class " + name);
  42. }
  43. if (creator == null) {
  44. throw new BadParcelableException("Parcelable protocol requires a "
  45. + "Parcelable.Creator object called "
  46. + " CREATOR on class " + name);
  47. }
  48.  
  49. map.put(name, creator);
  50. }
  51. }
  52.  
  53. if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
  54. return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader);
  55. }
  56. return creator.createFromParcel(this); //调用我们自定义的那个方法
  57. }

最后终于从我们自定义的方法中恢复那个保存的实例。

Android之Parcelable解析的更多相关文章

  1. Android Service完全解析,关于服务你所需知道的一切(下)

    转载请注册出处:http://blog.csdn.net/guolin_blog/article/details/9797169 在上一篇文章中,我们学习了Android Service相关的许多重要 ...

  2. android源码解析(十七)-->Activity布局加载流程

    版权声明:本文为博主原创文章,未经博主允许不得转载. 好吧,终于要开始讲讲Activity的布局加载流程了,大家都知道在Android体系中Activity扮演了一个界面展示的角色,这也是它与andr ...

  3. 【转】Android Service完全解析,关于服务你所需知道的一切(下) ---- 不错

    原文网址:http://blog.csdn.net/guolin_blog/article/details/9797169 转载请注册出处:http://blog.csdn.net/guolin_bl ...

  4. Android中Parcelable序列化总结

    在使用Parcelable对android中数据的序列化操作还是比较有用的,有人做过通过对比Serializable和Parcelable在android中序列化操作对象的速度比对,大概Parcela ...

  5. 《Android源代码设计模式解析》读书笔记——Android中你应该知道的设计模式

    断断续续的,<Android源代码设计模式解析>也看了一遍.书中提到了非常多的设计模式.可是有部分在开发中见到的几率非常小,所以掌握不了也没有太大影响. 我认为这本书的最大价值有两点,一个 ...

  6. [转]Android Service完全解析,关于服务你所需知道的一切

      目录(?)[+] Android Service完全解析,关于服务你所需知道的一切(上) 分类: Android疑难解析2013-10-31 08:10 6451人阅读 评论(39) 收藏 举报 ...

  7. 【Android开发精要笔记】Android组件模型解析

    Android组件模型解析 Android中的Mashup 将应用切分成不同类别的组件,通过统一的定位模型和接口标准将他们整合在一起,来共同完成某项任务.在Android的Mashup模式下,每个组件 ...

  8. Android Service完全解析,关于服务你所需知道的一切(下) (转载)

    转自:http://blog.csdn.net/guolin_blog/article/details/9797169 转载请注册出处:http://blog.csdn.net/guolin_blog ...

  9. Android Service完全解析,关于服务你所需知道的一切(上)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11952435 相信大多数朋友对Service这个名词都不会陌生,没错,一个老练的A ...

随机推荐

  1. Linux系统诊断必备技能之三:查看信息系统常用命令

    一.概述 Linux操作系统的学习中,CLI下进行操作,需要掌握大量命令,Linux的命令有很多,对于命令的学习大家记住只能是熟能生巧,所以现在把日常使用命令为大家罗列一部分,仅供参考. 二.常用命令 ...

  2. Redis之哨兵机制(sentinel)——配置详解及原理介绍

    说到Redis不得不提哨兵模式,那么究竟哨兵是什么意思?为什么要使用哨兵呢? 接下来一一为您讲解: 1.为什么要用到哨兵 哨兵(Sentinel)主要是为了解决在主从(master-slave)复制架 ...

  3. 解决宝塔面板没有命令行问题 && 查看宝塔面板项目环境

    # 宝塔面板没有命令行,无法查看错误输出 利用ssh.比如xshell,MObaxtern .输入ip,username,password就可以进入服务器的命令行. # 查看项目的环境 服务器默认的p ...

  4. Spring(三) Spring IOC 初体验

    Web IOC 容器初体验 我们还是从大家最熟悉的 DispatcherServlet 开始,我们最先想到的还是 DispatcherServlet 的 init() 方法.我们发现在 Dispath ...

  5. μC/OS-III---I笔记13---中断管理

    中断管理先看一下最常用的临界段进入的函数:进入临界段 OS_CRITICAL_ENTER() 退出临界段OS_CRITICAL_EXIT()他们两个的宏是这样的. 在使能中断延迟提交时: #if OS ...

  6. CSS Grid & Flex poster PDF 海报定制

    CSS Grid & Flex poster PDF 海报定制 CSS 手工实现 导出 SVG / PNG 导出 PDF 打印,定制海报 refs https://css-tricks.com ...

  7. TypeScript 4.1 Quick Start Tutorials

    TypeScript 4.1 Quick Start Tutorials TypeScript 4.1 快速上手教程 https://typescript-41-quick-start-tutoria ...

  8. Learning JavaScript with MDN (call, apply, bind)

    Learning JavaScript with MDN (call, apply, bind) call, apply, bind Object.prototype.toString() 检测 js ...

  9. GitHub Classroom

    GitHub Classroom GitHub Education https://classroom.github.com/classrooms https://classroom.github.c ...

  10. js function call hacker

    js function call hacker you don't know javascript function https://developer.mozilla.org/en-US/docs/ ...