I created a simple application, a counter app, that upon pressing a button increments a integer by one and updates a textview. The code can be seen below:
public class MainActivity extends Activity {
public static int count = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = (TextView) findViewById(R.id.count);
textView.setText(Integer.toString(count));
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
count++;
textView.setText(Integer.toString(count));
}
});
}
...
}
After decompiling the same app with dex2jar and jd-gui i received the following code back:
public class MainActivity extends Activity {
public static int count = 0;
protected void onCreate(final Bundle paramBundle) {
super.onCreate(paramBundle);
setContentView(2130903040);
paramBundle = (TextView)findViewById(2131296257);
paramBundle.setText(Integer.toString(count));
((Button)findViewById(2131296256)).setOnClickListener(new View.OnClickListener() {
public void onClick(View paramAnonymousView) {
MainActivity.count += 1;
paramBundle.setText(Integer.toString(MainActivity.count));
}
});
}
...
}
On the following line:
paramBundle = (TextView)findViewById(2131296257);
paramBundle.setText(Integer.toString(count));
How is it possible for the system to set the textview to the paramBundle? And why is this happening? paramBundle is of type Bundle and TextView is not a subclass of Bundle, further more Bundle is final according to the decompiled version. Did something go wrong upon decompiling? Is the information from the decompiler wrong or why do we get this result?
Edit:
# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
.locals 3
.param p1, "savedInstanceState" # Landroid/os/Bundle;
.prologue
.line 17
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 18
const/high16 v2, 0x7f030000
invoke-virtual {p0, v2}, Lcom/example/rawa/helloworld/MainActivity;->setContentView(I)V
.line 20
const v2, 0x7f090001
invoke-virtual {p0, v2}, Lcom/example/rawa/helloworld/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v1
check-cast v1, Landroid/widget/TextView;
.line 21
.local v1, "textView":Landroid/widget/TextView;
sget v2, Lcom/example/rawa/helloworld/MainActivity;->count:I
invoke-static {v2}, Ljava/lang/Integer;->toString(I)Ljava/lang/String;
move-result-object v2
invoke-virtual {v1, v2}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 22
const/high16 v2, 0x7f090000
invoke-virtual {p0, v2}, Lcom/example/rawa/helloworld/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/Button;
.line 23
.local v0, "button":Landroid/widget/Button;
new-instance v2, Lcom/example/rawa/helloworld/MainActivity$1;
invoke-direct {v2, p0, v1}, Lcom/example/rawa/helloworld/MainActivity$1;-><init>(Lcom/example/rawa/helloworld/MainActivity;Landroid/widget/TextView;)V
invoke-virtual {v0, v2}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 30
return-void
.end method
I'm definitely no smali expert, only a novice. But i decoded the application using apktool as well and received the smali code above. From my understanding, the savedinstance (paramBundle) is loaded in to p1(=v3) and used in onCreate, and it is not used in any way in line 20 or 21. To me this point towards a decompling error? Keep in mind that apktool allows for building the application again and thus no data may be lost when decompiling.
The cause is that the type of local variables has changed, but some decompilers fail to handle it correctly.
Here's your onCreate
code decompiled with dex2jar + javap:
protected void onCreate(android.os.Bundle);
Code:
0: aload_0
1: aload_1
2: invokespecial #20 // Method android/app/Activity.onCreate:(Landroid/os/Bundle;)V
5: aload_0
6: ldc #21 // int 2130903040
8: invokevirtual #25 // Method setContentView:(I)V
11: aload_0
12: ldc #26 // int 2131230720
14: invokevirtual #30 // Method findViewById:(I)Landroid/view/View;
17: checkcast #32 // class android/widget/TextView
20: astore_1
21: aload_1
22: getstatic #12 // Field count:I
25: invokestatic #38 // Method java/lang/Integer.toString:(I)Ljava/lang/String;
28: invokevirtual #42 // Method android/widget/TextView.setText:(Ljava/lang/CharSequence;)V
31: aload_0
32: ldc #43 // int 2131230721
34: invokevirtual #30 // Method findViewById:(I)Landroid/view/View;
37: checkcast #45 // class android/widget/Button
40: new #6 // class com/example/test/MainActivity$1
43: dup
44: aload_0
45: aload_1
46: invokespecial #48 // Method com/example/test/MainActivity$1."<init>":(Lcom/example/test/MainActivity;Landroid/widget/TextView;)V
49: invokevirtual #52 // Method android/widget/Button.setOnClickListener:(Landroid/view/View$OnClickListener;)V
52: return
Here aload_0
is local variable for MainActivity object, and aload_1
is local variable for Bundle object. The source of the problem occured at code #20, when it stores the reference of just-retrieved TextView object into local variable 1 (astore_1
), which was previously storing the Bundle object!
This is done since the Bundle object is not used for the rest of the method, therefore it's more efficient to reuse its local variable instead of a new variable. However it also means that decompilers need to work extra hard to produce correct Java code.