作為Android開(kāi)發(fā)者我們都知道Android應(yīng)用方法數(shù)65535這樣一個(gè)限制划乖,這是因?yàn)樵贏ndroid系統(tǒng)中,方法的id使用short類(lèi)型存儲(chǔ)在Dex文件中,而short類(lèi)型的取值范圍是-32,768到32,767梦碗,因此導(dǎo)致Android應(yīng)用方法數(shù)65535這樣一個(gè)最大限制,超過(guò)這個(gè)方法是打包就會(huì)失敗闸度,也可以使用MultiDexApplication來(lái)解決,但這不是最優(yōu)的解決方案蚜印,也可是用插件化來(lái)解決這個(gè)問(wèn)題(稍后我會(huì)分享插件化)莺禁,個(gè)人覺(jué)得最好是在開(kāi)發(fā)中盡量避免這樣的問(wèn)題。
1.為什么要減少方法數(shù)
這要從dex的文件格式說(shuō)起窄赋,在把源碼編譯哟冬、轉(zhuǎn)化為dex文件格式時(shí),dex文件中會(huì)有一個(gè)區(qū)域包含了所有源碼中定義或引用的方法列表寝凌。這個(gè)區(qū)域中所有方法項(xiàng)的總數(shù)就是方法數(shù)柒傻。
Android在剛開(kāi)始被設(shè)計(jì)的時(shí)候,這一區(qū)域的方法數(shù)量不能超過(guò)65536個(gè)较木,也就是2個(gè)字節(jié)表示的范圍。當(dāng)源碼定義或引用的方法數(shù)量超過(guò)了這個(gè)限制的話青柄,就會(huì)導(dǎo)致編譯不成功伐债,你說(shuō)重要不重要呢预侯?
2.如何查看這些方法?
dexdump 命令:
查看apk的method總數(shù) ?dexdump ?-f ?app.apk | grepmethod_ids_size ??
查看apk的field總數(shù)dexdump ?-f ?app.apk | grepfield_ids_size
查看apk的method詳情 ?dexdump -f ?app.apk
可以使用修改過(guò)的dexdump峰锁。標(biāo)準(zhǔn)的dexdump可以解析方法列表萎馅,但無(wú)法打印出來(lái),修改版的dexdump可以打印這些信息
如下是一個(gè)簡(jiǎn)單類(lèi)LogicActivity中使用的方法
Class: Lcom/xxx/activity/LogicActivity; 18
Method: ()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.減少方法數(shù)的辦法
以下所介紹的方法都可以在修改后峭竣,用dexdump –j來(lái)觀察、比較所修改的方法以及驗(yàn)證減少的效果晃虫。
方法1 避免在內(nèi)部類(lèi)中訪問(wèn)外部類(lèi)的私有方法/變量
當(dāng)在Java內(nèi)部類(lèi)(包括內(nèi)部匿名類(lèi))中訪問(wèn)外部類(lèi)的私有方法/變量時(shí)皆撩,編譯器會(huì)生成額外的方法,這也會(huì)增加方法數(shù)哲银,建議編碼時(shí)盡量避免扛吞。
具體原因:
考慮如下的代碼
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語(yǔ)言允許內(nèi)部類(lèi)直接訪問(wèn)外部類(lèi)的方法,但是虛擬機(jī)卻認(rèn)為Foo和Foo$Inner是兩個(gè)不同的類(lèi)荆责,為了支持Foo$Inner訪問(wèn)Foo的private成員滥比,編譯器會(huì)生成兩個(gè)額外的方法,而生成的這些方法也算在方法總數(shù)里面
/*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
解決辦法:
很簡(jiǎn)單做院,把mValue和doStuff()的private修飾符去掉就好了盲泛,這樣它的默認(rèn)訪問(wèn)域?yàn)榘?jí),編譯器就不需要生成額外的代碼山憨。
方法2 避免調(diào)用派生類(lèi)中的未被覆蓋(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);
}
}
}
實(shí)際上它會(huì)生成5個(gè)方法查乒,除了定義的onCreate和構(gòu)造函數(shù)之外,還有setContentView郁竟、getIntent()和finish()玛迄。因?yàn)榘凑誮ava的語(yǔ)義,如果有覆蓋父類(lèi)的方法棚亩,則會(huì)直接調(diào)用覆蓋的方法蓖议。
Class: Lcom/xxx/activity/DemoActivity; 5
Method: ()V
Method: finish ()V
Method: getIntent ()Landroid/content/Intent;
Method: onCreate (Landroid/os/Bundle;)V
Method: setContentView (I)V
解決辦法:
對(duì)于不需要被override的方法,顯式的改成調(diào)用父類(lèi)的方法讥蟆,如下所示
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);
}
}
}
則實(shí)際在方法數(shù)列表中它只占2個(gè)方法
Class: Lcom/tencent/mobileqq/activity/DemoActivity; 2
Method: ()V
Method: onCreate (Landroid/os/Bundle;)V