Android中方法數(shù)超限問題與啟動(dòng)優(yōu)化詳解
前言
最近寫了篇有關(guān)Eclipse工程轉(zhuǎn)Android Studio工程的文章,而導(dǎo)致公司項(xiàng)目需要轉(zhuǎn) AS 的直接原因,就是今天要寫的主題–方法數(shù)超限,相信大多數(shù) Android 項(xiàng)目的都會(huì)碰到這個(gè)問題。
傳統(tǒng)的 Eclipse 解決方法數(shù)超限的辦法,就是在 project.properties 中加上 dex.force.jumbo=true ,然后清理工程重新編譯。但是,當(dāng)方法數(shù)越來越多,這個(gè)方法也會(huì)解決不了問題,這個(gè)時(shí)候,就要用到 Google 官方給出的方案 MultiDex 了。
MultiDex 解決方案
一、 如果你的 minSdkVersion >= 21 ,那么只要在模塊的 build.gradle 中添加:
android {
defaultConfig {
...
multiDexEnabled true
}
...
}
二、 如果你的 minSdkVersion < 21 ,那么只要在模塊的 build.gradle 中添加:
android {
defaultConfig {
...
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
然后,如果你沒有寫自己的 Application 類,那么你只要寫上自己的 Application 類,并繼承 MultiDexApplication ;如果你寫過自己的 Application 類,并且/或者不希望 Application 類繼承 MultiDexApplication ,那么你需要重寫 Application 的 attachBaseContext 方法:
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(context);
Multidex.install(this);
}
谷歌multiDex存在的問題
雖然谷歌的分包方案很簡(jiǎn)單,但是效果并不是那么好,谷歌本身也枚舉了分包方案的缺點(diǎn):
- 如果在主線程中執(zhí)行MultiDex.install,加載second dex,因?yàn)榧虞d從dex是同步的,會(huì)阻塞線程,second dex太大的話,有可能導(dǎo)致ANR
- API Level 14之前,由于Dalvik LinearAlloc bug(問題22586,就是上文提到的LinearAlloc問題),很可能會(huì)出問題的
- 應(yīng)用程序使用了multiedex配置的,會(huì)造成使用比較大的內(nèi)存
- 對(duì)于應(yīng)用程序比較復(fù)雜的,存在較多的library的項(xiàng)目。multidex可能會(huì)造成不同依賴項(xiàng)目間的dex文件函數(shù)相互調(diào)用,找不到方法
啟動(dòng)優(yōu)化
官方的解決方案雖然簡(jiǎn)單,但是也存在一定的局限。比如,首次加載應(yīng)用時(shí),由于需要加載 DEX 文件,會(huì)消耗較多的時(shí)間,導(dǎo)致啟動(dòng)速度慢,影響用戶體驗(yàn),甚至很可能引發(fā) ANR 。
針對(duì)加載 Dex 問題,美團(tuán)技術(shù)團(tuán)隊(duì)是這樣做的:精簡(jiǎn)主 Dex 包,應(yīng)用啟動(dòng)起來后再異步加載第二個(gè) Dex 包。這是一個(gè)很不錯(cuò)的想法,但是實(shí)現(xiàn)起來有一定的難度。需要編寫腳本,區(qū)分哪些類要放在主 Dex 包中,而且一般項(xiàng)目中都會(huì)用到很多第三方 SDK,這很可能導(dǎo)致主 Dex 包的精簡(jiǎn)程度不能達(dá)到我們想要的狀態(tài)。
當(dāng)然,除此之外,還有更適合我們的方案,微信開發(fā)團(tuán)隊(duì)的解決思路就很有意思,他們逆了不少 APP,最后參考并改進(jìn)了 Facebook 的解決方案。大概的思路就是,新開一個(gè)進(jìn)程來執(zhí)行 Multidex.install() 方法。
微信開發(fā)團(tuán)隊(duì)的思路實(shí)現(xiàn)起來也比較簡(jiǎn)單,下面直接上我的代碼(順便把啟動(dòng)體驗(yàn)也優(yōu)化了~):
Application 中的 attachBaseContext 方法:
@Override
protected void attachBaseContext(Context context) {
super.attachBaseContext(context);
if (MultiDexUtils.isMainProcess(context)) { // 判斷是否是主進(jìn)程,避免重復(fù)執(zhí)行
MultiDexUtils.setMainActivityStarted(this, false); // 保存本地?cái)?shù)據(jù),標(biāo)記主頁(yè)面是否已經(jīng)開啟
MultiDexUtils.setLoadDexActivityClosed(this, false); // 保存本地?cái)?shù)據(jù),標(biāo)記加載Dex進(jìn)程是否已經(jīng)關(guān)閉
MultiDexUtils.startLoadDexActivity(context); // 打開加載 Dex 的進(jìn)程頁(yè)面,這樣我們的APP就變成后臺(tái)進(jìn)程了
}
}
加載 Dex 的進(jìn)程:
public class LoadDexActivity extends Activity {
private static final int MULTIDEX_ERROR = 0;
private static final int MULTIDEX_ACTIVITY_STARTED = 1;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MULTIDEX_ERROR:
case MULTIDEX_ACTIVITY_STARTED: // 退出當(dāng)前進(jìn)程
MultiDexUtils.setLoadDexActivityClosed(getApplication());
finish();
System.exit(0);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loaddex);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
new Thread() {
@Override
public void run() {
Message message = handler.obtainMessage();
long startTime = System.currentTimeMillis();
long timeout = 10 * 1000; // 加載超時(shí)時(shí)間
try {
MultiDex.install(getApplication());
Thread.sleep(500);
// 等待主界面啟動(dòng)
while (!MultiDexUtils.isMainActivityStarted(getApplication()) &&
(System.currentTimeMillis() - startTime) < timeout) {
Thread.sleep(200);
}
message.what = MULTIDEX_ACTIVITY_STARTED;
handler.sendMessage(message);
} catch (Exception e) {
message.what = MULTIDEX_ERROR;
handler.sendMessage(message);
}
}
}.start();
}
@Override
public void onBackPressed() {
//cannot backpress
}
}
Manifest 中 LoadDexActivity 的配置:
<activity android:name=".LoadDexActivity" android:alwaysRetainTaskState="false" android:excludeFromRecents="true" android:launchMode="singleTask" android:process=":loadDex" android:screenOrientation="portrait"> </activity>
主界面 onCreate 方法:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
MultiDexUtils.setMainActivityStarted(getApplication()); // 告訴loadDex進(jìn)程,主界面已啟動(dòng)
...
}
MultiDexUtils 工具類:
public class MultiDexUtils {
public static final String KEY_ACTIVITY_STARTED = "activity-started-";
public static final String KEY_LOADDEX_CLOSED = "loaddex-closed-";
/**
* 當(dāng)前進(jìn)程是否是主進(jìn)程
*/
public static boolean isMainProcess(Context context) {
return "com.***.***(進(jìn)程名一般是包名)".equals(getCurProcessName(context));
}
/**
* 設(shè)置-主界面已經(jīng)打開
*/
public static void setMainActivityStarted(Context context) {
setMainActivityStarted(context, true);
}
/**
* 設(shè)置-主界面是否已經(jīng)打開
*/
public static void setMainActivityStarted(Context context, boolean b) {
SharedPreferences sp = context.getSharedPreferences("multidex", Context.MODE_MULTI_PROCESS);
sp.edit().putBoolean(KEY_ACTIVITY_STARTED + getPackageInfo(context).versionCode, b).commit();
}
/**
* 是否需要等待主界面
*/
public static boolean isMainActivityStarted(Context context) {
SharedPreferences sp = context.getSharedPreferences("multidex", Context.MODE_MULTI_PROCESS);
return sp.getBoolean(KEY_ACTIVITY_STARTED + getPackageInfo(context).versionCode, false);
}
/**
* 判斷加載頁(yè)面是否關(guān)閉
*/
public static boolean isLoadDexActivityClosed(Context context) {
SharedPreferences sp = context.getSharedPreferences("multidex", Context.MODE_MULTI_PROCESS);
return sp.getBoolean(KEY_LOADDEX_CLOSED + getPackageInfo(context).versionCode, false);
}
/**
* 設(shè)置加載頁(yè)面已經(jīng)關(guān)閉
*/
public static void setLoadDexActivityClosed(Context context) {
setLoadDexActivityClosed(context, true);
}
/**
* 設(shè)置-加載頁(yè)面是否已經(jīng)關(guān)閉
*/
public static void setLoadDexActivityClosed(Context context, boolean b) {
SharedPreferences sp = context.getSharedPreferences("multidex", Context.MODE_MULTI_PROCESS);
sp.edit().putBoolean(KEY_LOADDEX_CLOSED + getPackageInfo(context).versionCode, b).commit();
}
/**
* 開啟等待頁(yè)面,新的進(jìn)程
*/
public static void startLoadDexActivity(Context context) {
Intent intent = new Intent();
ComponentName componentName = new ComponentName("com.***.***(包名)", LoadDexActivity.class.getName());
intent.setComponent(componentName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
/**
* 獲取進(jìn)程名
*/
public static String getCurProcessName(Context context) {
try {
int pid = android.os.Process.myPid();
ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) {
if (appProcess.pid == pid) {
return appProcess.processName;
}
}
} catch (Exception e) {
// ignore
}
return null;
}
/**
* 獲取包信息
*/
private static PackageInfo getPackageInfo(Context context) {
PackageManager pm = context.getPackageManager();
try {
return pm.getPackageInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
// Log.i(TAG, e.getLocalizedMessage());
}
return new PackageInfo();
}
}
另一種啟動(dòng)優(yōu)化方案
還有一種簡(jiǎn)單的啟動(dòng)優(yōu)化方案,只能優(yōu)化啟動(dòng)體驗(yàn),并不能解決 ANR 問題。
在點(diǎn)擊桌面圖標(biāo)啟動(dòng)應(yīng)用時(shí),給個(gè)背景圖片,啟動(dòng)完成后,將背景設(shè)回空。
1.在入口 Activity 中加入主題背景
android:theme="@style/SplashTheme"
style.xml 中加入配置:
value:
<style name="SplashTheme" parent="@android:style/Theme.NoTitleBar"> <item name="android:background">@drawable/logo_splash</item> </style>
value-v21:(解決21版本及以上的過度動(dòng)畫效果問題)
<style name="SplashTheme" parent="@android:style/Theme.NoTitleBar.Fullscreen"> <item name="android:windowBackground">@drawable/logo_splash</item> </style>
2.將背景設(shè)回原樣
@Override
public void setTheme(int resid) {
super.setTheme(R.style.CustomTransparent);
}
style.xml 配置如下:
<style name="CustomTransparent" parent="@android:style/Theme.Translucent"> <item name="android:background">@null</item> <item name="android:windowBackground">@color/curve_floater_frameColor</item> </style>
參考
- 其實(shí)你不知道 MultiDex 到底有多坑
- 美團(tuán) Android DEX 自動(dòng)拆包及動(dòng)態(tài)加載簡(jiǎn)介
- Android 拆分與加載 Dex 的多種方案對(duì)比
- 配置方法數(shù)超過 64K 的應(yīng)用
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Android編程實(shí)現(xiàn)類似天氣預(yù)報(bào)圖文字幕垂直滾動(dòng)效果的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)類似天氣預(yù)報(bào)圖文字幕垂直滾動(dòng)效果的方法,涉及Android基于布局及事件響應(yīng)實(shí)現(xiàn)圖文滾動(dòng)效果的相關(guān)操作技巧,需要的朋友可以參考下2017-08-08
Android中WebChromeClient和WebViewClient的區(qū)別淺析
這篇文章主要介紹了Android中WebChromeClient和WebViewClient的區(qū)別淺析,需要的朋友可以參考下2015-04-04
Android 簡(jiǎn)單實(shí)現(xiàn)一個(gè)流式布局的示例
本篇文章主要介紹了Android 簡(jiǎn)單實(shí)現(xiàn)一個(gè)流式布局的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02
Android自定義控件之圓形/圓角的實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android自定義控件之圓形/圓角的實(shí)現(xiàn)代碼,感興趣的小伙伴們可以參考一下2016-03-03
Android編程實(shí)現(xiàn)夜間模式的方法小結(jié)
這篇文章主要介紹了Android編程實(shí)現(xiàn)夜間模式的方法,涉及Android亮度調(diào)節(jié)、自定義Theme、遮罩模式等相關(guān)使用技巧,需要的朋友可以參考下2018-01-01

