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