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



关于标题

"相同包的类"是指共享相同包访问权限的类。请注意,类a.b.c.Foo没有对类a.b.Bar的包访问权限。因为如果前者的修饰符为默认值,则后者无法访问前者

问题

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

I/dalvikvm(6498):DexOpt:非法方法访问(从Lcom/fish47/multidex/TestMatchWord;调用Lcom/fiish47/multidex/Foo;.isWholeWord(Lcom/fish47/multiex/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/分析/优化.c===>行: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/分析/优化.c===>行: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===>行: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===>行:39-83

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

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

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

一般来说,只要两个DEX文件都由同一个类加载器加载,不同DEX文件中的同一个包中的两个类就可以通过包作用域相互访问。这就是AccessCheck.cpp中的检查所强制执行的。

您在Optimize.cpp中看到的是在验证和优化过程中使用的解析器的并行实现——dvmOptResolveClassdvmResolveClass。正如您所指出的,它将调整类加载器,但只有在dexopt内运行时才(这就是针对!gDvm.optimizing的检查的意思)。如果它在一个正常执行的VM实例中,那么在检查过程中不会调整加载程序。

当作为dexopt的一部分运行时,Optimize.cpp中的代码要么是验证+优化引导程序类,要么是验证-优化单个非引导程序DEX文件。无论哪种方式,所有的DEX文件都是通过引导加载程序加载的,因为虚拟机并没有真正运行,这是加载类的唯一方式。(dexopt的目的是在构建或安装时验证尽可能多的类,这样我们就不必在应用程序启动时进行验证。点击此处了解更多关于dexopt。)

tweakLoader中的代码说:如果我在dexopt中,并且我没有优化实际的引导程序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消息,最好包括这些消息,即使它们有点难以理解。对于这种性质的东西,指定你使用的安卓版本也很重要——它已经有一段时间没有改变了,但如果你追溯到足够远的地方,那就大不相同了。

最新更新