Android中動態(tài)加載jar包

概述

前段時間在開發(fā)外設驅(qū)動程序時,涉及到了動態(tài)加載jar包的知識,于是開始學習了下。
這里主要是完成客戶端驅(qū)動jar包代碼編寫,所以只涉及到java接口實現(xiàn)問題,然后將jar包經(jīng)過dex轉(zhuǎn)換,放到測試程序的指定目錄下即可進行驅(qū)動測試。

將jar包轉(zhuǎn)換成dex編碼格式

Android中Dalvik/ART虛擬機能夠直接加載的是dex文件。
使用dx命令將導出的驅(qū)動jar包轉(zhuǎn)為dex編碼格式,擴展名還是保持為jar不變即可。

// dx  -- 如27.0.3版本為Android\Sdk\build-tools\27.0.3下的dx.bat腳本
// XXX.jar  -- 為轉(zhuǎn)換后的文件名,仍保持擴展名為jar
// XXX-origin.jar  -- 要進行轉(zhuǎn)換前的jar包
dx --dex --output=XXX.jar XXX-origin.jar

加載經(jīng)過dex轉(zhuǎn)碼后的jar包

Android中使用DexClassLoader來動態(tài)加載指定路徑下的經(jīng)過dex轉(zhuǎn)化過的jar包或apk文件。
DexClassLoader類只有一個構造方法參數(shù)說明

// dexPath  -- 經(jīng)dex優(yōu)化的jar包/apk存放路徑
// optimizedDirectory  -- 解壓出的dex文件的存放路徑,不可存放在外置存儲,以免被注入攻擊
// librarySearchPath  -- C/C++依賴的本地庫文件目錄,可以為null
// parent  -- 父類的類加載器
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent);

獲取DexClassLoader實例

private DexClassLoader getClassLoader(String paramString) {
    String dexPath = new File(getAppJarDir(), paramString).getAbsolutePath(); // 經(jīng)過dex轉(zhuǎn)碼后的jar包存放路徑
    String optimizedDirectory = getAppDexDir().getAbsolutePath(); // dex優(yōu)化文件存放路徑
    String librarySearchPath = getAppLibDir().getAbsolutePath(); // 本地依賴庫存放路徑
    ClassLoader parent = getClassLoader(); // 父類的類加載器
    return new DexClassLoader(dexPath, optimizedDirectory, librarySearchPath, parent);
}

調(diào)用dex里面的代碼

這里可以使用兩種方式來調(diào)用dex里面的代碼:

  • 通過接口實現(xiàn)方式
  • 通過反射機制方式
通過接口實現(xiàn)方式

將方法抽象成公共接口,并制作成jar包,直接用于開發(fā)驅(qū)動jar包和驅(qū)動測試程序。
測試程序中可以直接對加載后的類實例進行強制類型轉(zhuǎn)換來直接調(diào)用方法。
接口定義如下:

// (Java Library)
// pluginif/src/main/java/com.shellever.plugin/PluginIf.java

package com.shellever.plugin;

public interface PluginIf {
    String getDeviceInfo();
}

接口實現(xiàn)如下:

// (Java Library)
// pluginimpl/src/main/java/com.shellever.plugin/PluginImpl.java

package com.shellever.plugin;

public class PluginImpl implements PluginIf {
    @Override
    public String getDeviceInfo() {
        return "Shellever";
    }
}

測試程序中動態(tài)調(diào)用方式如下:

// (Phone)
// app/src/main/java/com.shellever.dexclassloader.MainActivity.java

public void testDexClassLoaderWithIf() {
    // 加載接口具體實現(xiàn)的經(jīng)過dex轉(zhuǎn)換過的jar包
    DexClassLoader dexClassLoader = getClassLoader("pluginImpl.jar");
    try {
        // 加載接口具體實現(xiàn)類
        Class pluginImplClazz = dexClassLoader.loadClass("com.shellever.plugin.PluginImpl");
        PluginIf pluginIf = (PluginIf) pluginImplClazz.newInstance();   // 直接強制類型轉(zhuǎn)換
        String devInfo = pluginIf.getDeviceInfo();  // 調(diào)用接口方法
        mLoaderResultTv.setText(devInfo);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    }
}
通過反射機制方式

通過文檔接口約定來編寫驅(qū)動時,在測試程序中只能通過反射的方法來調(diào)用接口方法了。
驅(qū)動實現(xiàn)代碼如下:

// (Phone)
// app/src/main/java/com.shellever.dexclassloader.MainActivity.java

package com.shellever.plugin.common;

import android.content.Context;

public class Common {

    private Context mContext;

    public Common(Context context){
        mContext = context;
    }

    public String getDeviceInfo(){
        return "Shellever.HPF";
    }
}

測試程序這邊考慮到多模塊驅(qū)動時,保證接口的正確性和程序的可擴展性,通過xml文件對模塊接口進行定義:

// app/src/main/res/xml/device_class_module.xml

<?xml version="1.0" encoding="utf-8"?>
<devices>
    <modules>
        <module id="COMMON" name="com.shellever.plugin.common.Common" args="Context"/>
    </modules>
    <functions>
        <function id="COMMON" name="getDeviceInfo" args=""/>
    </functions>
</devices>

其中的標簽及屬性定義說明:
modules用于定義模塊集合;
module用于模塊類路徑及構造方法的定義,id為模塊名稱,name為模塊類全路徑,args為構造方法參數(shù)類型。
functions為具體模塊的方法聲明;
function用于具體方法聲明,id為指定所屬模塊,name為方法名稱,args為方法參數(shù)類型。

測試程序中xml解析代碼如下:

public HashMap<String, DeviceInfo> parseDeviceInfo(){
    HashMap<String, DeviceInfo> deviceInfo = new HashMap<>();
    XmlResourceParser parser = getResources().getXml(R.xml.device_class_module);
    try {
        int eventType = parser.getEventType();
        while(eventType != XmlPullParser.END_DOCUMENT){
            switch (eventType){
                case XmlPullParser.START_TAG:
                    if("module".equals(parser.getName())){
                        DeviceInfo localDeviceInfo = new DeviceInfo();
                        localDeviceInfo.name = parser.getAttributeValue(null, "name");
                        localDeviceInfo.args = parser.getAttributeValue(null, "args");
                        deviceInfo.put(parser.getAttributeValue(null, "id"), localDeviceInfo);
                    }
                    if("function".equals(parser.getName())){
                        DeviceInfo localDeviceInfo = new DeviceInfo();
                        localDeviceInfo.name = parser.getAttributeValue(null, "name");
                        localDeviceInfo.args = parser.getAttributeValue(null, "args");
                        deviceInfo.put(parser.getAttributeValue(null, "id") + localDeviceInfo.name, localDeviceInfo);
                    }
                    break;
            }
            eventType = parser.next();
        }
    } catch (XmlPullParserException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return deviceInfo;
}

public class DeviceInfo {
    public String args;
    public String name;
}

測試程序中動態(tài)調(diào)用代碼如下:

public void testDexClassLoaderWithReflect() {
    HashMap<String, DeviceInfo> deviceInfo = parseDeviceInfo(); // 解析xml定義文件

    // 加載接口具體實現(xiàn)的經(jīng)過dex轉(zhuǎn)換過的jar包
    DexClassLoader dexClassLoader = getClassLoader("pluginDevInfo.jar");
    // 獲取COMMON模塊構造方法及參數(shù)
    DeviceInfo localDeviceInfo = deviceInfo.get("COMMON");
    String className = localDeviceInfo.name;
    String classArgs = localDeviceInfo.args;
    Log.d(TAG, "className = " + className);

    // 獲取COMMON模塊接口方法及參數(shù)
    DeviceInfo localDeviceInfo2 = deviceInfo.get("COMMON" + "getDeviceInfo");
    String functionName = localDeviceInfo2.name;
    String functionArgs = localDeviceInfo2.args;
    Log.d(TAG, "functionName = " + functionName);
    try {
        Class pluginDevInfoClazz = dexClassLoader.loadClass(className);
        Object localObject = pluginDevInfoClazz.getConstructor(getParamType(classArgs)).newInstance(new Object[]{this});
        Method localMethod = localObject.getClass().getDeclaredMethod(functionName, getParamType(functionArgs));
        String returnType = localMethod.getReturnType().getSimpleName(); // 獲取方法返回值類型
        if("String".equals(returnType)){
            // 通過反射調(diào)用接口方法
            String devinfo = (String) localMethod.invoke(localObject, new Object[0]);
            mLoaderResultTv.append("\n" + devinfo);
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

導出jar包方式

Eclipse導出jar包比較簡單,詳細步驟可以參考這里: 使用eclipse工程導出jar包

Android Studio中創(chuàng)建Java Library (Android Library會同時創(chuàng)建資源文件及目錄)默認會創(chuàng)建jar任務,直接雙擊運行即可生成jar包。

Gradle-Tasks-build-jar.png

當創(chuàng)建模塊是app應用程序時,則需要手動編寫Gradle任務來將編譯生成的class文件打包成jar包:

// plugindevinfo模塊打jar包任務
// Phone
task clearJar(type: Delete) {
    // 編譯完成后jar包存放位置
    delete 'build/libs/pluginDevInfo.jar'
    delete 'libs/pluginDevInfo.jar'
}
//打包任務
task makeJar(type: Jar) {
    //指定生成的jar名
    baseName 'pluginDevInfo'
    //從哪里打包class文件
    from('build/intermediates/classes/release/com/shellever/plugin')
    //打包到jar后的目錄結構
    into('com/shellever/plugin/')
    //去掉不需要打包的目錄和文件
    exclude('BuildConfig.class', 'R.class')
    //去掉R$開頭的文件
    exclude { it.name.startsWith('R$') }
}
makeJar.dependsOn(clearJar, build)

GitHub源代碼參考

DexClassLoader

參考文章

使用eclipse工程導出jar包
AS生成jar包,并在其他工程中引用jar包的方法
Android動態(tài)加載入門 簡單加載模式

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容