天天看點

(安卓/android)dex2oat與應用安裝時間優化

dex2oat與應用安裝時間優化

背景

  • 4.4之前,android使用dalvik虛拟機,采用JIT(Just-in-time 即時編譯),在運作時将位元組碼即時翻譯成機器碼再執行
  • 5.0開始,android使用art虛拟機,采用AOT(Ahead Of Time 運作前編譯),在安裝時将位元組碼(.dex)翻譯成機器碼(.oat)再執行,提高運作時效率
  • 由于dex2oat過程涉及 讀取dex -> 以類為粒度編譯 -> 生成許多中間檔案 ->合并為.oat(odex)檔案 是以EMMC性能 ,CPU性能, swap區大小, 等硬體性能在一定程度上決定了編譯的速度, 而3561平台性能較弱,編譯時間的增加直接的造成安裝時間提高, 同時應用的熱更新包編譯會占據一定的cpu資源造成系統卡頓,直接影響使用者體驗,本文主要從靈活修改dex2oat排程政策來優化dex2oat過程,而沒有減少dex2oat時間

優化

1.對與安裝應用,根據應用的方法數來決定是否執行dex2oat

  • 如果對所有應用強制不進行dex2oat,能一勞永逸,将安裝時間壓縮到最低,但是這違背了art虛拟機設計的初衷
  • 雖然很多市場上的app體積比較大,但是除去資源檔案,他的類數量和方法數量不一定像體積那麼大,對于方法數,

    較小的應用,去做dex2oat的優化是有必要的,因為與dex2oat時間産生直接關系的是方法數而不是apk體積

  • 我們希望在3561平台上将所有應用的安裝時間控制在1分鐘以内,經過測試和統計,執行dex2oat在一分鐘左右的應用方法數在110000左右,是以對于方法數在110000以下的應用執行dex2oat,對方法數在110000以上的應用禁止dex2oat,以達到性能和安裝時間均衡的狀态
commands.cpp
# 從property中擷取dex2oat方法數邊界值
bool have_dex2oat_num_dex_methods_flag = property_get("fly.dex2oat.num_dex_methods",
                                                              dex2oat_num_dex_methods_flag, NULL) > 0;
# 設定--num-dex-methods參數,
if(have_dex2oat_num_dex_methods_flag){
        sprintf(dex2oat_num_dex_method_arg, "--num-dex-methods=%s", dex2oat_num_dex_methods_flag);
    }


dex2oat.cc   
if (!image_ &&
      compiler_options_->IsCompilationEnabled()) {
      size_t num_methods = 0;
      for (size_t i = 0; i != dex_files_.size(); ++i) {
        const DexFile* dex_file = dex_files_[i];
        CHECK(dex_file != nullptr);
        num_methods += dex_file->NumMethodIds();
      }
      # GetNumDexMethodsThreshold()擷取--num-dex-methods參數
if (num_methods != 0 
      && compiler_options_->GetNumDexMethodsThreshold() != 0 
      && num_methods <= compiler_options_->GetNumDexMethodsThreshold()) {
        # 方法數小于--num-dex-methods則設定為kSpeed,執行dex2oat
        compiler_options_->SetCompilerFilter(CompilerOptions::kSpeed);
      }
else{  
        # 方法數大于--num-dex-methods則設定為kInterpretOnly,禁止dex2oat
    	compiler_options_->SetCompilerFilter(CompilerOptions::kInterpretOnly);
      }
    }
           

2.對與應用插件,限制對插件執行dex2oat的線程數

熱更新不可能直接把java源碼下發,隻能下發dex檔案,在本地把基線dex給反編譯然後合并再回編譯很明顯不可能,畢竟baksmali.jar和smali.jar加起來都快5m了,熱更新架構不可能搞這麼大。但是利用dex2oat的這個特性,我們可以隻下發包括更新代碼的dex,用戶端接收後把新dex作為classes.dex,舊dex作為classes2.dex,送進dex2oat,就能得到一個更新過代碼的oat檔案,盡可能簡單的完成熱更新。

作者:琴梨梨

連結:https://www.jianshu.com/p/cf63266cca86

插件下發時,dex2oat預設會開啟和cpu核心數相同的線程數去編譯,占據非常的的cpu資源,很容易會造成系統卡頓

插件一般會下載下傳到APP的私有資料目錄,即 “data/data/” 或 “data/user/” ,而使用dex2oat時會把dex的源路徑作為參數傳入,根據參數有無包含這兩個路徑可以判斷是否是一個插件在調用dex2oat,如果是的話則限制dex2oat的線程數為1,保證熱更新的正常和系統的流暢

dex2oat.cc   
 for (const char* dex_file_name : dex_filenames_) {
	std::string fileNameStr(dex_file_name);
        if(fileNameStr.find("data/user/") != std::string::npos || fileNameStr.find("data/data/") != std::string::npos)        {
	  compiler_options_->SetCompilerFilter(CompilerOptions::kSpeed);
	  # 限制線程數為1
          thread_count_ = 1;	
	  break;
	}
      }