我所知道的ClassLoader

你对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)