現在的Android應用越來越龐大,開發者越來越能感受到方法數限制帶來的影響。這裏簡單講幾個減少方法數的小建議。
1.什麼是方法數?爲什麼它這麼重要?
這要從dex的文件格式說起,在把源碼編譯、轉化爲dex文件格式時,dex文件中會有一個區域包含了所有源碼中定義或引用的方法列表。這個區域中所有方法項的總數就是方法數。
很遺憾,Android在剛開始被設計的時候,這一區域的方法數量不能超過65536個,也就是2個字節表示的範圍。當源碼定義或引用的方法數量超過了這個限制的話,就會導致編譯不成功,你說重要不重要呢?
2.如何查看這些方法?
可以使用修改過的dexdump。標準的dexdump可以解析方法列表,但無法打印出來,修改版的dexdump可以打印這些信息
如下是一個簡單類LogicActivity中使用的方法
Class: Lcom/xxx/activity/LogicActivity; 18
Method: <init> ()V
Method: access$000 (Lcom/xxx/activity/LogicActivity;)Lcom/xxx/app/AppInterface;
Method: access$100 (Lcom/xxx/activity/LogicActivity;)Lcom/xxx/app/AppInterface;
Method: addFriend (Ljava/lang/String;ILjava/lang/String;)V
Method: addObserver (Lcom/xxx/app/BusinessObserver;)V
Method: finish ()V
Method: getIntent ()Landroid/content/Intent;
Method: getString (I)Ljava/lang/String;
Method: getTitleBarHeight ()I
Method: joinTroop ()V
Method: onActivityResult (IILandroid/content/Intent;)V
Method: onCreate (Landroid/os/Bundle;)V
Method: onDestroy ()V
Method: removeObserver (Lcom/xxx/app/BusinessObserver;)V
Method: setLastActivityName ()Ljava/lang/String;
Method: setResult (ILandroid/content/Intent;)V
Method: startActivity (Landroid/content/Intent;)V
Method: startActivityForResult (Landroid/content/Intent;I)V
可以看出,這裏面的方法是包含代碼中引用的方法的,如finish(),getIntent()這些方法。
3.減少方法數的辦法
以下所介紹的方法都可以在修改後,用dexdump –j來觀察、比較所修改的方法以及驗證減少的效果。
方法1 避免在內部類中訪問外部類的私有方法/變量
當在Java內部類(包括內部匿名類)中訪問外部類的私有方法/變量時,編譯器會生成額外的方法,這也會增加方法數,建議編碼時儘量避免。
具體原因:
考慮如下的代碼
publicclassFoo{
privateclassInner{
void stuff(){
Foo.this.doStuff(Foo.this.mValue);
}
}
privateint mValue;
publicvoid run(){
Innerin=newInner();
mValue =27;
in.stuff();
}
privatevoid doStuff(int value){
System.out.println("Value is "+ value);
}
}
雖然Java語言允許內部類直接訪問外部類的方法,但是虛擬機卻認爲Foo和Foo$Inner是兩個不同的類,爲了支持Foo$Inner訪問Foo的private成員,編譯器會生成兩個額外的方法,而生成的這些方法也算在方法總數裏面
/*package*/staticintFoo.access$100(Foo foo){
return foo.mValue;
}
/*package*/staticvoidFoo.access$200(Foo foo,int value){
foo.doStuff(value);
}
具體可以參考:http://developer.android.com/training/articles/perf-tips.html#PackageInner
解決辦法:
很簡單,把mValue和doStuff()的private修飾符去掉就好了,這樣它的默認訪問域爲包級,編譯器就不需要生成額外的代碼。
方法2 避免調用派生類中的未被覆蓋(override)的方法
考慮下面的代碼
publicclass DemoActivity extends Activity {
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent.getAction().equals("add")) {
finish();
}
else {
setContentView(R.id.background);
}
}
}
實際上它會生成5個方法,除了定義的onCreate和構造函數之外,還有setContentView、getIntent()和finish()。因爲按照java的語義,如果有覆蓋父類的方法,則會直接調用覆蓋的方法。
Class: Lcom/xxx/activity/DemoActivity; 5
Method: <init> ()V
Method: finish ()V
Method: getIntent ()Landroid/content/Intent;
Method: onCreate (Landroid/os/Bundle;)V
Method: setContentView (I)V
解決辦法:
對於不需要被override的方法,顯式的改成調用父類的方法,如下所示
publicclass DemoActivity extends Activity {
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = super.getIntent();
if (intent.getAction().equals("add")) {
super.finish();
}
else {
super.setContentView(R.id.background);
}
}
}
則實際在方法數列表中它只佔2個方法
Class: Lcom/tencent/mobileqq/activity/DemoActivity; 2
Method: <init> ()V
Method: onCreate (Landroid/os/Bundle;)V
其實減少方法數還有很多辦法,比如插件化之類的,後面有空再介紹。
另外,手機QQ空間求Android開發,有意者可以直接發送簡歷到郵箱 [email protected],或者聯繫QQ 115818782,註明來意。