前言
在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势。本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病。
声明
欢迎转载,但请保留文章原始出处:)
博客园:http://www.cnblogs.com
农民伯伯: http://over140.cnblogs.com
Android中文翻译组:http://androidbox.sinaapp.com/
正文
一、 基本概念和注意点
1.1 首先需要了解一点:在Android中可以动态加载,但无法像Java中那样方便动态加载jar
原因:Android的虚拟机(Dalvik VM)是不认识Java打出jar的byte code,需要通过dx工具来优化转换成Dalvik byte code才行。这一点在咱们Android项目打包的apk中可以看出:引入其他Jar的内容都被打包进了classes.dex。
所以这条路不通,请大家注意。
1.2 当前哪些API可用于动态加载
1.2.1 DexClassLoader
这个可以加载jar/apk/dex,也可以从SD卡中加载,也是本文的重点。
1.2.3 PathClassLoader
只能加载已经安装到Android系统中的apk文件。
二、 准备
本文主要参考"四、参考文章"中第一篇文章,补充细节和实践过程。
2.1 下载开源项目
http://code.google.com/p/goodev-demo
将项目导入工程,工程报错的话应该是少了gen文件夹,手动添加即可。注意这个例子是从网上下载优化好的jar(已经优化成dex然后再打包成的jar)到本地文件系统,然后再从本地文件系统加载并调用的。本文则直接改成从SD卡加载。
三、实践
3.1 编写接口和实现
3.1.1 接口IDynamic
package com.dynamic;
public interface IDynamic {
public String helloWorld();
}
3.1.2 实现类DynamicTest
package com.dynamic;
public class DynamicTest implements IDynamic {
@Override
public String helloWorld() {
return "Hello World!";
}
}
3.2 打包并转成dex
3.2.1 选中工程,常规流程导出即可,如图:
注意:在实践中发现,自己新建一个Java工程然后导出jar是无法使用的,这一点大家可以根据文章一来了解相关原因,也是本文的重点之一。这里打包导出为dynamic.jar
(后期修复:打包请不要把接口文件打进来,参见文章末尾后续维护!)
3.2.2 将打包好的jar拷贝到SDK安装目录android-sdk-windows\platform-tools下,DOS进入这个目录,执行命名:
dx --dex --output=test.jar dynamic.jar
3.3 修改调用例子
修改MainActivity,如下:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mToastButton = (Button) findViewById(R.id.toast_button);
// Before the secondary dex file can be processed by the DexClassLoader,
// it has to be first copied from asset resource to a storage location.
// final File dexInternalStoragePath = new File(getDir("dex", Context.MODE_PRIVATE),SECONDARY_DEX_NAME);
// if (!dexInternalStoragePath.exists()) {
// mProgressDialog = ProgressDialog.show(this,
// getResources().getString(R.string.diag_title),
// getResources().getString(R.string.diag_message), true, false);
// // Perform the file copying in an AsyncTask.
// // 从网络下载需要的dex文件
// (new PrepareDexTask()).execute(dexInternalStoragePath);
// } else {
// mToastButton.setEnabled(true);
// }
mToastButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
// Internal storage where the DexClassLoader writes the optimized dex file to.
//final File optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);
final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().toString()
+ File.separator + "test.jar");
// Initialize the class loader with the secondary dex file.
// DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
// optimizedDexOutputPath.getAbsolutePath(),
// null,
// getClassLoader());
DexClassLoader cl = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(),
Environment.getExternalStorageDirectory().toString(), null, getClassLoader());
Class libProviderClazz = null;
try {
// Load the library class from the class loader.
// 载入从网络上下载的类
// libProviderClazz = cl.loadClass("com.example.dex.lib.LibraryProvider");
libProviderClazz = cl.loadClass("com.dynamic.DynamicTest");
// Cast the return object to the library interface so that the
// caller can directly invoke methods in the interface.
// Alternatively, the caller can invoke methods through reflection,
// which is more verbose and slow.
//LibraryInterface lib = (LibraryInterface) libProviderClazz.newInstance();
IDynamic lib = (IDynamic)libProviderClazz.newInstance();
// Display the toast!
//lib.showAwesomeToast(view.getContext(), "hello 世界!");
Toast.makeText(MainActivity.this, lib.helloWorld(), Toast.LENGTH_SHORT).show();
} catch (Exception exception) {
// Handle exception gracefully here.
exception.printStackTrace();
}
}
});
}
3.4 执行结果
四、参考文章
五、补充
大家可以看看DexClassLoader的API文档,里面不提倡从SD卡加载,不安全。此外,我也正在组织翻译组尽快把这个命名空间下的几个类都翻译出来,以供大家参考。
工程下载:这里,Dex文件下载:这里。大家可以直接把Dex文件拷贝到SD卡,然后运行例子。
六、后期维护
6.1 2011-12-1 修复本文错误
感谢网友ppp250和liuzhaocn的反馈,基本按照评论2来修改:
6.1.1 不需要在本工程里面导出jar,自己新建一个Java工程然后导出来也行。
6.1.2 导出jar时不能带接口文件,否则会报以下错:
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
6.1.3 将jar优化时应该重新成jar(jar->dex->jar),如果如下命令:
dx --dex --output=test.jar dynamic.jar
以下转自云在千峰博客:
普通的Android程序Dalvik虚拟机都是从一个默认的地方载入程序需要的类文件(dex文件),而Dalvik虚拟机还提供了从其他地方载入类的能力(比如从设备的内部存储空间以及互联网).
这种自定义类加载机制可以使用于一些场景:
dex文件只能包含最多64K的函数引用,对于大型的程序如果超过了该数字,就可以通过把程序打包为多个dex文件来实现,在程序运行的时候在加载这些需要的类
一些开发框架可以通过运行时自定义类加载机制来设计出更加可扩展的框架
通过该方式实现更加强壮的程序注册机制,防止被人破解
Android有个示例项目演示了如何使用, 项目地址
http://code.google.com/p/android-custom-class-loading-sample/
要使用该示例,不能使用Eclipse插件ADT来打包必需通过该项目提供的Ant脚本, 另外该Ant脚本需要Android SDK 12版本, 可以通过Android SDK Manager来下载或者到如下地址下载,然后解压到对应的目录中
https://dl-ssl.google.com/android/repository/tools_r12-windows.zip
https://dl-ssl.google.com/android/repository/platform-tools_r06-windows.zip
在示例中有3个类文件:
com.example.dex.MainActivity: UI界面,在这个类中动态载入需要的类
com.example.dex.LibraryInterface: 动态载入类的接口定义
com.example.dex.lib.LibraryProvider: 动态载入类的实现,该类在打包的时候会打包到另外一个dex文件中
在打包的时候需要修改项目目录下的local.properties文件,把sdk.dir的值修改为对应的android SDK目录.例如:sdk.dir=E:\\google\\android-sdk-windows
另外不要忘记在default.properties中指定需要的android平台: 例如 target=android-9
然后就可以执行android install来build并且安装到模拟器或者手机上了.
载入自定义类的过程
获取需要载入的自定义类的dex文件,可以是设备本地的文件或者互联网上的文件
把获取到的自定义类dex文件保存到程序的内部储存空间中:new File(getDir(“dex”, Context.MODE_PRIVATE),SECONDARY_DEX_NAME);
通过DexClassLoader类加载器来解析优化前面的dex文件
通过DexClassLoader的loadClass函数来载入类
通过获得到的类的newInstance函数来生成需要的对象
开始使用获取到的动态类对象~\(≧▽≦)/~啦啦啦
Android提供的示例项目中,通过Ant打包后把com.example.dex.lib.LibraryProvider类放入了程序的assets文件夹中,然后从这里读取需要动态载入的类. 为了演示从互联网载入类和使用Eclipse ADT插件来build该示例,我们对该项目做了简单修改,修改后的示例项目地址:
http://code.google.com/p/goodev-demo 中 的android-custom-class-loading-goodev-demo
在该示例中删除了com.example.dex.lib.LibraryProvider类,我们把该类打包为dex文件并且放入到了互联网上下载地址: http://goodev.sinaapp.com/and/secondary_dex.jar
在程序运行的时候先从该地址下载需要的类文件,然后解析.
详细情况请参考项目中的代码注释
修改后的项目可以通过Eclipse ADT来build.
Read more: http://blog.chengyunfeng.com/?p=87#ixzz2x8vY4oTM
来源:oschina
链接:https://my.oschina.net/u/661133/blog/213332