由于项目需要一个jar包,但是这个jar包比较大,有几百kb(项目的优点之一就是安装包体积小),综合考虑后决定采用网络下载后动态加载jar包。于是我用周六日两天研究了一下这个技术,记录下来,以备以后查阅。
关于动态加载,理论上可以用到的有DexClassLoader、PathClassLoader和URLClassLoader。
DexClassLoader :可以加载文件系统上的jar、dex、apk。jar和apk能被加载也是因为里面包含dex的格式。
PathClassLoader(Android应用中的默认加载器):只能加载/data/app中的apk,也就是已经安装到手机中的apk。
URLClassLoader :可以加载java中的jar,但是由于android不能直接识别jar,所以此方法在android中无法使用。
通过Eclipse生成jar包步骤是,选中工程,右键 ->Export->Java目录下的JAR file -> 选择要打包进去的java文件,选择jar文件的存储路径。-> Finish.
原始的jar是不行的,需要转换成android能识别的字节码dex。使用dx工具,可以在andorid的SKD目录下面搜索一下这个工具,以前的说在platform-tools这个目录下面但是我没有找到,我是在build-tools的子目录里面找到了不同版本的dx工具,我将最新版本的dx文件和当前目录下得lib目录一起拷贝到platform-tools。
转换命令 :
dx --dex --output=dest.jar src.jar
先简单介绍一下DexClassLoader 和 PathClassLoader
public DexClassLoader(String dexPath, String dexOutputDir, String libPath, ClassLoader parent) public PathClassLoader(String path, String libPath, ClassLoader parent)
可以看出来PathClassLoader的构造函数比DexClassLoader的少了一个参数dexOutDir,
这个原因很简单啦,因为PathClassLoader加载的是安装后的也就是/data/app中得apk,
而这部分的apk都会解压释放dex到指定的目录/data/dalvik-cache,这个释放解压操作
是系统做得,所以不需要dexOutDir参数。
下面就是jar包中的代码,有对象函数也有静态函数和枚举。
package com.dynamic; import android.app.AlertDialog; import android.app.Dialog; import android.app.AlertDialog.Builder; import android.content.Context; import android.content.DialogInterface; import android.widget.Toast; public class DynamicTest { public enum arithmetic { plus, minus, multiply, divide; }; private Context mContext; public DynamicTest(Context context) { mContext = context; } public static String helloWorld() { return "helloWorld"; } public static int arithmetic(arithmetic sign, int a, int b) { int s = 0; switch (sign) { case plus: s = a + b; break; case minus: s = a - b; break; case multiply: s = a * b; break; case divide: if (b != 0) { s = a / b; } break; default: break; } return s; } public void toastShow(String showString) { Toast.makeText(mContext, showString, Toast.LENGTH_SHORT).show(); } public void showPluginWindow(String[] message) { AlertDialog.Builder builder = new Builder(mContext); builder.setMessage(message[1]); builder.setTitle(message[0]); builder.setNegativeButton(message[2], new Dialog.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); Dialog dialog = builder.create(); dialog.show(); } }
选择要导出的文件保存成jar包,然后在终端执行 cd 到platform-tools目录下执行
dx --dex --output=test.jar dynamic.jar
将转码后的test.jar push到手机的sd卡上
adb push test.jar /sdcard/text.jar
然后就是通过反射执行这些函数,下面是对4个函数的调用
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory() .toString() + File.separator + apkFileName); optimizedDexOutputPath.setExecutable(true); File dexOutputDirs = getApplicationContext().getDir("dex", 0); final DexClassLoader cl = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(), dexOutputDirs.getAbsolutePath(), null, getClass().getClassLoader()); mToastButton = (Button) findViewById(R.id.helloWorld); mArithmetic = (Button) findViewById(R.id.arithmetic); mToastShow = (Button) findViewById(R.id.toastShow); mShowPluginWindow = (Button) findViewById(R.id.showPluginWindow); mShowPluginWindow.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Class<?> libProviderClazz = null; try { libProviderClazz = cl.loadClass("com.dynamic.DynamicTest"); String[] strMessage = { "title", "message", "ok" }; Method method = libProviderClazz.getDeclaredMethod("showPluginWindow", new Class[] { String[].class }); Constructor<?> con = libProviderClazz.getConstructor(Context.class);// 返回该类的构造方法 method.invoke(con.newInstance(MainActivity.this), (Object) strMessage); } catch (Exception exception) { exception.printStackTrace(); } } }); mToastShow.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Class<?> libProviderClazz = null; try { libProviderClazz = cl.loadClass("com.dynamic.DynamicTest"); Method method = libProviderClazz.getDeclaredMethod("toastShow", new Class[] { String.class }); Constructor<?> con = libProviderClazz.getConstructor(Context.class);// 返回该类的构造方法 method.invoke(con.newInstance(MainActivity.this), "好吗!"); } catch (Exception exception) { exception.printStackTrace(); } } }); mArithmetic.setOnClickListener(new View.OnClickListener() { @TargetApi(Build.VERSION_CODES.CUPCAKE) @SuppressLint("NewApi") @Override public void onClick(View v) { // TODO Auto-generated method stub Class<?> libProviderClazz = null; try { libProviderClazz = cl.loadClass("com.dynamic.DynamicTest"); Class<?> libProviderEnumClazz = cl .loadClass("com.dynamic.DynamicTest$arithmetic"); Method[] methods = libProviderEnumClazz.getMethods(); Field[] fields = libProviderEnumClazz.getFields(); for (Method m : methods) { // 打印所有方法 Log.d("abcd", m.toString()); } for (Field f : fields) {// 打印所有对象 Log.d("abcde", f.toString()); } Field f = libProviderEnumClazz.getField("multiply"); Method method = libProviderClazz.getDeclaredMethod("arithmetic", new Class[] { libProviderEnumClazz, int.class, int.class }); int s = (Integer) method.invoke(null, f.get(libProviderEnumClazz), 5, 8); Toast.makeText(MainActivity.this, String.valueOf(s), Toast.LENGTH_SHORT).show(); } catch (Exception exception) { exception.printStackTrace(); } } }); mToastButton.setOnClickListener(new View.OnClickListener() { @SuppressLint("NewApi") @TargetApi(Build.VERSION_CODES.CUPCAKE) public void onClick(View view) { Class<?> libProviderClazz = null; try { libProviderClazz = cl.loadClass("com.dynamic.DynamicTest"); Method[] methods = libProviderClazz.getMethods(); for (Method m : methods) { Log.d("abcd", m.toString()); } Method method = libProviderClazz .getDeclaredMethod("helloWorld", new Class[] {}); // 如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法,不通过对象调用 Toast.makeText(MainActivity.this, (String) method.invoke(null), Toast.LENGTH_SHORT).show(); } catch (Exception exception) { exception.printStackTrace(); } } }); }
通过ClassLoader装载类,调用其内部函数的过程有点烦琐,包括先构造出Method对象,并构造出Method对象所使用的参数对象,然后才能调用。
有一种方法比较简单,那就是定义interface接口,interface仅仅定义函数的输入输出,却不定义函数的具体实现,然后在插件工程导出jar包的时候不要勾选interface文件,因为interface是要包含在主工程里的,如果导出jar包也被包含在里面,程序运行的时候是会出错的(程序运行时包含了两份interface文件)。因为不像反射这么烦琐,就不记录了。