我所知道的ClassLoader

作者: ___刘辉良 | 来源:发表于2019-11-07 16:49 被阅读0次

    你对Android中的ClassLoader了解吗?

    在回答这个问题之前,我们需要知道AndroidClassLoader的类型。通过IDEA的类的继承结构示意图可以看到。

    image.png

    可以看到有很多类型的ClassLoader,我们可以尝试着看看,平常我们使用的都是哪些ClassLoader呢?我们随便运行一个空的项目然后断点看下

    image.png

    从断点中,我们可以知道

    1. 加载MainActivity类的ClassLoaderPathClassLoader
    2. PathClassLoader的父亲是BootClassLoader

    PathClassLoader

    通过上述的类的继承结构图可以知道,PathClassLoader属于BaseDexClassLoader的子类。在Anroid中,PathClassLoader通常用来加载已经安装的apk的dex文件(安装的apk的dex文件会存储在/data/dalvik-cache中)

    BootClassLoader

    Android系统启动时会使用BootClassLoader来预加载常用类。

    image.png

    DexClassLoader

    除了上述介绍的两个类型,还有DexClassLoader也经常被使用到。因为它可以根据路径加载dex文件以及包含dex的压缩文件(apk和jar文件)。这样就为动态加载提供了可能性。

    public class DexClassLoader extends BaseDexClassLoader {
       
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    }
    

    我们可以知道DexClassLoader需要四个参数

    1. 要加载类文件的路径
    2. 优化dex文件的目录,不能为空
    3. 包含C/C++库的路径集合,多个路径用文件分隔符分隔分割,可以为空
    4. 父加载器

    动态加载代码

    上面介绍了很多概念性的东西。接下来要实战一下。动态加载我们SD中的jar文件。然后调用方法。首先我们需要一个被加载的jar文件。先编译一个测试类,这个类很简单,只是返回一串字符串

    
    public class HelloWorld {
        public HelloWorld() {
     
        }
        public static final String getMessage() {
            return "hello world";
        }
    }
    
    

    然后找到这个类的字节码,它所在的目录如图

    image.png

    使用命令行,编译该字节码jar文件(因为是文件夹的关系,中间需要创建一个MANIFEST.MF文件)然后使用命令将class文件编译成jar文件

    jar cvf demo.jar HelloWorld.class   
    

    当我们得到jar文件后,将jar文件放到assest文件夹中。尝试着用自己的ClassLoader来加载它。

    
     public class MainActivity extends AppCompatActivity implements PermissionUtils.SimpleCallback {
    
    
        public static final String FINAL_PATH = SDCardUtils.getSDCardPathByEnvironment() + "/new/demo2.jar";
    
        TextView tvHelloWorld;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            tvHelloWorld = findViewById(R.id.tvHelloWorld);
            PermissionUtils.permission(PermissionConstants.STORAGE).callback(this).request();
        }
    
        @SuppressWarnings("all")
        @Override
        public void onGranted() {
            boolean copyResult = ResourceUtils.copyFileFromAssets("demo2.jar", FINAL_PATH);
            if (copyResult) {
                File cc = getDir("dex", 0);
                DexClassLoader classLoader = new DexClassLoader(FINAL_PATH, cc.getAbsolutePath(), null, getClassLoader());
                try {
                    Class mm = classLoader.loadClass("cc.dd.mm.HelloWorld");
                    Method method = mm.getMethod("getMessage");
                    String message = (String) method.invoke(null);
                    tvHelloWorld.setText(message);
                } catch (Exception e) {
                    System.out.println(e);
                }
            }
        }
    
        @Override
        public void onDenied() {
    
        }
    
    }
    

    可以看到,代码的逻辑很简单。就是检查SD卡的读写权限。然后将事先放在assets文件夹中的jar拷贝到SD卡中。用自己的ClassLoader来加载jar文件。之后尝试调用其中的方法。

    当你尝试的运行APP的时候,你会发现有一个这样的错误

    No original dex files found for dex location

    这是因为我们之前直接将.class文件转化成.jar文件。但是Android Dalvik并不能识别java二进制代码。所以我们需要将刚刚生成的jar文件,改成能被Android Dalvik所识别的jar文件。这里需要dx工具,帮我们完成这个任务。(这个工具在:安卓安装目录下\SDK\build-tools)

    dx --dex --output=old.jar new.jar
    

    这样你就得到一个新的jar文件,然后替换之前的jar。再次运行。就会得到你想要的结果了。

    相关文章

      网友评论

        本文标题:我所知道的ClassLoader

        本文链接:https://www.haomeiwen.com/subject/hsmvbctx.html