Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的普通需求。但是有些特殊问题,常常引发我们进一步的沉思。我们从沉思中产生顿悟,从而产生新的技术形式。
如何开发一个可以自定义控件的Android应用?就像eclipse一样,可以动态加载插件;如何让Android应用执行服务器上的不可预知的代码?如何对Android应用加密,而只在执行时自解密,从而防止被破解?……
熟悉Java技术的朋友,可能意识到,我们需要使用类加载器灵活的加载执行的类。这在Java里已经算是一项比较成熟的技术了,但是在Android中,我们大多数人都还非常陌生。
类加载机制
Dalvik虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载可以从class文件中读取,也可以是其他形式的二进制流。因此,我们常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。
然而Dalvik虚拟机毕竟不算是标准的Java虚拟机,因此在类加载机制上,它们有相同的地方,也有不同之处。我们必须区别对待。
例如,在使用标准Java虚拟机时,我们经常自定义继承自ClassLoader的类加载器。然后通过defineClass方法来从一个二进制流中加载Class。然而,这在Android里是行不通的,大家就没必要走弯路了。参看源码我们知道,Android中ClassLoader的defineClass方法具体是调用VMClassLoader的defineClass本地静态方法。而这个本地方法除了抛出一个“UnsupportedOperationException”之外,什么都没做,甚至连返回值都为空。
static void Dalvik_java_lang_VMClassLoader_defineClass(const u4* args,
JValue* pResult)
{
Object* loader = (Object*) args[0];
StringObject* nameObj = (StringObject*) args[1];
const u1* data = (const u1*) args[2];
int offset = args[3];
int len = args[4];
Object* pd = (Object*) args[5];
char* name = NULL;
name = dvmCreateCstrFromString(nameObj);
LOGE("ERROR: defineClass(%p, %s, %p, %d, %d, %p)\n",
loader, name, data, offset, len, pd);
dvmThrowException("Ljava/lang/UnsupportedOperationException;",
"can't load this type of class file");
free(name);
RETURN_VOID();
}
Dalvik虚拟机类加载机制
那如果在Dalvik虚拟机里,ClassLoader不好使,我们如何实现动态加载类呢?Android为我们从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader。其中需要特别说明的是PathClassLoader中一段被注释掉的代码:
这从另一方面佐证了defineClass函数在Dalvik虚拟机里确实是被阉割了。而在这两个继承自ClassLoader的类加载器,本质上是重载了ClassLoader的findClass方法。在执行loadClass时,我们可以参看ClassLoader部分源码:
/**//* --this doesn't work in current version of Dalvik--
if (data != null) {
System.out.println("--- Found class " + name
+ " in zip[" + i + "] '" + mZips[i].getName() + "'");
int dotIndex = name.lastIndexOf('.');
if (dotIndex != -1) {
String packageName = name.substring(0, dotIndex);
synchronized (this) {
Package packageObj = getPackage(packageName);
if (packageObj == null) {
definePackage(packageName, null, null,
null, null, null, null, null);
}
}
}
return defineClass(name, data, 0, data.length);
}
*/
因此DexClassLoader和PathClassLoader都属于符合双亲委派模型的类加载器(因为它们没有重载loadClass方法)。也就是说,它们在加载一个类之前,回去检查自己以及自己以上的类加载器是否已经加载了这个类。如果已经加载过了,就会直接将之返回,而不会重复加载。
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
// Don't want to see this.
}
if (clazz == null) {
clazz = findClass(className);
}
}
return clazz;
}
DexClassLoader和PathClassLoader其实都是通过DexFile这个类来实现类加载的。这里需要顺便提一下的是,Dalvik虚拟机识别的是dex文件,而不是class文件。因此,我们供类加载的文件也只能是dex文件,或者包含有dex文件的.apk或.jar文件。
也许有人想到,既然DexFile可以直接加载类,那么我们为什么还要使用ClassLoader的子类呢?DexFile在加载类时,具体是调用成员方法loadClass或者loadClassBinaryName。其中loadClassBinaryName需要将包含包名的类名中的”.”转换为”/”。我们看一下loadClass代码就清楚了:
在这段代码前有一段注释,截取关键一部分就是说:If you are not calling this from a class loader, this is most likely not going to do what you want. Use {@link Class#forName(String)} instead. 这就是我们需要使用ClassLoader子类的原因。至于它是如何验证是否是在ClassLoader中调用此方法的,我没有研究,大家如果有兴趣可以继续深入下去。
public Class loadClass(String name, ClassLoader loader) {
String slashName = name.replace('.', '/');
return loadClassBinaryName(slashName, loader);
}
有一个细节,可能大家不容易注意到。PathClassLoader是通过构造函数new DexFile(path)来产生DexFile对象的;而DexClassLoader则是通过其静态方法loadDex(path, outpath, 0)得到DexFile对象。这两者的区别在于DexClassLoader需要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来说,就是PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。
另外,PathClassLoader在加载类时调用的是DexFile的loadClassBinaryName,而DexClassLoader调用的是loadClass。因此,在使用PathClassLoader时类全名需要用”/”替换”.”。
实际操作
这一部分比较简单,因此我就不赘言了。只是简单的说下。
可能使用到的工具都比较常规:javac、dx、eclipse等。其中dx工具最好是指明--no-strict,因为class文件的路径可能不匹配。
加载好类后,通常我们可以通过Java反射机制来使用这个类。但是这样效率相对不高,而且老用反射代码也比较复杂凌乱。更好的做法是定义一个interface,并将这个interface写进容器端。待加载的类,继承自这个interface,并且有一个参数为空的构造函数,以使我们能够通过Class的newInstance方法产生对象。然后将对象强制转换为interface对象,于是就可以直接调用成员方法了。
关于代码加密的一些设想
最初设想将dex文件加密,然后通过JNI将解密代码写在Native层。解密之后直接传上二进制流,再通过defineClass将类加载到内存中。
现在也可以这样做,但是由于不能直接使用defineClass,而必须传文件路径给dalvik虚拟机内核,因此解密后的文件需要写到磁盘上,增加了被破解的风险。
Dalvik虚拟机内核仅支持从dex文件加载类的方式是不灵活的,由于没有非常深入的研究内核,我不能确定是Dalvik虚拟机本身不支持还是Android在移植时将其阉割了。不过相信Dalvik或者是Android开源项目都正在向能够支持raw数据定义类方向努力。
我们可以在文档中看到Google说:Jar or APK file with "classes.dex". (May expand this to include "raw DEX" in the future.);在Android的Dalvik源码中我们也能看到RawDexFile的身影(不过没有具体实现)。
在RawDexFile出来之前,我们都只能使用这种存在一定风险的加密方式。需要注意释放的dex文件路径及权限管理,另外,在加载完毕类之后,除非出于其他目的否则应该马上删除临时的解密文件。
分享到:
相关推荐
Android中实现动态加载机制,可以做成插件技术的实现
Android动态加载技术.pdf
动态加载技术 该技术在Java中是一个比较成熟的技术,而Android中该技术还没有被大家充分利用起来。该技术思想主要分为以下几步: 1.将核心代码编译成dex文件的Jar包 2. 对jar包进行加密处理 3.在程序主入口利用NDK...
Android动态加载技术的研究.pdf
android ViewPager实现动态添加View对象,demo用了2种方式实现,一种是点击动态添加 一种是滑动时动态添加 2种方式测试都能成功。 欢迎对android感兴趣的朋友技术交流。本人QQ 351531626 添加QQ请带上说明哦 =。=
基于OSGI的Android动态模块加载技术的研究.pdf
通过使用LayoutInflater 每次点击按钮时候去读取布局文件,然后找到布局文件里面的各个VIEW 操作完VIEW 后加载进我们setContentView 方面里面的要放的布局...可能会领会不一样的心得,祝你早上掌握android 的开发技术。
(3)Hook技术动态加载APK文件中的Activity 二、Android 动态加载dex文件 1.反射方式加载(较麻烦,不介绍),需要反射出具体的方法等 2.接口编程的方式加载(以下重点介绍),只需要反射出具体的类即可,转换成接口...
17.6.2 加载物体类——loadedobjectvertexnormal 488 17.6.3 加载物体刚体类——loadrigidbody 490 17.7 铰链关节 491 17.7.1 铰链关节的基本知识 491 17.7.2 案例的运行效果 492 17.7.3 铰链关节...
Android大图家在技术,包括内存解决方案
主题简介:本演讲将结合凡客的Android客户端应用实例,详细介绍凡客应用的实现架构,阐述分析电商应用中的多线程的合理使用、电商应用中因商品图片加载过多引发的内存溢出的优化处理等技术问题。演讲提纲涉及:(1)...
Android延迟加载综合案例,关于ListView和ImageView的分页延迟加载,已经若干有用的封装技术。
从0开始的3d游戏开发书籍,包含高级一点的模型加载,最后还有游戏开发的实例。
《android技术内幕:系统卷》 前言 第1章 准备工作 /1 1.1 深入认识android /2 1.1.1 android的系统构架 /2 1.1.2 android的初始化流程 /5 1.1.3 各个层次之间的相互关系 /8 1.1.4 android系统开发(移植)和应用...
开放源代码:Android系统采用开放源代码模式,允许开发者自由访问、修改和定制操作系统,这促进了技术的创新和发展,使得Android系统具有高度的灵活性和可定制性。 多任务处理:Android允许用户同时运行多个应用程序...
android动态添加表格行.zip项目安卓应用源码下载android动态添加表格行.zip项目安卓应用源码下载 1.适合学生毕业设计研究参考 2.适合个人学习研究参考 3.适合公司开发项目技术参考
Android动态加载Apk-插件化技术(动态代理方案) 一. 什么是插件化 1. 主App(宿主App)加载插件apk的实现 2. 每个业务组件模块形成一个独立的Apk, 然后通过主App动态加载部署业务组件模块Apk的一种方案 二.插件化的...
本项目是基于Android开发的一款全新的旅行社交类app,用户可以注册本软件,成为用户。可以通过本软件发布一些自己喜欢的好玩的信息,让其他用户可以参与到其中和你一起去旅行,同样你也可以选择自己感兴趣的活动进行...
android 解析加载图片是一个常用的技术 但是如何优化 这是一个问题 想要用户体验好必须加载速度快 稳定 那么你看了这个代码就明白了
在android中给图片添加水印文字和图片