顯示具有 DEX 標籤的文章。 顯示所有文章
顯示具有 DEX 標籤的文章。 顯示所有文章

2014/03/03

DexClassLoader 應用 - 整合服務平台


原理:使用 DexClassLoader 動態載入 apk 中 dex file,並產生 classes 然後執行其 method
※ 此方法必須先知道 apk 中 dex file 有哪些 classes 與 method

Code 解說

目的:將編譯好的 classB 之 apk 放到 SD 卡目錄下,在使用 classA 動態啟動(載入) classB
class A

String path = Environment.getExternalStorageDirectory() + File.separator;  //File.separator 表示"/"符號,系統自動產生對應的 "\" 或 "/"
String filename = "TestClassLoadBDex.apk";

String optimizedDirectory = path + File.separator + "dex_temp"  ;
//為解決 .dex文件在 4.1 之後不能存在 SD 卡下,否則會出現 IllegalArgumentException 問題,這裡透過 getDir 來取得 file
File file = getDir("dex", 0) ;
DexClassLoader cl = new DexClassLoader(path + filename, file.getAbsolutePath(),null, getClassLoader());

Class libProviderClazz = null;

try {
 libProviderClazz = cl.loadClass("com.example.testclassloadb.MainActivity");  //class 完整名稱

 Method onCreateMethod = libProviderClazz.getDeclaredMethod("onCreate", Bundle.class);  //呼叫 class 中 method
 onCreateMethod.setAccessible(true);  //設定 method 是否可訪問
 onCreateMethod.invoke(this, savedInstanceState); //第一個参数為,一般 method 傳入 context,靜態 method 傳入 null
    
     Method changeTitle = libProviderClazz.getDeclaredMethod("changeTitle", String.class);  //呼叫 class 中 method
     changeTitle.setAccessible(true);  //設定 method 是否可訪問
 changeTitle.invoke(this, "class A call change title"); //第一個参数為,一般 method 傳入 context,靜態 method 傳入 null
    

} catch (Exception exception) {
 exception.printStackTrace();
}
class B (此部分代碼為錯誤示範)

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 tv_title = (TextView) findViewById(R.id.tv_title);
 btn_showToast = (Button) findViewById(R.id.btn_showToast);
 
 Log.i("ruby", "path="+this.getFilesDir());
 
 btn_showToast.setOnClickListener(new OnClickListener() {
  
  @Override
  public void onClick(View v) {
   Toast.makeText(MainActivity.this, "in ClassLoadB", Toast.LENGTH_SHORT).show();
  }
 });
 
}

private void changeTitle(String str){
 tv_title.setText(str);
}

執行步驟:

  1. 將 classB 編譯成 apk 檔案
  2. 將 apk 放置手機 SD 卡跟目錄下 (這裡示範位置在根目錄,要自訂的請自行修改路徑)
  3. 執行 classA

執行結果:

輸入完以上代碼後執行,發現雖然成功執行了,但是 classA 並沒有載入 classB 的 apk,也沒有執行預定的 method (onCreate 與 changeTitle)
其實不安装apk,直接動態加載 dex 中的類別,是屬於透過反射的方式手動的創建 Activity
這種方式跟系统創建的 Activity 是不一樣的,没有系统創建之 Activity 的特性,而且 R 也找不到(無法透過 layout 來畫 UI),其實他並不是啟動 Apk 中的 Activity,而是在 classA Activity 上劃出 Apk 中 Activity 的 View

解決辦法:

由上述內容可知,其實只要手動產生一個 activity 並傳入 classB 即可正常執行 classB 及其 method
首先,增加一個 setActivity 的 method
class B

public void setActivity(Activity paramActivity) {
    Log.i("ruby", "setActivity..." + paramActivity);
    this.activity = paramActivity;
}
接著,在 classB OnCreate method 中修改 view 的呈現
※ classB 載入的 view 不可使用 resources 只能動態產生,因為載入 classB 時無法做 resources 連結
classB

boolean b = false;
TextView t = new TextView(this.activity);  
t.setText("class B"); 

if (savedInstanceState != null) {
 b = savedInstanceState.getBoolean("KEY_START_FROM_OTHER_ACTIVITY", false);
 if (b) {
  this.activity.setContentView(t);
 }
}
if (!b) {
 super.onCreate(savedInstanceState);
 setContentView(t);
}
最後,在 classA 呼叫 classB 的 onCreate method 時,先呼叫 setActivity method
classA

Constructor localConstructor = libProviderClazz.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
Method localMethodSetActivity = libProviderClazz.getDeclaredMethod(
  "setActivity", new Class[] { Activity.class });
localMethodSetActivity.setAccessible(true);
localMethodSetActivity.invoke(instance, new Object[] { this });

應用範圍:

app 整合平台,例如:game center classB 就像是遊戲大廳,classA 就像是裡面的各個小遊戲,讓不同公司開發出的小遊戲可以直接存取底層的遊戲大廳(classB)

getDeclaredMethod 與 getMethod 差異

  1. getDeclaredMethod:取得本身 class 中的所有方法,包含 public、protected 和 private 方法
  2. getMethod:取得 class 中的所有共有方法,包括自己的 public 方法、繼承來的方法、實作介面的 method 

References

2014/02/19

LinearAlloc exceeded capacity Problem


在開發 Android App 的過程中,專案會因為功能需求引入一些 opensource 套件包、lib、jar等資源,當專案日益龐大,就有可能發生 LinearAlloc exceeded capacity 的問題。本篇就來簡單介紹一下原因以及可行的解決方法
首先,先簡單介紹 LinearAlloc

LinearAlloc (線性分配器)

  1. source code:android_src/dalvik/vm/ 下 LinearAlloc.h 和 LinearAlloc.c
  2. 目的:主要用来管理 Dalvik 中 class 加載時的内存,使其簡單、快速地分配内存。簡單來說就是讓app在執行時,減少系統內存的佔用。
  3. 屬於 Android Running 層,由 Dalvik Virtual Machine 執行

Dalvik 與 DEX 文件檔

  1. Android 上所提供的 ByteCode 虛擬器
  2. 目標:
    (1) 要能運作在效能需求不高、較少的記憶體,與使用慢速的內部 Flash 儲存
    (2) 作業系統要能支援虛擬記憶體,執行行程與執行緒(Process/Thread)
    (3) 要具備使用者帳號安全管理機制(UID-based security mechanisms),並能透過 GNU C 編譯器編譯後運作在包括 Linux,BSD 與 Mac OS X 等 Unix 環境下
    (4) 支援 Little-endian 與 Big-endian 處理器執行環境
    (5) Class Data 尤其是共用的 ByteCode,要能跨行程共用,節省系統整體記憶體需求(參考Process Memory Map 目前包括相關的 jar 與所包含的 Bytecode Dex 檔案,都能跨行程在不同的 Dalvik 行程中共用
    (6) 減少 Dalvik 應用程式載入啟動的成本,加速應用程式的反應時間
    (7) Class Data 儲存在個別的檔案中,導致額外的儲存成本,針對儲存空間的需求需要多加注意
    (8) 要從 Class Data 中讀取出資料數值(例如:整數或是字串)會增加不必要的成本,評估如何採用 C 的形式存取,會是有必要的
    (9) ByteCode 的驗證雖耗時但卻是必要的,應該試著在程式執行前完成驗證
    (10) 透過快速指令集與機制的優化進行 ByteCode 的最佳化對於執行效率與電池壽命是相當重要的
    (11) 為了安全需求,執行中的行程不能修改共享的程式碼
  3. 核心的函式庫主要是承襲 Open Source 的 Java SE 實作 Apache Harmony 而來,並基於 Open Source 中相關 OpenSSL, zlib 與 ICU(International Components for Unicode) 的計畫成果
    ※ 一般虛擬器的設計,會在應用程式啟動時,動態的從儲存裝置讀取並解壓縮個別的 Classes 到記憶體中,在沒有經過適度優化的架構下,每個獨立的虛擬器行程,都會包含自己一份的相關 Classes 記憶體空間
  4. 基於上述目標, Dalvik 在設計時,作了以下的決定:
    (1) 可以把多個 Classes 檔案整合到一個 DEX 檔案中
    (2) DEX 檔案會以唯讀方案載入到記憶體中,並跨行程共享
    (3) 因應支援的系統架構,調整 Byte Ordering 與 Word Alignment
    (4) ByteCode 驗證對所有的 Classes 都是必要的,且會儘可能進行事先的驗證加速系統效率
    (5) 對於需修改 ByteCode 的最佳化動作,會在執行前完成

APK文件檔

  1. 屬於一種壓縮檔案,可用 zip 解壓縮
  2. 包含:
    (1) META-INF:清單信息(Manifest file),應用程序的證書和授權信息,SHA-1 信息資源列表
    (2) res:APK所需要的資源文件夾
    (3) AndroidManifest.xml:一個傳統的 Android 清單文件,用於描述該應用程序的名字、版本號、所需許可權、註冊的服務、連結的其他應用程序。該文件使用 XML 文件格式,可以編譯為二進制的XML
    (4) classes.dex:classes 文件通過 DEX 編譯後的文件格式,用於在 Dalvik 虛擬機上運行的主要代碼部分
    (5) resources.arsc:編譯後的二進制資源文件

LinearAlloc exceeded capacity Problem

  1. 原因:app 在安裝時會將 apk 中 dex 文件安裝到 dalvik-cache 目錄下,在安裝過程中發生的錯誤。在 Android 2.2 與 2.3 版本中,LinearAlloc buffer 只有 5MB,但較新的版本 buffer 則有 8 或 16MB ,也就是說如果您的app要支援 Android 2.3 或以下版本, dex 文件檔必須小於 5MB,如果超出則會出現類似以下的錯誤訊息

12-17 14:59:56.959: D/PackageManager(183): Scanning package tw.com.maxkit.android.kokola.mobile.activity
12-17 14:59:56.959: E/PackageManager(183): Package tw.com.maxkit.android.kokola.mobile.activity has mismatched uid: 10091 on disk, 10098 in settings
12-17 14:59:56.959: I/PackageManager(183): Linking native library dir for /data/app/tw.com.maxkit.android.kokola.mobile.activity-1.apk
12-17 14:59:59.639: W/dalvikvm(1891): Superclass of 'Lorg/apache/http/params/SyncBasicHttpParams;' is final 'Lorg/apache/http/params/BasicHttpParams;'
12-17 14:59:59.989: E/AlarmManagerService(183): android_server_AlarmManagerService_set to type=1, 1387263691.768000000
12-17 14:59:59.989: E/AlarmManagerService(183): android_server_AlarmManagerService_set to type=1, 1387263660.000000000
12-17 14:59:59.989: D/PowerManagerService(183): acquireWakeLock flags=0x1 tag=AlarmManager
12-17 15:00:00.009: D/DigitalClock(32302): syw.action = android.intent.action.TIME_TICK
12-17 15:00:00.029: D/PowerManagerService(183): releaseWakeLock flags=0x1 tag=AlarmManager
12-17 15:00:02.939: E/dalvikvm(1891): LinearAlloc exceeded capacity (5242880), last=100
12-17 15:00:02.939: E/dalvikvm(1891): VM aborting
12-17 15:00:03.049: I/DEBUG(521): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
12-17 15:00:03.049: I/DEBUG(521): Build fingerprint: 'samsung/GT-S7500/GT-S7500:2.3.6/GINGERBREAD/ZSLD3:user/release-keys'
12-17 15:00:03.049: I/DEBUG(521): pid: 1891, tid: 1891  >>> /system/bin/dexopt <<<
12-17 15:00:03.049: I/DEBUG(521): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr deadd00d
12-17 15:00:03.049: I/DEBUG(521):  r0 fffffe7c  r1 deadd00d  r2 00000026  r3 00000000
12-17 15:00:03.049: I/DEBUG(521):  r4 8009d5b0  r5 004fffd4  r6 0050003c  r7 0001a82c
12-17 15:00:03.049: I/DEBUG(521):  r8 00000064  r9 00000001  10 00000000  fp befb99b8
12-17 15:00:03.049: I/DEBUG(521):  ip 8009d6bc  sp befb96e8  lr afd192e9  pc 80043ae4  cpsr 20000030
12-17 15:00:03.049: I/DEBUG(521):  d0  74726f6261204d69  d1  646564656563786e
12-17 15:00:03.049: I/DEBUG(521):  d2  0000000000000067  d3  000000000000000a
12-17 15:00:03.049: I/DEBUG(521):  d4  0000000200000002  d5  0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d6  00000002402c5020  d7  0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d8  0000000000000000  d9  0000000000000000
12-17 15:00:03.059: I/DEBUG(521):  d10 0000000000000000  d11 0000000000000000
12-17 15:00:03.059: I/DEBUG(521):  d12 0000000000000000  d13 0000000000000000
12-17 15:00:03.059: I/DEBUG(521):  d14 0000000000000000  d15 0000000000000000
12-17 15:00:03.059: I/DEBUG(521):  d16 0000000200000002  d17 0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d18 0000000200000002  d19 0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d20 0000000c00000002  d21 0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d22 402926d000000002  d23 4014ccf04014e928
12-17 15:00:03.059: I/DEBUG(521):  d24 0000000000000000  d25 0000000000000000
12-17 15:00:03.069: I/DEBUG(521):  d26 0000000000000000  d27 0000000000000000
12-17 15:00:03.069: I/DEBUG(521):  d28 0000000000000000  d29 0000000000000000
12-17 15:00:03.069: I/DEBUG(521):  d30 0000000000000000  d31 0000000000000000
12-17 15:00:03.069: I/DEBUG(521):  scr 00000000
12-17 15:00:03.109: I/DEBUG(521):          #00  pc 00043ae4  /system/lib/libdvm.so (dvmAbort)
12-17 15:00:03.109: I/DEBUG(521):          #01  pc 0004b85e  /system/lib/libdvm.so (dvmLinearAlloc)
12-17 15:00:03.109: I/DEBUG(521):          #02  pc 0006535e  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #03  pc 00065ca0  /system/lib/libdvm.so (dvmLinkClass)
12-17 15:00:03.109: I/DEBUG(521):          #04  pc 00067210  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #05  pc 000673e6  /system/lib/libdvm.so (dvmFindSystemClassNoInit)
12-17 15:00:03.109: I/DEBUG(521):          #06  pc 00066c5e  /system/lib/libdvm.so (dvmFindClassNoInit)
12-17 15:00:03.109: I/DEBUG(521):          #07  pc 00059908  /system/lib/libdvm.so (dvmOptResolveClass)
12-17 15:00:03.109: I/DEBUG(521):          #08  pc 00055f66  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #09  pc 00057e64  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #10  pc 0005803a  /system/lib/libdvm.so (dvmVerifyCodeFlow)
12-17 15:00:03.109: I/DEBUG(521):          #11  pc 000597c8  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #12  pc 0005982a  /system/lib/libdvm.so (dvmVerifyClass)
12-17 15:00:03.109: I/DEBUG(521):          #13  pc 0005875a  /system/lib/libdvm.so
12-17 15:00:03.119: I/DEBUG(521):          #14  pc 0005880e  /system/lib/libdvm.so
12-17 15:00:03.119: I/DEBUG(521):          #15  pc 0005890a  /system/lib/libdvm.so
12-17 15:00:03.119: I/DEBUG(521):          #16  pc 00058af4  /system/lib/libdvm.so (dvmContinueOptimization)
12-17 15:00:03.119: I/DEBUG(521):          #17  pc 00008c46  /system/bin/dexopt
12-17 15:00:03.119: I/DEBUG(521):          #18  pc 00008d32  /system/bin/dexopt
12-17 15:00:03.119: I/DEBUG(521):          #19  pc 00008dc8  /system/bin/dexopt
12-17 15:00:03.119: I/DEBUG(521):          #20  pc 000091ce  /system/bin/dexopt
12-17 15:00:03.119: I/DEBUG(521):          #21  pc 00014c62  /system/lib/libc.so (__libc_init)
12-17 15:00:03.119: I/DEBUG(521): libc base address: afd00000
12-17 15:00:03.119: I/DEBUG(521): code around pc:
12-17 15:00:03.119: I/DEBUG(521): 80043ac4 447a4479 f7d34c0b 2000edca eecef7d3 
12-17 15:00:03.119: I/DEBUG(521): 80043ad4 447c4809 6bdb5823 4798b103 22264902 
12-17 15:00:03.119: I/DEBUG(521): 80043ae4 f7d3700a bf00ef36 deadd00d 00041204 
12-17 15:00:03.119: I/DEBUG(521): 80043af4 00042a09 00059ad6 fffffe7c 4b09b40e 
12-17 15:00:03.119: I/DEBUG(521): 80043b04 4c09b517 aa05447b f852591b 6b5b1b04 
12-17 15:00:03.119: I/DEBUG(521): code around lr:
12-17 15:00:03.119: I/DEBUG(521): afd192c8 4a0e4b0d e92d447b 589c41f0 26004680 
12-17 15:00:03.119: I/DEBUG(521): afd192d8 686768a5 f9b5e006 b113300c 47c04628 
12-17 15:00:03.119: I/DEBUG(521): afd192e8 35544306 37fff117 6824d5f5 d1ef2c00 
12-17 15:00:03.119: I/DEBUG(521): afd192f8 e8bd4630 bf0081f0 00028258 ffffff84 
12-17 15:00:03.119: I/DEBUG(521): afd19308 b086b570 f602fb01 9004460c a804a901 
12-17 15:00:03.119: I/DEBUG(521): stack:
12-17 15:00:03.119: I/DEBUG(521):     befb96a8  00000002  
12-17 15:00:03.119: I/DEBUG(521):     befb96ac  00015270  
12-17 15:00:03.119: I/DEBUG(521):     befb96b0  0001a828  
12-17 15:00:03.119: I/DEBUG(521):     befb96b4  004ffdc8  
12-17 15:00:03.119: I/DEBUG(521):     befb96b8  afd4272c  
12-17 15:00:03.119: I/DEBUG(521):     befb96bc  afd426d8  
12-17 15:00:03.119: I/DEBUG(521):     befb96c0  00000000  
12-17 15:00:03.119: I/DEBUG(521):     befb96c4  afd192e9  /system/lib/libc.so
12-17 15:00:03.119: I/DEBUG(521):     befb96c8  00059ad6  
12-17 15:00:03.119: I/DEBUG(521):     befb96cc  004fffd4  
12-17 15:00:03.119: I/DEBUG(521):     befb96d0  0050003c  
12-17 15:00:03.119: I/DEBUG(521):     befb96d4  0001a82c  
12-17 15:00:03.119: I/DEBUG(521):     befb96d8  00000064  
12-17 15:00:03.119: I/DEBUG(521):     befb96dc  afd183e1  /system/lib/libc.so
12-17 15:00:03.119: I/DEBUG(521):     befb96e0  df002777  
12-17 15:00:03.119: I/DEBUG(521):     befb96e4  e3a070ad  
12-17 15:00:03.119: I/DEBUG(521): #00 befb96e8  0001a828  
12-17 15:00:03.119: I/DEBUG(521):     befb96ec  8004b863  /system/lib/libdvm.so
12-17 15:00:03.119: I/DEBUG(521): #01 befb96f0  00000064  
12-17 15:00:03.119: I/DEBUG(521):     befb96f4  00000064  
12-17 15:00:03.119: I/DEBUG(521):     befb96f8  402f9080  
12-17 15:00:03.119: I/DEBUG(521):     befb96fc  8009d5b0  
12-17 15:00:03.119: I/DEBUG(521):     befb9700  00000019  
12-17 15:00:03.119: I/DEBUG(521):     befb9704  00000001  
12-17 15:00:03.119: I/DEBUG(521):     befb9708  fffffff7  
12-17 15:00:03.119: I/DEBUG(521):     befb970c  00000001  
12-17 15:00:03.119: I/DEBUG(521):     befb9710  00000000  
12-17 15:00:03.119: I/DEBUG(521):     befb9714  80065363  /system/lib/libdvm.so
12-17 15:00:03.139: I/DEBUG(521): dumpstate /data/log/dumpstate_app_native.txt

※ FaceBook也遇到相同問題:Dalvik patch for Facebook for Android,FaceBook Solve:Rebuilding Facebook for Android
2. Solve:
(1) 由上連結可知 FB 的 solve 是修改 LinearAlloc buffer,並盡可能減少內存記憶體的佔用
(2) 將 dex 文件切割成多個 dex 文件,並將其先下載至手機儲存後,在使用 DexClassLoader 動態載入 class (作法請參考下篇 使用 DexClassLoader 動態載入 DEX)

References