前言
虽然之前公司的项目有用到动态加载技术,但是并没有太在意,今天突然看到别人的app中有用到动态加载,打算了解一下。
概述
Android使用Dalvik虚拟机加载可执行程序,所以不能直接加载基于class的jar,而是需要将class转化为dex字节码,从而执行代码。优化后的字节码文件可以存在一个.jar中,只要其内部存放的是.dex即可使用。
实验步骤
Android Studio为IDE
1.创建动态加载模块
1.1 在工程中新建需要实现动态加载的模块,并添加测试类,如下图
其中base-lib表示动态加载模块,DynamicTest表示要动态加载的类,IDynamic类用于DynamicTest的父类接口。
1.1.1.创建IDynamic接口
package com.lgf.plugin;
/**
* 打包时不能打包此类
*/
public interface IDynamic {
String show();
}
1.1.2 创建一个DynamicTest 类实现IDynamic 接口
package com.lgf.base;
import com.lgf.plugin.IDynamic;
public class DynamicTest implements IDynamic {
@Override
public String show() {
return "DynamicTest";
}
}
1.2 打包jar
在该模块的build.gradle中添加打包的task
apply plugin: 'com.android.library'
android {
...省略一百字
}
dependencies {
...省略一百字
}
task buildJar(dependsOn: ['assembleRelease'], type: Jar) {
archiveName = "origin.jar" // 打包普通jar的名字
from('build/intermediates/classes/release/com/lgf/base/')
into('com/lgf/base/')
exclude 'com/lgf/plugin/IDynamic.class' // 去除IDynamic类
}
// 执行此方法生成dex的jar
task exportPluginJar(dependsOn: ['buildJar'], type: Exec) {
def outJar = buildJar.archivePath.getAbsolutePath()
commandLine "dx.bat", "--dex", "--output=\"build/libs/plugin.jar\"", "${outJar}"
}
1.3 命令行窗口执行如下命令,即可在模块的build/libs目录下生成plugin.jar
gradlew exportPluginJar
注意:需要将dx添加到系统环境变量中(要将普通的jar转换为包含dex的jar,可使用Android SDK自带的dx工具,可将dx加入到系统的环境变量中,方便使用(注意大部分SDK的dx文件是在platform-tools文件夹下,也有一些是在build-tools文件夹下)。
1.4 如果不用1.2和1.3步骤,也可手动打jar:到要转换的jar(这里假设为origin.jar)目录,执行
dx --dex --output=plugin.jar origin.jar
会发现在该目录下生成了一个plugin.jar文件,这个文件就是android工程可以加载的文件。
1.5.将生成的jar放入手机SD卡
进入该模块的build/libs目录,执行如下命令
adb push plugin.jar /sdcard
2.主模块使用动态加载
2.1 复制动态加载模块中IDynamic类到主模块,包名也要设置成一样的,如下图
2.2 实现动态加载
package com.lgf.dynamicload.demo;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
import com.lgf.plugin.IDynamic;
import java.io.File;
import dalvik.system.DexClassLoader;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// layout相应的View中使用onClick属性
public void showMessage(View view) {
// 此路径为插件存放路径
File dexPathFile = new File(Environment.getExternalStorageDirectory() + File.separator + "plugin.jar");
String dexPath = dexPathFile.getAbsolutePath();
String dexDecompressPath = getDir("dex", MODE_PRIVATE).getAbsolutePath(); // dex解压后的路径
// String dexDecompressPath = Environment.getExternalStorageDirectory().getAbsolutePath(); // 不能放在SD卡下,否则会报错
/**
* DexClassLoader参数说明
* 参数1 dexPath:待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限(<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> )
* 参数2 optimizedDirectory:解压后的dex存放位置,此位置一定要是可读写且仅该应用可读写(安全性考虑),所以只能放在data/data下。本文getDir("dex", MODE_PRIVATE)会在/data/data/**package/下创建一个名叫”app_dex1“的文件夹,其内存放的文件是自动生成output.dex;如果不满足条件,Android会报错误
* 参数3 libraryPath:指向包含本地库(so)的文件夹路径,可以设为null
* 参数4 parent:父级类加载器,一般可以通过Context.getClassLoader获取到,也可以通过ClassLoader.getSystemClassLoader()获取到。
*/
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, dexDecompressPath, null, getClassLoader());
Class libClazz = null;
try {
libClazz = dexClassLoader.loadClass("com.lgf.base.DynamicTest");
IDynamic lib = (IDynamic) libClazz.newInstance();
Toast.makeText(this, lib.show(), Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3 添加权限
由于要读取sd卡的文件,因此需要添加读取权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>