Search code examples
androidproguard

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


My project linked with multiple proguard files. If I look at all proguard files, here's the rules deal with 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;
}

I have the following class file, which is before proguard process.

Before proguard process

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;
}

After proguard process (I get the following code via reverse engineering)

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);
  }
}

As you can see, CREATOR createFromParcel(Parcel) and newArray(int), has been renamed to a(Parcel) and newArray(int)

I expect during parceble process like Bundle.putParcelable, thing will fail because OS system no longer can find createFromParcel.

However, to my surprise, the following line of code still be executed with no issue

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

May I know why it is so? I though Android OS are expected to execute createFromParcel(Parcel). How does the OS know it need to execute a(Parcel) ?


Solution

  • I was curious about this too. It turns out that the compiler creates a synthetic method which points to the obfuscated method. This can be seen if you use apktool to reverse the apk and dig into the smali code.

    Here is the relevant smali for a class Foo that implements Parcelable and is obfuscated such that createFromParcel() -> a().

    # The synthetic method definition
    .method public synthetic createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object;
    
        invoke-virtual {p0, p1}, Lcom/example/Foo$1;->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
    

    Here is the stack trace which shows that the Parcel class calls the synthetic method, which then calls the obfuscated method.

    at com.example.Foo$1.a(SourceFile:52)
        at com.example.Foo$1.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)