【问题标题】:Is it necessary to keep classes of the same package in the same dex while using multiple dex files使用多个dex文件时是否需要将同一个包的类保持在同一个dex中
【发布时间】:2013-08-16 12:45:30
【问题描述】:

关于标题

“同一个包的类”是指共享同一个包访问的类。请注意类 a.b.c.Foo 没有包访问类 a.b.Bar 的事实。因为如果前者的修饰符是默认的,后者就无法访问前者。

问题

如果我将同一个包中的两个类拆分成两个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;

    ...
} 

【问题讨论】:

    标签: java android classloader dalvik dex


    【解决方案1】:

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

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

    一般来说,只要两个 DEX 文件由同一个类加载器加载,不同 DEX 文件中同一包中的两个类就可以在包范围内相互访问。这就是 AccessCheck.cpp 中执行的检查。

    您在 Optimize.cpp 中看到的是解析器的并行实现——dvmOptResolveClassdvmResolveClass——在验证和优化期间使用。如您所述,它将调整类加载器,但如果它在 dexopt 内部运行(这就是检查 !gDvm.optimizing 的含义)。如果它位于正常执行的 VM 实例中,则在检查期间不会调整加载程序。

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

    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 消息,最好包含这些消息,即使它们有点难以理解。对于这种性质的任何事情,指定您正在使用的 Android 版本也很重要——它已经有一段时间没有改变了,但如果你回溯到足够远,它就会非常不同。

    【讨论】:

    • 感谢您耐心的回复。这很有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-14
    • 1970-01-01
    • 2013-12-18
    • 1970-01-01
    相关资源
    最近更新 更多