Android 加载外部 dex文件中的类 的源码实例

关于安卓类加载机制分析可以参考:https://blog.csdn.net/u013394527/article/details/80980340

类加载机制  是 研究插件化 和 热修复 的基础。 加载外部 dex文件中的类,我们在这部分主要做的流程有:

  • 1.编写基本的Java文件并编译为.class文件。
  • 2.将.class文件转为.dex文件。
  • 3.将转好的dex文件放入创建好的Android工程内并在启动时将其写入本地。
  • 4.加载解压后的.dex文件中的类,并调用其方法进行测试。

前提提交:系统环境里,配置jdk,Android sdk,然后开始以下操作:

 

1、创建一个独立的java 类  (TestUtil.java)

public class TestUtil {

    public void testFunc() {
        System.out.println("jack test func");
    }


    public String getStr(int no) {
        return "jack number:"+no;
    }

}

2, 用jdk里的javac 编译生成class文件,以下是编译:

3.用Android sdk里的dx工具,把class文件生成dex: (Android\Sdk\build-tools\30.0.3\dx.bat  ),build-tools的每个版本下都有dx,我用的是build-tools\30.0.3下dx.bat

dx的基本用法如下:

 dx --dex  [--output=<file>]  [<file>.class | <file>.{zip,jar,apk} | <directory>] ...

参数1:--dex 表示要执行打包 dex动作; 
参数2: --output=myA.dex 表示设置生成的文件的名称;   
参数3:需要转换的class文件

同理:也可以把jar打包成dex

dx --dex --output=mytarget.dex  myorigin.jar

4,在Android工程中加载外部dex文件

实现思路:把外部 独立的 dex文件 拷贝到 安卓项目的 assets 目录下,     app运行时 将 asstes 目录下的文件 通过 流式读取 复制到 应用的私有目录下。
为什么要复制出来?因为assets目录下的文件并不能直接通过new File(xxx_path)的形式来访问, 它只能通过 Context.getAssets().open() 来获取一个访问流。    
通过 DexClassLoader 读取 复制到app私有目录 下的 dex,调用 dexClassLoader.loadClass("Test") 获取 对应的 Class<?>, 并通过构造函数创建找对象,再通过反射访问其内部的方法


源码如下:

  DexClassLoader dexClassLoader_a;
    public void OperateDexA(View view) {
        if (null == dexClassLoader_a) {
            dexClassLoader_a = loadDex("myA.dex", false);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (null == dexClassLoader_a) {
            return;
        }

        try {
            Class<?> clazz = dexClassLoader_a.loadClass("TestUtil");
            System.out.println("loaded clas: " + clazz);
            System.out.println("class loader: " + clazz.getClassLoader());
            System.out.println("class loader parent: " + clazz.getClassLoader().getParent());
            Constructor constructor = clazz.getConstructor();
            constructor.setAccessible(true);
            Object obj = constructor.newInstance();
            Method getStr = clazz.getDeclaredMethod("getStr", int.class);
//            getStr.setAccessible(true);
            Object result = getStr.invoke(obj, 666);
            System.out.println("result:>>>>>>>>   " + result.toString());
            Method testFunc = clazz.getDeclaredMethod("testFunc");
            testFunc.setAccessible(true);
            testFunc.invoke(obj);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //dex文件里要加载so库,演示如何载入带有so库的dex
    private DexClassLoader loadDex(String dexName, boolean hasLibs) {
        File originDex = null;
        try {
            InputStream open = getAssets().open(dexName);
            File dexOutputDir = getCacheDir();
            System.out.println("getCacheDir---189: " + dexOutputDir.getAbsolutePath());
            originDex = new File(dexOutputDir, dexName);
            FileOutputStream fileOutputStream = new FileOutputStream(originDex);
            byte[] bytes = new byte[1024];
            int length = 0;
            while ((length = open.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, length);
            }
            fileOutputStream.flush();
            fileOutputStream.close();
            open.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

//        2.创建DexClassLoader加载dex文件中的类
        if (originDex != null) {
            File dexOptimizeDir = getDir("dex", Context.MODE_PRIVATE);
            String dexOutputPath = dexOptimizeDir.getAbsolutePath();
            //把外部dex要System.loadLibrary("native-lib")的so文件放在jniLibs目录打包进当前应用里,
            // 演示如何让外部的dex在当前应用里设置搜索libpath
            String librarySearchPath = null;
            if (hasLibs) {
                librarySearchPath = getApplicationInfo().nativeLibraryDir;
                System.out.println("librarySearchPath: " + librarySearchPath);
            }
            return new DexClassLoader(originDex.getAbsolutePath(), dexOutputPath, librarySearchPath,
                    getClassLoader());
        } else {
            return null;
        }
    }

执行运行后输出如下:

二、如何把不同包下的class文件打包成一个dex。(放在包下的类,要有相应的文件路径,如:com\jack\Category.class)

调用代码如下:

 DexClassLoader dexClassLoader_b;

    public void OperateDexB(View view) {

        if (null == dexClassLoader_b) {
            dexClassLoader_b = loadDex("myB.dex", false);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (null == dexClassLoader_b) {
            return;
        }

        try {
            Class<?> categoryclazz = dexClassLoader_b.loadClass("com.jack.Category");
            System.out.println("loaded clas: " + categoryclazz);
            System.out.println("class loader: " + categoryclazz.getClassLoader());
            System.out.println("class loader parent: " + categoryclazz.getClassLoader().getParent());
            Constructor categoryconstructor = categoryclazz.getConstructor();
            categoryconstructor.setAccessible(true);
            Object category = categoryconstructor.newInstance();
            Field channel = categoryclazz.getDeclaredField("channel");
//                channel.setAccessible(true);
            channel.set(category, "cmakeDemo_channel");
            Method cateMethod = categoryclazz.getDeclaredMethod("toString");
            System.out.println("toString:>>>>>> " + cateMethod.invoke(category));

            Method cateSum = categoryclazz.getDeclaredMethod("sum", int.class, int.class);
            cateSum.invoke(category, 10, 22);

            System.out.println(">>>>>>>>>>>>>>>>>>>Employee:>>>>>>>>   ");

            Class<?> clazz = dexClassLoader_b.loadClass("Employee");
            Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class, int.class);
//            constructor.setAccessible(true);
            Object obj = constructor.newInstance("Peter", 1, 8000);
            Method raiseSalary = clazz.getDeclaredMethod("raiseSalary", int.class);
            raiseSalary.setAccessible(true);
            raiseSalary.invoke(obj, 5000);
            Method printEmployee = clazz.getDeclaredMethod("printEmployee");
            printEmployee.setAccessible(true);
            printEmployee.invoke(obj);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //dex文件里要加载so库,演示如何载入带有so库的dex
    private DexClassLoader loadDex(String dexName, boolean hasLibs) {
        File originDex = null;
        try {
            InputStream open = getAssets().open(dexName);
            File dexOutputDir = getCacheDir();
            System.out.println("getCacheDir---189: " + dexOutputDir.getAbsolutePath());
            originDex = new File(dexOutputDir, dexName);
            FileOutputStream fileOutputStream = new FileOutputStream(originDex);
            byte[] bytes = new byte[1024];
            int length = 0;
            while ((length = open.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, length);
            }
            fileOutputStream.flush();
            fileOutputStream.close();
            open.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

//        2.创建DexClassLoader加载dex文件中的类
        if (originDex != null) {
            File dexOptimizeDir = getDir("dex", Context.MODE_PRIVATE);
            String dexOutputPath = dexOptimizeDir.getAbsolutePath();
            //把外部dex要System.loadLibrary("native-lib")的so文件放在jniLibs目录打包进当前应用里,
            // 演示如何让外部的dex在当前应用里设置搜索libpath
            String librarySearchPath = null;
            if (hasLibs) {
                librarySearchPath = getApplicationInfo().nativeLibraryDir;
                System.out.println("librarySearchPath: " + librarySearchPath);
            }
            return new DexClassLoader(originDex.getAbsolutePath(), dexOutputPath, librarySearchPath,
                    getClassLoader());
        } else {
            return null;
        }
    }

运行的结果如下:

三、加载的dex需要载入动态库 so文件的

可以用AS 生成一个带有so的apk

生成dex有两个方法:

①、编写gradle的task 把需要打包的class 打包生成jar,然后在利用以上的dx命令打包生成dex文件

def curVersion = '1.0.3.12'

def jarName = "Player-" + getDate() + "-v" + curVersion + "-release"
def javaFile = file('build/libs/java.jar')
//package jar
task buildJavaJar(type: Jar) {
    archiveName = "java.jar"
    def srcClassDir = [project.buildDir.absolutePath + "/intermediates/javac/debug/classes"]
    from srcClassDir

    exclude "com/mymusic/player/BuildConfig.class"
    exclude "com/mymusic/player/MainActivity.class"
    include "com/mymusic/beans/Node.class"
    include "com/mymusic/utils/JNITools.class"
    include "com/mymusic/utils/MyNetWork.class"
}
task buildAssetsJar(type: Jar, dependsOn: buildJavaJar) {
    from zipTree(javaFile)
    from fileTree(dir: 'src/main', includes: ['assets/**'])
    baseName = jarName
}

②就是把生成的apk里dex取出来

然后把生成的so文件,拷贝到jniLibs文件下

拷贝到以下

DexClassLoader的 librarySearchPath 改为当前应用的lib:(librarySearchPath = getApplicationInfo().nativeLibraryDir;)

源码如下:


    //dex文件里要加载so库,演示如何载入带有so库的dex
    private DexClassLoader loadDex(String dexName, boolean hasLibs) {
        File originDex = null;
        try {
            InputStream open = getAssets().open(dexName);
            File dexOutputDir = getCacheDir();
            System.out.println("getCacheDir---189: " + dexOutputDir.getAbsolutePath());
            originDex = new File(dexOutputDir, dexName);
            FileOutputStream fileOutputStream = new FileOutputStream(originDex);
            byte[] bytes = new byte[1024];
            int length = 0;
            while ((length = open.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, length);
            }
            fileOutputStream.flush();
            fileOutputStream.close();
            open.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

//        2.创建DexClassLoader加载dex文件中的类
        if (originDex != null) {
            File dexOptimizeDir = getDir("dex", Context.MODE_PRIVATE);
            String dexOutputPath = dexOptimizeDir.getAbsolutePath();
            //把外部dex要System.loadLibrary("native-lib")的so文件放在jniLibs目录打包进当前应用里,
            // 演示如何让外部的dex在当前应用里设置搜索libpath
            String librarySearchPath = null;
            if (hasLibs) {
                librarySearchPath = getApplicationInfo().nativeLibraryDir;
                System.out.println("librarySearchPath: " + librarySearchPath);
            }
            return new DexClassLoader(originDex.getAbsolutePath(), dexOutputPath, librarySearchPath,
                    getClassLoader());
        } else {
            return null;
        }
    }

    DexClassLoader dexClassLoader;
    public void operate_another_dex(View view) {
        if (null == dexClassLoader) {
            dexClassLoader = loadDex("myclasses.dex", true);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (null == dexClassLoader) {
            return;
        }
        try {
            Class<?> jnitoolclazz = dexClassLoader.loadClass("com.mymusic.utils.JNITools");
            System.out.println("loaded clas: " + jnitoolclazz);
            System.out.println("class loader: " + jnitoolclazz.getClassLoader());
            System.out.println("class loader parent: " + jnitoolclazz.getClassLoader().getParent());
            Constructor jnitoolconstructor = jnitoolclazz.getConstructor();
            jnitoolconstructor.setAccessible(true);
            Object jnitoolobj = jnitoolconstructor.newInstance();

            Method jnitoolMethod = jnitoolclazz.getDeclaredMethod("testNodeFunc", int.class, String.class);
            System.out.println("testNodeFunc:>>>>>> " + jnitoolMethod.invoke(jnitoolobj, 100, "my-name is Peter"));

            Method jnitoolstringFromJNI = jnitoolclazz.getDeclaredMethod("stringFromJNI");
            System.out.println("stringFromJNI:" + jnitoolstringFromJNI.invoke(jnitoolobj));

            Method jnitoolsum = jnitoolclazz.getDeclaredMethod("jniSum", int.class, int.class);
            System.out.println("jnitoolsum>>>>>:" + jnitoolsum.invoke(jnitoolobj, 101, 202));


        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            Class<?> jninetclazz = dexClassLoader.loadClass("com.mymusic.utils.MyNetWork");
            System.out.println("loaded clas: " + jninetclazz);
            System.out.println("class loader: " + jninetclazz.getClassLoader());
            System.out.println("class loader parent: " + jninetclazz.getClassLoader().getParent());
            Constructor jninetconstructor = jninetclazz.getConstructor();
            jninetconstructor.setAccessible(true);
            Object jninetobj = jninetconstructor.newInstance();

            Method jninetMethod = jninetclazz.getDeclaredMethod("syncGet", String.class);
            System.out.println("syncGet:>>>>>> " + jninetMethod.invoke(jninetobj, "https://www.baidu.com"));


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

输出如下:

 

调用的外部代码关键部分也贴出来,如下:

package com.mymusic.utils;

import android.util.Log;

import com.mymusic.beans.Node;

public class JNITools {
    String TAG = "jniTag";

    static {
        System.loadLibrary("good-native-lib");
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    public native int jniSum(int a, int b);

    public String testNodeFunc(int no, String name) {

        Node node = new Node(no, name);
        Log.d(TAG, "init data>>>>>: " + node);

        node.updateData(888, stringFromJNI());
        Log.d(TAG, "three updateDate data>>>>>: " + node);

        return "JNITools:" + node;
    }
}
#include <jni.h>
#include <string>
#include "android/log.h"

#ifdef __cplusplus
extern "C" {
#endif


#ifndef  LOG_TAG
#define  LOG_TAG    "AndroidJNI"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#endif

JNIEXPORT jstring JNICALL
Java_com_mymusic_utils_JNITools_stringFromJNI(JNIEnv *env, jobject thiz) {
    std::string hello = "hello from C++";
    return env->NewStringUTF(hello.c_str());
}
#ifdef __cplusplus
};
#endif
extern "C"
JNIEXPORT jint JNICALL
Java_com_mymusic_utils_JNITools_jniSum(JNIEnv *env, jobject thiz, jint a, jint b) {
    jint result = a + b;
//    __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "%d + %d = %d", a, b, result);
    LOGE("%d + %d = %d", a, b, result);
    return result;
}

cmake_minimum_required(VERSION 3.10.2)


project("player")


add_library( # Sets the name of the library.
        good-native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        good-native-lib.cpp)

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

target_link_libraries( # Specifies the target library.
        good-native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

总结一下,以此勉励,如有什么问题欢迎指正。

 

 

参考以下文章:

https://blog.csdn.net/u013394527/article/details/81384468

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值