在使用多个dex文件时,是否有必要将同一个包的类保留在同一个dex中

关于标题

“相同包的类”表示共享相同包访问的类。 请注意,类abcFoo没有类abBar的包访问 。 因为如果前者的修饰符是默认值,后者无法访问前者。

问题

如果我将同一个包中的两个类分成两个dex文件,即使我正确加载它们,我也会在运行时遇到一些错误,logcat喜欢:

I / dalvikvm(6498):DexOpt:非法方法访问(从Lcom / fish47 / multidex / TestMatchWord调用Lcom / fish47 / multidex / Foo; .isWholeWord(Lcom / fish47 / multidex / Foo;)Z;)
I / dalvikvm(6498):找不到方法com.fish47.multidex.core.Foo.isWholeWord,从com.fish47.multidex.core.TestMatchWord.test_english方法引用
W / dalvikvm(6498):VFY:无法解析虚方法758:Lcom / fish47 / multidex / Foo; .isWholeWord(Lcom / fish47 / multidex / Foo;)Z

推测

这是代码,它会弹出以下错误消息:
vm / analysis / Optimize.c ==> line:697 – 714

/* access allowed? */ tweakLoader(referrer, resMethod->clazz); bool allowed = dvmCheckMethodAccess(referrer, resMethod); untweakLoader(referrer, resMethod->clazz); if (!allowed) { IF_LOGI() { char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype); LOGI("DexOpt: illegal method access (call %s.%s %s from %s)\n", resMethod->clazz->descriptor, resMethod->name, desc, referrer->descriptor); free(desc); } if (pFailure != NULL) *pFailure = VERIFY_ERROR_ACCESS_METHOD; return NULL; } 

注意resClass-> classLoader的值。 如果这两个类不是来自同一个dex文件,则其值将设置为0xdead3333
vm / analysis / Optimize.c ==> line:285 – 299

 static void tweakLoader(ClassObject* referrer, ClassObject* resClass) { if (!gDvm.optimizing) return; assert(referrer->classLoader == NULL); assert(resClass->classLoader == NULL); if (!gDvm.optimizingBootstrapClass) { /* class loader for an array class comes from element type */ if (dvmIsArrayClass(resClass)) resClass = resClass->elementClass; if (referrer->pDvmDex != resClass->pDvmDex) resClass->classLoader = (Object*) 0xdead3333; } } 

这是一个技巧,它允许checkAccess(…)方法最后返回false,如果这两个类在同一个包中,彼此可访问但不公开。
vm / oo / AccessCheck.c ==> line:88 – 116

 static bool checkAccess(const ClassObject* accessFrom, const ClassObject* accessTo, u4 accessFlags) { /* quick accept for public access */ if (accessFlags & ACC_PUBLIC) return true; /* quick accept for access from same class */ if (accessFrom == accessTo) return true; /* quick reject for private access from another class */ if (accessFlags & ACC_PRIVATE) return false; /* * Semi-quick test for protected access from a sub-class, which may or * may not be in the same package. */ if (accessFlags & ACC_PROTECTED) if (dvmIsSubClass(accessFrom, accessTo)) return true; /* * Allow protected and private access from other classes in the same * package. */ return dvmInSamePackage(accessFrom, accessTo); } 

vm / oo / AccessCheck.c ==> line:39 – 83

 bool dvmInSamePackage(const ClassObject* class1, const ClassObject* class2) { ... /* class loaders must match */ if (class1->classLoader != class2->classLoader) return false; ... } 

这是一个有点奇怪的领域,主要是由于dexopt执行的“预validation”和优化。 对于背景,您应该阅读oo / Class.cpp开头的注释 (第39-153行)。

(注意:文件在ICS中已从“.c”更改为“.cpp”。您可能应该检查当前的来源,但实际上在过去几年中这里没有什么变化。)

一般来说,只要两个DEX文件都由同一个类加载器加载,不同DEX文件中同一个包中的两个类就能够使用包范围相互访问。 这就是AccessCheck.cpp中的检查所强制执行的。

您在Optimize.cpp中看到的是解析器的并行实现 – dvmOptResolveClassdvmResolveClass – 在validation和优化期间使用。 它会像你指出的那样调整类加载器,但!gDvm.optimizing是它在dexopt中运行(这是检查!gDvm.optimizing意思)。 如果它位于正常执行的VM实例中,则在检查期间不会调整加载程序。

当作为dexopt的一部分运行时,Optimize.cpp中的代码要么validation+优化引导类,要么validation+优化单个非引导DEX文件。 无论哪种方式,所有DEX文件都是通过引导加载程序加载的,因为VM并没有真正运行,而且它是加载类的唯一方法。 (dexopt的目的是在构建或安装时validation尽可能多的类,因此我们不必在app启动时执行此操作 。请在此处阅读有关dexopt的更多信息。)

tweakLoader的代码说:如果我在tweakLoader中,并且我没有优化实际的bootstrap DEX文件(例如framework.jar),那么我需要确保包范围检查假定当前的类引导类加载器没有加载DEX文件。

例如,我可以在我的应用程序中创建一个名为java.lang.Stuff的类。 在dexopt中,因为所有内容都由一个加载器加载,所以如果我们不调整加载器,它将能够触及其他java.lang类中的包私有内容。 实际运行应用程序时, java.lang类来自引导加载程序,而Stuff类来自应用程序加载程序,因此应禁止这些访问。

这就是代码的作用。 就你的具体问题而言,我希望只要使用相同的加载器加载两个DEX文件,调用就会起作用。 如果一个DEX由应用程序框架加载,另一个由自定义DexClassLoader那么我不希望它工作。

另外一个注意事项:您粘贴的错误提到com.fish47.multidex.Foocom.fish47.multidex.core.Foo ,它们不是同一个包。 我不知道那是否相关。 此外,如果有额外的VFY消息,那么包含这些消息是很好的,即使它们有点难以理解。 对于任何这种性质而言,指定您正在使用的Android版本也很重要 – 它在一段时间内没有发生变化,但如果你回到足够远的地方则会有很大不同。