1. 程式人生 > >Android動態載入dex技術初探

Android動態載入dex技術初探

     今天不忙,研究了下Android動態載入dex的技術,主要參考:

           1、http://www.cnblogs.com/over140/archive/2011/11/23/2259367.html

            2、http://www.fengyoutian.com/web/single/13

      好歹算是跑通了。下面把實現過程與遇到的問題歸納下,方便後續查詢使用。

       一、綜述

       Android使用Dalvik虛擬機器載入可執行程式,所以不能直接載入基於class的jar,而是需要將class轉化為dex位元組碼,從而執行程式碼。優化後的位元組碼檔案可以存在一個*.jar中,只要其內部存放的是*.dex即可使用。

       將class的jar包轉化為dex需要用到命令dx(在*\android-sdk\build-tools\version[23.0.1] 或 *\android-sdk\platform-tools下能找到);命令使用方式為:dx --dex --output=output.jar origin.jar,該命令將包含class的origin.jar轉化為包含dex的output.jar檔案。

        Android支援動態載入的兩種方式是:DexClassLoader和PathClassLoader,DexClassLoader可載入jar/apk/dex,且支援從SD卡載入;PathClassLoader據說只能載入已經安裝在Android系統內APK檔案,此說法我尚未驗證(http://blog.csdn.net/quaful/article/details/6096951);以下這一段是摘錄:

//******************************************************************************************************//

         PathClassLoader 的限制要更多一些,它只能載入已經安裝到 Android 系統中的 apk 檔案,也就是 /data/app 目錄下的 apk 檔案。其它位置的檔案載入的時候都會出現 ClassNotFoundException. 例如:

PathClassLoader cl = new PathClassLoader(jarFile.toString(), "/data/app/", ClassLoader.getSystemClassLoader());

為什麼有這個限制呢?我認為這其實是當前 Android 的一個 bug, 因為 PathClassLoader 會去讀取 /data/dalvik-cache 目錄下的經過 Dalvik 優化過的 dex 檔案,這個目錄的 dex 檔案是在安裝 apk 包的時候由 Dalvik 生成的。例如,如果包的名字是 com.qihoo360.test,Android 應用安裝之後都儲存在 /data/app 目錄下,即 /data/app/com.qihoo360.test-1.apk,那麼 /data/dalvik-cache 目錄下就會生成 [email protected]@[email protected] 檔案。在呼叫 PathClassLoader 時,它就會按照這個規則去找 dex 檔案,如果你指定的 apk 檔案是 /sdcard/test.apk,它按照這個規則就會去讀 /data/dalvik-cache/[email protected]@classes.dex 檔案,顯然這個檔案不會存在,所以 PathClassLoader 會報錯。

//*******************************************************************************************************//

        二、實驗步驟

        新建一個Android工程,並進行如下操作:

            2.1 定義一個介面IShowToast.java

package com.example.testdextoast;

import android.content.Context;

public interface IShowToast {
	public int showToast(Context context);
}
            2.2 再定義一個簡單實現ShowToastImpl.java
package com.example.testdextoast;

import android.content.Context;
import android.widget.Toast;

public class ShowToastImpl implements IShowToast {

	@Override
	public int showToast(Context context) {
		Toast.makeText(context, "我來自另一個dex檔案", Toast.LENGTH_LONG).show();
		return 100;
	}
}
            2.3 將且僅將ShowToastImpl.java匯出為jar(Eclipse工程右鍵->Export->Java->Jar file),這時候我們得到一個包含class檔案的jar,命名為origin.jar。(此處按文章1說法同時匯出介面IShowToast.java會報錯:java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation,但是我測試了匯出介面IShowToast.java的情況,可以正常使用,沒有崩潰問題。)

            2.4 在命令列下,進入dx所在目錄,將jar檔案拷到該目錄下,執行dx --dex --output=output.jar origin.jar,得到內含class.dex的output.jar

            2.5 將output.jar檔案放到SD卡下adb push output.jar sdcard/output.jar(或者將jar內的dex抽出來放到SD卡亦可)

            2.6 開始編寫呼叫程式碼

            新建呼叫的Android工程,將IShowToast.java放入該工程,注意:此處一定不能改變此檔案的包路徑,否則dex中的實現類找不到介面,會報錯:

java.lang.ClassNotFoundException: Didn't find class "com.example.testdextoast.ShowToastImpl" on path: DexPathList[[zip file "/storage/emulated/0/testtoast.jar"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
     ...
     Suppressed: java.lang.NoClassDefFoundError: Failed resolution of: Lcom/example/testdextoast/IShowToast;
             ... 16 more
     Caused by: java.lang.ClassNotFoundException: Didn't find class "com.example.testdextoast.IShowToast" on path: DexPathList[[zip file "/storage/emulated/0/testtoast.jar"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
             ... 21 more
             Suppressed: java.lang.ClassNotFoundException: Didn't find class "com.example.testdextoast.IShowToast" on path: DexPathList[[zip file "/data/app/com.example.testshowtoastdex-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
                     ... 22 more
                     Suppressed: java.lang.ClassNotFoundException: com.example.testdextoast.IShowToast
                             ... 23 more
                     Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available
     Suppressed: java.lang.ClassNotFoundException: Didn't find class "com.example.testdextoast.ShowToastImpl" on path: DexPathList[[zip file "/data/app/com.example.testshowtoastdex-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
             ... 15 more
             Suppressed: java.lang.ClassNotFoundException: com.example.testdextoast.ShowToastImpl
                     ... 16 more
             Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available
            測試工程程式碼如下:
package com.example.testshowtoastdex;

import java.io.File;

import com.example.testdextoast.IShowToast;

import dalvik.system.DexClassLoader;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		File dexOutputDir = getDir("dex1", 0);
		String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "output.jar";
		DexClassLoader loader = new DexClassLoader(dexPath, 
				dexOutputDir.getAbsolutePath(), 
				null, getClassLoader());
		try {
			Class clz = loader.loadClass("com.example.testdextoast.ShowToastImpl");
			IShowToast impl = (IShowToast) clz.newInstance();
			impl.showToast(this);
		} catch (Exception e) {
			Log.d("TEST111", "error happened", e);
		}
	}
}
            此處需要注意DexClassLoader的四個引數:

               引數1 dexPath:待載入的dex檔案路徑,如果是外存路徑,一定要加上讀外存檔案的許可權(<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> ),否則會報與上面一樣的錯誤,這點參考文章2中說這個許可權可有可無是錯誤的。(更正下:Android4.4 KitKat及以後的版本需要此許可權,之前的版本不需要許可權)

               引數2 optimizedDirectory:解壓後的dex存放位置,此位置一定要是可讀寫且僅該應用可讀寫(安全性考慮),所以只能放在data/data下。本文getDir("dex1", 0)會在/data/data/**package/下建立一個名叫”app_dex1“的資料夾,其記憶體放的檔案是自動生成output.dex;如果不滿足條件,Android會報的錯誤為:

              java.lang.IllegalArgumentException: optimizedDirectory not readable/writable: /storage/sdcard0

              java.lang.IllegalArgumentException: Optimized data directory /storage/sdcard0 is not owned by the current user. Shared storage cannot protect your application from code injection attacks.

               引數3 libraryPath:指向包含本地庫(so)的資料夾路徑,可以設為null

               引數4 parent:父級類載入器,一般可以通過Context.getClassLoader獲取到,也可以通過ClassLoader.getSystemClassLoader()取到

    三、執行結果

           上述步驟完成後就能呼叫到ShowToastImpl中方法並展示一個Toast,動態載入dex的皮毛已經瞭解了。另外文章1談到”在實踐中發現,自己新建一個Java工程然後匯出jar是無法使用的“,這點經測試是不正確了,在Java工程中匯出的class也可以被轉化為dex並載入。匯出jar的操作步驟見上文。

package com.example.testdextoast;


public interface IShowToast {
	public String getToast();
}
public class ShowToastImpl implements IShowToast {

	@Override
	public String getToast() {
		return "我來自另一個dex檔案";
	}

}
package com.example.testshowtoastdex;
import java.io.File;
import com.example.testdextoast.IShowToast;
import dalvik.system.DexClassLoader;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		File dexOutputDir = getDir("dex1", 0);
		String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "output.dex";
		DexClassLoader loader = new DexClassLoader(dexPath, 
				dexOutputDir.getAbsolutePath(), 
				null, getClassLoader());
		try {
			Class clz = loader.loadClass("com.example.testdextoast.ShowToastImpl");
			IShowToast impl = (IShowToast) clz.newInstance();
			Toast.makeText(this, "來自Java包1:" + impl.getToast(), Toast.LENGTH_SHORT).show();
		} catch (Exception e) {
			Log.d("TEST111", "error happened", e);
		}
	}
}

這段程式碼彈出的Toast是:”來自Java包1:我來自另一個dex檔案“,說明java工程也是可以匯出class轉為dex的。我估計原文意思是java工程生成的可執行jar檔案不能使用。

    四、補充

           1、如何在不安裝APK的情況下呼叫其內部的Activity?

http://blog.zhourunsheng.com/2011/09/%E6%8E%A2%E7%A7%98%E8%85%BE%E8%AE%AFandroid%E6%89%8B%E6%9C%BA%E6%B8%B8%E6%88%8F%E5%B9%B3%E5%8F%B0%E4%B9%8B%E4%B8%8D%E5%AE%89%E8%A3%85%E6%B8%B8%E6%88%8Fapk%E7%9B%B4%E6%8E%A5%E5%90%AF%E5%8A%A8%E6%B3%95/

             核心思想是反射目標APK中的Activity,構造完成後將當前應用的Context傳入到目標APK中使其具有上下文環境。

相關推薦

Android動態載入dex技術初探

     今天不忙,研究了下Android動態載入dex的技術,主要參考:            1、http://www.cnblogs.com/over140/archive/2011/11/23/2259367.html             2、http://ww

Android動態載入Dex過程

 一、綜述       Android使用Dalvik虛擬機器載入可執行程式,所以不能直接載入基於class的jar,而是需要將class轉化為dex位元組碼,從而執行程式碼。優化後的位元組碼檔案可以存在一個*.jar中,只要其內部存放的是*.dex即可使用。       將

Android 動態載入dex

為什麼要學習這個,動態載入dex,因為學習android外掛化必須要了解這個才行。   Android使用Dalvik虛擬機器載入可執行程式,所以不能直接載入基於class的jar,而是需要將class轉化為dex位元組碼,從而執行程式碼。優化後的位元組碼檔案可以存在一個*

Android應用安全之外部動態載入DEX檔案風險

1. 外部動態載入DEX檔案風險描述 Android 系統提供了一種類載入器DexClassLoader,其可以在執行時動態載入並解釋執行包含在JAR或APK檔案內的DEX檔案。外部動態載入DEX檔案的安全風險源於:Anroid4.1之前的系統版本容許Android應用將動態載入的DEX檔案儲存

深入淺出Android動態載入jar包技術

在實際專案中,由於某些業務頻繁變更而導致頻繁升級客戶端的弊病會造成較差的使用者體驗,而這也恰是Web App的優勢,於是便衍生了一種思路,將核心的易於變更的業務封裝在jar包裡然後通過網路下載下來,再由android動態載入執行的方案,以改善頻繁升級的毛病   --前言

Android動態載入技術三個關鍵問題詳解

動態載入技術(也叫外掛化技術)在技術驅動型的公司中扮演著相當重要的角色,當專案越來越龐大的時候,需要通過外掛化來減輕應用的記憶體和CPU佔用,還可以實現熱插拔,即在不釋出新版本的情況下更新某些模組。動態載入是一項很複雜的技術,這裡主要介紹動態載入技術中的三個基礎

關於apk加殼之動態載入dex檔案

由於自己之前做了一個關於手機令牌的APK軟體,在實現的過程中儘管使用了native so進行一定的邏輯演算法保護,但是在自己逆向破解的過程中發現我的手機令牌關鍵資料能夠“輕易地”暴露出來,所以我就想進一步的對其進行加固。於是,我使用的網上常用的梆梆加固、愛加密和阿里的聚安全應用來對我的apk進行一個

thinkphp+ajax 移動端實現滾動到底部載入資料(無重新整理動態載入資料技術的應用)

監聽滾動條的js檔案地址:http://ons.me/526.html 1、dropload.css檔案 .dropload-up,.dropload-down{     position: relative;     height: 0; &n

DL動態載入框架技術文件

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android 動態載入sd卡里面so庫

有些so檔案太大,可以從手機記憶體或者sd卡里面拷貝到執行的應用程式裡面。介面都是之前打包在裡面了。還可以做so更新,就是把之前拷貝進行刪除,然後進行不重新打包apk,進行重新拷貝進去。 1,封裝好的類 package com.rtcmdemo.until; impor

Android動態載入基礎 ClassLoader工作機制

基本資訊 類載入器ClassLoader 早期使用過Eclipse等Java編寫的軟體的同學可能比較熟悉,Eclipse可以載入許多第三方的外掛(或者叫擴充套件),這就是動態載入。這些外掛大多是一些Jar包,而使用外掛其實就是動態載入Jar包裡的Class進行工作。這其實

Android動態載入輪播圖BannerView

輪播圖在每個app中扮演著一個點綴的角色,在獨立做了三款app後都有這個需求,所以我決定把它單獨抽出來。以後只需copy,然後再根據需求改一下即可。 /** * 載入網路輪播圖 *@author jiangrongtao * *csdn

Android動態載入Activity原理

activity的啟動流程 載入一個Activity肯定不會像載入一般的類那樣,因為activity作為系統的元件有自己的生命週期,有系統的很多回調控制,所以自定義一個DexClassLoader類載入器來載入外掛中的Activity肯定是不可以的。 首先不得不瞭解一下ac

Android動態載入入坑指南

private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException&g

Android動態載入Jar

定義介面, 把實現介面的部分打包成jar 在 將打包好的jar拷貝到SDK安裝目錄android-sdk-windows\platform-tools下,DOS進入這個目錄,執行命名: dx --dex --output=test.jar update.jar 核心載入

Android 動態載入佈局檔案

本文轉自:原文地址 Android的基本UI介面一般都是在xml檔案中定義好,然後通過activity的setContentView來顯示在介面上,這是Android UI的最簡單的構建方式。其實,為了實現更加複雜和更加靈活的UI介面,往往需要動態生成UI介面,甚至根

Android 動態載入二維碼檢視生成快照

1.需求背景 需要實現一個動態載入但不顯示出來的檢視,且該檢視上有個動態生成的二維碼,最後用其去生成一張快照(也就是圖片)。 (常見這種情況是來源於“圖片分享”的功能需求,與普通圖片分享不同在於,該快照圖片是動態載入不顯示的。) 2.需求功能拆解 動態二維碼的實現動態檢

android 動態載入sd卡的jar檔案

下面以一個例子列出Android程式執行時動態載入sd卡jar包的步驟: 1. 首先要準備好jar包。 本例中要對com.test.dynamic包進行打包,com.test.dynamic包下面有一個MyClass類.程式碼如下: package com.test.dynamic;   impo

android動態載入外部類

基本資訊  我們很早開始就在Android專案中採用了動態載入技術,主要目的是為了達到讓使用者不用重新安裝APK就能升級應用的功能,這樣一來不但可以大大提高應用新版本的覆蓋率,也減少了伺服器對舊版本介面相容的壓力,同時如果也可以快速修復一些線上的BUG。  這種技術並

Android動態載入jar檔案

這裡用個例子來演示,具體流程是用Android Studio建一個Android專案並編寫相應程式碼,然後用Eclipse編寫一個java程式碼並打成jar包,再轉換成Android能識別的dexjar包,最後先安裝好APP,然後把jar包放到APP目錄下,ap