Proguard - 为什么不需要保留 Parcelable CREATOR 的 createFromParcel 名称

Proguard - Why preserving createFromParcel name of Parcelable CREATOR isn't necessary

我的项目链接了多个混淆文件。如果我查看所有混淆文件,这里是处理 Parcelable

的规则
-keepclassmembers class * implements android.os.Parcelable {
    public static final ** CREATOR;
}

-keep class * implements android.os.Parcelable {
*;
}

-keepnames class * implements android.os.Parcelable {
    public static final ** CREATOR;
}

我有以下 class 文件,它是在 proguard 进程之前。

proguard 进程之前

package org.yccheok.jstock.engine;

import android.os.Parcel;
import android.os.Parcelable;

/**
 *
 * @author yccheok
 */
public class Code implements Parcelable {
    private Code(String code) {
        this.code = code;
    }

    public static Code newInstance(String code) {
        if (code == null) {
            throw new java.lang.IllegalArgumentException("code cannot be null");
        }

        return new Code(code);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + code.hashCode();

        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }

        if (!(o instanceof Code)) {
            return false;
        }

        return this.code.equals(((Code)o).code);
    }

    @Override
    public String toString() {
        return code;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Handling Parcelable nicely.

    public static final Parcelable.Creator<Code> CREATOR = new Parcelable.Creator<Code>() {
        public Code createFromParcel(Parcel in) {
            android.util.Log.i("CHEOK", "createFromParcel");
            return new Code(in);
        }

        public Code[] newArray(int size) {
            android.util.Log.i("CHEOK", "newArray");
            return new Code[size];
        }
    };

    private Code(Parcel in) {
        code = in.readString();
        android.util.Log.i("CHEOK", "Code parcel " + code);
    }

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

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeString(code);        
    }

    // Handling Parcelable nicely.    
    ////////////////////////////////////////////////////////////////////////////

    private String code;
}

proguard 处理后(我通过逆向工程得到以下代码)

package org.yccheok.jstock.engine;

import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable.Creator;
import android.util.Log;

public class Code
  implements Parcelable
{
  public static final Parcelable.Creator<Code> CREATOR = new Parcelable.Creator()
  {
    public Code a(Parcel paramAnonymousParcel)
    {
      Log.i("CHEOK", "createFromParcel");
      return new Code(paramAnonymousParcel, null);
    }

    public Code[] a(int paramAnonymousInt)
    {
      Log.i("CHEOK", "newArray");
      return new Code[paramAnonymousInt];
    }
  };
  private String code;

  private Code(Parcel paramParcel)
  {
    this.code = paramParcel.readString();
    Log.i("CHEOK", "Code parcel " + this.code);
  }

  private Code(String paramString)
  {
    this.code = paramString;
  }

  public static Code newInstance(String paramString)
  {
    if (paramString == null)
      throw new IllegalArgumentException("code cannot be null");
    return new Code(paramString);
  }

  public int describeContents()
  {
    return 0;
  }

  public boolean equals(Object paramObject)
  {
    if (paramObject == this)
      return true;
    if (!(paramObject instanceof Code))
      return false;
    return this.code.equals(((Code)paramObject).code);
  }

  public int hashCode()
  {
    return 527 + this.code.hashCode();
  }

  public String toString()
  {
    return this.code;
  }

  public void writeToParcel(Parcel paramParcel, int paramInt)
  {
    paramParcel.writeString(this.code);
  }
}

如您所见,CREATOR createFromParcel(Parcel)newArray(int) 已重命名为 a(Parcel)newArray(int)

我预计在像 Bundle.putParcelable 这样的分块过程中,事情会失败,因为 OS 系统不再能找到 createFromParcel

然而,令我惊讶的是,下面这行代码仍然可以正常执行

Log.i("CHEOK", "createFromParcel");

请问为什么会这样?我虽然 Android OS 预计会执行 createFromParcel(Parcel)。 OS 如何知道它需要执行 a(Parcel)

我也很好奇这个。事实证明,编译器创建了一个指向混淆方法的合成方法。这个用apktool反apk,深挖smali代码就可以看出来

这是 class Foo 的相关 smali,它实现了 Parcelable 并且被混淆了 createFromParcel() -> a().

# The synthetic method definition
.method public synthetic createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object;

    invoke-virtual {p0, p1}, Lcom/example/Foo;->a(Landroid/os/Parcel;)Lcom/example/Foo;

    move-result-object v0

    return-object v0
.end method

# The obfuscated method definition
.method public a(Landroid/os/Parcel;)Lcom/example/Foo;

    new-instance v0, Lcom/example/Foo;

    invoke-direct {v0, p1}, Lcom/example/Foo;-><init>(Landroid/os/Parcel;)V

    return-object v0
.end method

这是显示 Parcel class 调用合成方法,然后调用混淆方法的堆栈跟踪。

at com.example.Foo.a(SourceFile:52)
    at com.example.Foo.createFromParcel(SourceFile:49)
    at android.os.Parcel.readParcelable(Parcel.java:2471)
    at android.os.Parcel.readValue(Parcel.java:2365)
    at android.os.Parcel.readListInternal(Parcel.java:2793)
    at android.os.Parcel.readArrayList(Parcel.java:2036)
    at android.os.Parcel.readValue(Parcel.java:2386)
    at android.os.Parcel.readArrayMapInternal(Parcel.java:2732)
    at android.os.BaseBundle.unparcel(BaseBundle.java:269)
    at android.os.BaseBundle.getString(BaseBundle.java:992)