<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>


       To build a better world !

    Android類動態加載技術

    轉載請注明出處:http://www.tkk7.com/zh-weir/archive/2011/10/29/362294.html 

    Android類動態加載技術

    Android應用開發在一般情況下,常規的開發方式和代碼架構就能滿足我們的普通需求。但是有些特殊問題,常常引發我們進一步的沉思。我們從沉思中產生頓悟,從而產生新的技術形式。

    如何開發一個可以自定義控件的Android應用?就像eclipse一樣,可以動態加載插件;如何讓Android應用執行服務器上的不可預知的代碼?如何對Android應用加密,而只在執行時自解密,從而防止被破解?……

    熟悉Java技術的朋友,可能意識到,我們需要使用類加載器靈活的加載執行的類。這在Java里已經算是一項比較成熟的技術了,但是在Android中,我們大多數人都還非常陌生。

    類加載機制

           Dalvik虛擬機如同其他Java虛擬機一樣,在運行程序時首先需要將對應的類加載到內存中。而在Java標準的虛擬機中,類加載可以從class文件中讀取,也可以是其他形式的二進制流。因此,我們常常利用這一點,在程序運行時手動加載Class,從而達到代碼動態加載執行的目的。

           然而Dalvik虛擬機畢竟不算是標準的Java虛擬機,因此在類加載機制上,它們有相同的地方,也有不同之處。我們必須區別對待。

           例如,在使用標準Java虛擬機時,我們經常自定義繼承自ClassLoader的類加載器。然后通過defineClass方法來從一個二進制流中加載Class。然而,這在Android里是行不通的,大家就沒必要走彎路了。參看源碼我們知道,AndroidClassLoaderdefineClass方法具體是調用VMClassLoaderdefineClass本地靜態方法。而這個本地方法除了拋出一個“UnsupportedOperationException”之外,什么都沒做,甚至連返回值都為空。

    static void Dalvik_java_lang_VMClassLoader_defineClass(const u4* args,

        JValue
    * pResult)

    {

        Object
    * loader = (Object*) args[0];

        StringObject
    * nameObj = (StringObject*) args[1];

        
    const u1* data = (const u1*) args[2];

        
    int offset = args[3];

        
    int len = args[4];

        Object
    * pd = (Object*) args[5];

        
    char* name = NULL;

     

        name 
    = dvmCreateCstrFromString(nameObj);

        LOGE(
    "ERROR: defineClass(%p, %s, %p, %d, %d, %p)\n",

            loader, name, data, offset, len, pd);

        dvmThrowException(
    "Ljava/lang/UnsupportedOperationException;",

            
    "can't load this type of class file");

     

        free(name);

        RETURN_VOID();

    }


    Dalvik
    虛擬機類加載機制

           那如果在Dalvik虛擬機里,ClassLoader不好使,我們如何實現動態加載類呢?Android為我們從ClassLoader派生出了兩個類:DexClassLoaderPathClassLoader。其中需要特別說明的是PathClassLoader中一段被注釋掉的代碼:

    /* --this doesn't work in current version of Dalvik--

        if (data != null) {

            System.out.println("--- Found class " + name

                + " in zip[" + i + "] '" + mZips[i].getName() + "'");

            int dotIndex = name.lastIndexOf('.');

            if (dotIndex != -1) {

                String packageName = name.substring(0, dotIndex);

                synchronized (this) {

                    Package packageObj = getPackage(packageName);

                    if (packageObj == null) {

                        definePackage(packageName, null, null,

                                null, null, null, null, null);

                    }

                }

            }

     

            return defineClass(name, data, 0, data.length);

        }

    */



           這從另一方面佐證了defineClass函數在Dalvik虛擬機里確實是被閹割了。而在這兩個繼承自ClassLoader的類加載器,本質上是重載了ClassLoaderfindClass方法。在執行loadClass時,我們可以參看ClassLoader部分源碼:

    protected Class<?> loadClass(String className, boolean resolve) 

    throws ClassNotFoundException {

    Class
    <?> clazz = findLoadedClass(className);

     

        
    if (clazz == null{

            
    try {

                clazz 
    = parent.loadClass(className, false);

            }
     catch (ClassNotFoundException e) {

                
    // Don't want to see this.

            }


     

            
    if (clazz == null{

                clazz 
    = findClass(className);

            }


        }


     

        
    return clazz;

    }


           因此DexClassLoaderPathClassLoader都屬于符合雙親委派模型的類加載器(因為它們沒有重載loadClass方法)。也就是說,它們在加載一個類之前,回去檢查自己以及自己以上的類加載器是否已經加載了這個類。如果已經加載過了,就會直接將之返回,而不會重復加載。

           DexClassLoaderPathClassLoader其實都是通過DexFile這個類來實現類加載的。這里需要順便提一下的是,Dalvik虛擬機識別的是dex文件,而不是class文件。因此,我們供類加載的文件也只能是dex文件,或者包含有dex文件的.apk.jar文件。

            
    也許有人想到,既然DexFile可以直接加載類,那么我們為什么還要使用ClassLoader的子類呢?DexFile在加載類時,具體是調用成員方法loadClass或者loadClassBinaryName。其中loadClassBinaryName需要將包含包名的類名中的”.”轉換為”/”。我們看一下loadClass代碼就清楚了:


    public Class loadClass(String name, ClassLoader loader) {

            String slashName 
    = name.replace('.''/');

            
    return loadClassBinaryName(slashName, loader);

    }

           在這段代碼前有一段注釋,截取關鍵一部分就是說:If you are not calling this from a class loader, this is most likely not going to do what you want. Use {@link Class#forName(String)} instead. 這就是我們需要使用ClassLoader子類的原因。至于它是如何驗證是否是在ClassLoader中調用此方法的,我沒有研究,大家如果有興趣可以繼續深入下去。

           有一個細節,可能大家不容易注意到。PathClassLoader是通過構造函數new DexFile(path)來產生DexFile對象的;而DexClassLoader則是通過其靜態方法loadDexpath, outpath, 0)得到DexFile對象。這兩者的區別在于DexClassLoader需要提供一個可寫的outpath路徑,用來釋放.apk包或者.jar包中的dex文件。換個說法來說,就是PathClassLoader不能主動從zip包中釋放出dex,因此只支持直接操作dex格式文件,或者已經安裝的apk(因為已經安裝的apkcache中存在緩存的dex文件)。而DexClassLoader可以支持.apk.jar.dex文件,并且會在指定的outpath路徑釋放出dex文件。

           另外,PathClassLoader在加載類時調用的是DexFileloadClassBinaryName,而DexClassLoader調用的是loadClass。因此,在使用PathClassLoader時類全名需要用”/”替換”.”


    實際操作

           這一部分比較簡單,因此我就不贅言了。只是簡單的說下。

           可能使用到的工具都比較常規:javacdxeclipse等。其中dx工具最好是指明--no-strict,因為class文件的路徑可能不匹配。

           加載好類后,通常我們可以通過Java反射機制來使用這個類。但是這樣效率相對不高,而且老用反射代碼也比較復雜凌亂。更好的做法是定義一個interface,并將這個interface寫進容器端。待加載的類,繼承自這個interface,并且有一個參數為空的構造函數,以使我們能夠通過ClassnewInstance方法產生對象。然后將對象強制轉換為interface對象,于是就可以直接調用成員方法了。


    關于代碼加密的一些設想

           最初設想將dex文件加密,然后通過JNI將解密代碼寫在Native層。解密之后直接傳上二進制流,再通過defineClass將類加載到內存中。

           現在也可以這樣做,但是由于不能直接使用defineClass,而必須傳文件路徑給dalvik虛擬機內核,因此解密后的文件需要寫到磁盤上,增加了被破解的風險。

           Dalvik虛擬機內核僅支持從dex文件加載類的方式是不靈活的,由于沒有非常深入的研究內核,我不能確定是Dalvik虛擬機本身不支持還是Android在移植時將其閹割了。不過相信Dalvik或者是Android開源項目都正在向能夠支持raw數據定義類方向努力。

           我們可以在文檔中看到Google說:Jar or APK file with "classes.dex". (May expand this to include "raw DEX" in the future.);在AndroidDalvik源碼中我們也能看到RawDexFile的身影(不過沒有具體實現)。

           RawDexFile出來之前,我們都只能使用這種存在一定風險的加密方式。需要注意釋放的dex文件路徑及權限管理,另外,在加載完畢類之后,除非出于其他目的否則應該馬上刪除臨時的解密文件。


    轉載請注明出處:http://www.tkk7.com/zh-weir/archive/2011/10/29/362294.html 

    posted on 2011-10-29 21:51 zh.weir 閱讀(37953) 評論(25)  編輯  收藏 所屬分類: Android軟件安全

    評論

    # re: Android類動態加載技術 2011-11-30 13:25 android動態加載

    更好的做法是定義一個interface,并將這個interface寫進容器端
    >容器端做interface的強制類型轉換時出現異常,java.lang.ClassCastException:
    不知道您有沒有遇到?  回復  更多評論   

    # re: Android類動態加載技術 2011-11-30 20:52 zh-weir


    檢查一下,是否在Host端和Plugin端分別都定義了liangzijian這個接口。如果都定義了,且均被編譯進了字節碼文件中就會發生類似的問題。原因是 newInstance生成的對象實現的是Plugin端的liangzijian接口,而你想要轉化的是Host端的liangzijian接口。兩個接口雖然名字相同,甚至可能包名也一樣,但是它們是由不同的類加載器加載的,所以不同不能實現轉化。

    解決辦法是不要將liangzijian接口編譯進Plugin端的字節碼中。具體做法是導入liangzijian接口的jar庫時,不要使用添加外部jar庫,而應使用user library。后一種方式不會將庫中的類編譯進字節碼中,而是在運行時去動態鏈接。
      回復  更多評論   

    # re: Android類動態加載技術 2011-12-01 15:15 android動態加載

    @zh-weir
    問題解決了,需要注意兩點:
    1、Host端和Plugin端interface文件的包名需要一致
    2、jar文件打包時不要包含interface文件

    另外,DexClassLoader的最后一個參數使用getClassLoader()或者VMStack.getCallingClassLoader();  回復  更多評論   

    # re: Android類動態加載技術 2011-12-02 10:31 zephyranthes

    有點問題想請教樓主,之前使用dexclassloader想加載另一個apk的activity會出現activitynotfoundexception異常,似乎這種方式行不通.

    后來想到是不時只是寫一個jni的native activity,當apk運行時,會先運行這個jni so,然后該so生成解密的dex文件,再通過dexclassloader加載該dex中的activity?

    麻煩樓主解答,謝謝  回復  更多評論   

    # re: Android類動態加載技術 2012-02-17 23:59 passbye

    反復反復反復反復反復反復反復反復反復  回復  更多評論   

    # re: Android類動態加載技術 2012-02-18 00:01 pass

    請問對于(對Android應用加密,而只在執行時自解密,從而防止被破解?)這句話該如何理解,是對本地的(已安裝好的)apk加密還是什么操作?請大家指教。謝謝,希望大家共同進步。  回復  更多評論   

    # re: Android類動態加載技術 2012-02-28 09:19 zh_weir

    @pass

    由于apk應用需要經過Android系統的解析和安裝,因此是不能對它進行直接加密的哦。那樣會導致apk無法安裝或者無法運行。

    我的意思是將核心代碼抽取出來,作為二進制資源,由APK應用在運行時動態加載。  回復  更多評論   

    # re: Android類動態加載技術 2012-03-08 20:05 林風

    @android動態加載
    可否留個郵箱,交流一下,My Email --xk1411@139.com.  回復  更多評論   

    # re: Android類動態加載技術 2012-03-09 17:06 zh.weir

    @林風

    呵呵,其實右上方的公告里有寫。zh.weir@gmail.com 。

    歡迎交流!  回復  更多評論   

    # re: Android類動態加載技術 2012-03-26 10:04 ljj

    小弟現正開發一個這樣一個應用,這個應用做為主應用,只需要實現一些基本的功能就行,
    如果要擴展功能的話,就通過開發一些小插件來實現

    實現原理是這樣的:
    主應用(Host)定義一個接口:
    public abstract interface IExtension{
    void method1(WebView webview,....);
    void method2();


    public class AbstractExtension extends ContextWrapper implements IExtension{
    .....
    基本是對IExtension的空實現

    }


    這里應用反射來實例化一個插件:
    ClassLoader localExtensionClassLoader = context.createPackageContext(this.mPackageName,Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY).getClassLoader();
    ClassLoader localSystemClassLoader = OlivePackageManager.getSystemClassLoader();

    ClassLoaderWrapper localClassLoaderWrapper = new ClassLoaderWrapper(localExtensionClassLoader, localSystemClassLoader);

    String localClassName = String.valueOf(this.mPackageName) + ".Extension";
    Class<?> localClass = localClassLoaderWrapper.loadClass(localClassName);

    Constructor<?> localConstructor = localClass.getConstructor( new Class[]{Context.class} );
    localConstructor.setAccessible(true);

    Object instance = localConstructor.newInstance( new Object[]{context} );




    public class ClassLoaderWrapper extends ClassLoader{

    public ClassLoaderWrapper(ClassLoader extensionClassLoader, ClassLoader systemClassLoader) {
    super(systemClassLoader);
    this.mLoader = extensionClassLoader;
    }

    protected Class<?> findClass(String className) throws ClassNotFoundException {
    System.out.println("mLoader type === " + mLoader.toString());
    Class<?> localClass = null;

    if (localClass == null){
    //這個地方,就是這個地方,加載失敗
    localClass = this.mLoader.loadClass(className);
    }

    System.out.println("mLoader.loadClass============="+localClass);
    return localClass;
    }

    protected Class<?> loadClass(String className, boolean paramBoolean){
    Class<?> localClass = null;
    try {
    localClass = getParent().loadClass(className);
    } catch (ClassNotFoundException localClassNotFoundException) {
    localClass = findLoadedClass(className);

    if (localClass == null) {
    try {
    localClass = findClass(className);
    } catch (ClassNotFoundException e) {
    }
    }
    }

    return localClass;
    }




    在插件工程中:
    public class Extension extends AbstractExtension{
    ...實現IExtension接口


    這樣做的話,插件工程必須加入對主工程(Host)的引用



    思想是好的,但是問題出來了,我主應用的類加載器總是load不到插件工程的Extension類,
    但是如果我的插件工程里面不extends AbstractExtension 的話,是ok的,但是不繼承,就失去了意義。

    分析原因:
    public ClassLoaderWrapper(ClassLoader extensionClassLoader, ClassLoader systemClassLoader) {
    super(systemClassLoader);
    this.mLoader = extensionClassLoader;
    }
    這個構造函數里面,extensionClassLoader作為父類加載器,extensionClassLoader作為插件的類加載器,

    loadClass方法里面,先用systemClassLoader加載,應該是加載不到插件的Extension的,那么接下來就使用extensionClassLoader來加載,這個時候extensionClassLoader與插件的Extension是同一Context,理所當然能夠加載到,但是問題是Extension extends AbstractExtension,它肯定是加載不到AbstractExtension,導致插件的Extension也加載失敗

    這個問題困擾了我多日了,希望哪位給個意見,幫助分析一下,我應該怎么做?

      回復  更多評論   

    # re: Android類動態加載技術 2012-03-26 15:09 zh-weir

    @ljj

    之前的評論有說:

    需要注意兩點:
    1、Host端和Plugin端interface文件的包名需要一致
    2、jar文件打包時不要包含interface文件

    另外,DexClassLoader的最后一個參數使用getClassLoader()或者VMStack.getCallingClassLoader();  回復  更多評論   

    # re: Android類動態加載技術 2012-03-26 15:39 ljj

    樓主,我用的是自定的classloader,沒用DexClassLoader,我現在就是要動態加載插件的
    類Class<?> localClass = localClassLoaderWrapper.loadClass(localClassName);但是在自定義的classloader中的@Override
    protected Class<?> findClass(String className)
    throws ClassNotFoundException {
    System.out.println("mLoader type === " + mLoader.toString());
    Class<?> localClass = null;
    if ((this.mLoader instanceof PathClassLoader)){
    //localClass = a(paramString);

    }
    if (localClass == null) {
    // 這個地方,就是這個地方,加載失敗
    localClass = this.mLoader.loadClass(className);
    }

    System.out.println("mLoader.loadClass=============" + localClass);
    return localClass;
    }

    我沒有用到jar包,謝謝  回復  更多評論   

    # re: Android類動態加載技術 2012-03-26 16:45 zh-weir

    @ljj

    沒有用到jar,那你是從哪個文件里load類?這點需要明確。個人認為,第一可能是沒有指明從哪個文件load類;第二是IExtension被不同的類加載器加載了兩次,這樣兩個IExtension雖然名字相同、包名相同,但是它們是不同的類。  回復  更多評論   

    # re: Android類動態加載技術 2012-03-26 17:25 ljj

    @zh-weir

    我是要加載插件中的類

    String localClassName = String.valueOf(packageName)+ ".Extension";


    其中ClassLoaderWrapper 是自定義的classloader
    IExtension只在主host定義的接口
    在主host基本是對IExtension的空實現

    在插件中具體實現

    ClassLoaderWrapper localClassLoaderWrapper = new ClassLoaderWrapper (localExtensionClassLoader , localSystemClassLoader );
    String localClassName = String.valueOf(this.c)+ ".Extension";
    Class<?> localClass = localClassLoaderWrapper.loadClass(localClassName);


      回復  更多評論   

    # re: Android類動態加載技術 2012-03-26 17:37 zh.weir

    @ljj

    String localClassName = String.valueOf(this.c)+ ".Extension";
    Class<?> localClass = localClassLoaderWrapper.loadClass(localClassName);

    這是load插件中的類嗎?你的“插件”存在于哪?是安裝好的apk,還是未安裝的apk,還是jar或者dex文件?總需要一個路徑的。  回復  更多評論   

    # re: Android類動態加載技術 2012-03-27 08:17 ljj

    @zh-weir
    我的插件是已安裝好的apk,


    String localClassName = String.valueOf(this.c)+ ".Extension";
    Class<?> localClass = localClassLoaderWrapper.loadClass(localClassName);


    localClassName 是要去動態加載的插件中的類名,現在就是加載不上,
    自定義的classloader中

    @Override
    protected Class<?> findClass(String className)
    throws ClassNotFoundException {
    System.out.println("mLoader type === " + mLoader.toString());
    Class<?> localClass = null;
    if ((this.mLoader instanceof PathClassLoader)){
    //localClass = a(paramString);

    }
    if (localClass == null) {
    // 這個地方,就是這個地方,加載失敗
    localClass = this.mLoader.loadClass(className);
    }

    System.out.println("mLoader.loadClass=============" + localClass);
    return localClass;
    }  回復  更多評論   

    # re: Android類動態加載技術 2012-03-27 19:54 zh-weir

    又看了一下你的代碼。首先應該是AbstractExtension這個類沒有加載到。這個類,理論上應該是由Host端的類加載器加載好的。所以問題是,plugin端可能并沒有發現由host端加載好的AbstractExtension類。分析了可能的原因,覺得ClassLoader localSystemClassLoader = OlivePackageManager.getSystemClassLoader();這個Classloader可能并不是ClassLoader localExtensionClassLoader = context.createPackageContext(this.mPackageName,Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY).getClassLoader(); 這個ClassLoader的parent……  回復  更多評論   

    # re: Android類動態加載技術 2012-03-27 20:14 ljj

    我把插件工程 和主host 的代碼發到你郵箱了,幫看下吧,謝謝!  回復  更多評論   

    # re: Android類動態加載技術 2012-03-29 08:34 zh-weir

    不好意思,我可能周末才有時間看你的這份代碼哦……
    辦公室不讓收外部郵箱郵件,業余時間又少得可憐。  回復  更多評論   

    # re: Android類動態加載技術[未登錄] 2012-03-30 14:37 123

    @zh-weir
    按照這個方法我依然沒有實現,我用的是PathClassLoader。  回復  更多評論   

    # re: Android類動態加載技術[未登錄] 2012-03-30 15:04 123

    @zh-weir
    你好,看了你的帖子的確讓我學到不少。

    但是我的實現依然存在問題。
    以下是我最近一個項目的需求:我要使我的App(容器端)中某個固定的View可以替換為其他開發者開發的任意自定義控件。目前利用PathClassLoader已經實現了基本的替換功能。但是存在一個問題,跟上面幾位朋友討論的問題相似,在容器端我需要定義一些接口(目前是為了實現觀察者模式,當然以后可能提供更多的接口用以添加容器端和插件端的交互),在插件端的自定義控件可以實現這些接口,但是根據樓上提供的方法,依然存在ClassCastException的問題。

    以下是我實現的一些細節,請LZ看看哪里有問題:
    1.我使用的是PathClassLoader,因為插件需要被安裝,所以不用DexClassLoader。
    2.PathClassLoader構造的時候使用的是getSystemClassLoader。
    3.利用Eclipse將接口導出到jar(我這里不太清楚是否需要用dx工具將jar里面的.class類型轉化,我目前的做法是沒有轉化),且不含源文件,插件端可以編譯通過。  回復  更多評論   

    # re: Android類動態加載技術 2012-05-15 19:45 briancol

    博主:請教個問題:
    Dalvik 在加載類時, 如果該類已被加載,就不會重新加載。有么有辦法讓dalvik強制重新加載?
    這個需求的原因是因為: android系統有些自帶類會有各種限制,所以我想通過寫一個和系統某個類同包名同類名的類,然后讓dalvik加載我的這個類,從而突破某些限制。
    博主有沒有什么思路?  回復  更多評論   

    # re: Android類動態加載技術 2013-01-23 14:45 菜鳥1234

    不是很明白,既然有最后解密的過程,自然就可以找到解密的算法和密鑰,再怎么動態加載類又有什么用呢?  回復  更多評論   

    # re: Android類動態加載技術 2013-05-08 11:28 chuan

    說理清晰,thx  回復  更多評論   

    # re: Android類動態加載技術 2013-07-05 14:36 卷起鋪蓋流浪

    @briancol
    我和你在做同樣的事情,如果我想動態替換報名類名都相同的一個類,那么這個類dex加載后會被apk中存在的那個類的實例覆蓋,沒有實現動態加載的意義,請問你找到解決方法了么?  回復  更多評論   

    公告

    大家好!歡迎光臨我的 Android 技術博客!



    本博客旨在交流與 Android 操作系統相關的各種技術及信息。

    博客內的文章會盡量以開源的形式提供給大家,希望我們能相互交流,共同提高!

    有不足之處,請不吝賜教!

    我的郵箱:zh.weir@gmail.com
    我的新浪微博:@囧虎張建偉

     

    導航

    <2011年10月>
    2526272829301
    2345678
    9101112131415
    16171819202122
    23242526272829
    303112345

    統計

    留言簿(19)

    隨筆分類(24)

    隨筆檔案(18)

    文章檔案(1)

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 亚洲精品美女在线观看播放| 亚洲区精品久久一区二区三区| 亚洲人成网站18禁止| 91人人区免费区人人| 精品亚洲成a人片在线观看| 99精品视频免费| 亚洲最大AV网站在线观看| 国产免费AV片在线观看播放| 国产精品亚洲二区在线观看| 一级特黄特色的免费大片视频| 亚洲精品高清在线| 一个人免费观看视频在线中文| 中文字幕亚洲一区| 久热免费在线视频| 亚洲人成在线中文字幕| 最近2019中文字幕mv免费看| 无码一区二区三区亚洲人妻| 亚洲av再在线观看| a级毛片黄免费a级毛片| 久久亚洲精品成人无码网站 | 亚洲av午夜精品无码专区| 中文字幕影片免费在线观看 | 免费可以在线看A∨网站| 亚洲人成色在线观看| 亚洲黄片手机免费观看| 两个人看的www免费视频| 亚洲网站视频在线观看| 免费无码精品黄AV电影| 日韩毛片一区视频免费| 婷婷亚洲久悠悠色悠在线播放| 久久久久av无码免费网| 97在线免费观看视频| 亚洲人成网站影音先锋播放| 在线精品免费视频无码的 | 亚欧免费一级毛片| 亚洲www77777| 亚洲欧洲美洲无码精品VA| 又黄又爽又成人免费视频| 一级做a爰片久久免费| 亚洲福利视频一区二区三区| 国产免费观看视频|