4

タイトルについて

「同じパッケージのクラス」とは、同じパッケージ アクセスを共有するクラスを意味します。クラスabcFooはクラスabBarへのパッケージ アクセスを持っていないことに注意してください。前者の修飾子がデフォルトの場合、後者は前者にアクセスできないためです。

問題

同じパッケージ内の 2 つのクラスを 2 つの dex ファイルに分割すると、それらを正しくロードしても、実行中にエラーが発生します。logcat は次のようになります。

I/dalvikvm( 6498): DexOpt: 不正なメソッド アクセス (Lcom/fish47/multidex/Foo;.isWholeWord (Lcom/fish47/multidex/Foo;)Z から Lcom/fish47/multidex/TestMatchWord を呼び出す;)
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の値に注意してください。2 つのクラスが同じ dex ファイルからのものでない場合、その値は0xdead3333に設定されます。
vm/analysis/Optimize.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;
    }
}


これはトリックです。2 つのクラスが同じパッケージ内にあり、互いにアクセス可能であるが公開されていない場合、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;

    ...
} 
4

1 に答える 1

4

これは、dexopt によって実行される「事前検証」と最適化が主な原因で、やや奇妙な領域です。背景については、 oo/Class.cpp (行 39 ~ 153)の先頭にあるコメントを読む必要があります。

(注: ファイルは ICS で ".c" から ".cpp" に変更されました。実際には、ここ数年でほとんど変更されていませんが、おそらく現在のソースを調べる必要があります。)

一般に、異なる DEX ファイル内の同じパッケージ内の 2 つのクラスは、両方の DEX ファイルが同じクラス ローダーによってロードされる限り、パッケージ スコープで相互にアクセスできます。それが、AccessCheck.cpp のチェックが強制するものです。

Optimize.cpp で見ているのは、検証と最適化の際に使用されるリゾルバー ( dvmOptResolveClassvs. ) の並列実装です。dvmResolveClassあなたが指摘したように、クラスローダーを微調整しますが、 dexopt内で実行されている場合のみです(これがチェックの!gDvm.optimizing意味です)。正常に実行されている VM インスタンス内にある場合、ローダーはチェック中に調整されません。

dexopt の一部として実行する場合、Optimize.cpp のコードは、ブートストラップ クラスの検証と最適化、または単一の非ブートストラップ DEX ファイルの検証と最適化のいずれかです。どちらの方法でも、VM は実際には実行されておらず、クラスをロードする唯一の方法であるため、すべての DEX ファイルはブートストラップ ローダーを介してロードされます。(dexopt のポイントは、ビルド時またはインストール時にできるだけ多くのクラスを検証することです。これにより、アプリの起動時に検証する必要がなくなります。dexopt の詳細については、こちらを参照してください。)

のコードは次のtweakLoaderように述べています: dexopt を使用していて、実際のブートストラップ DEX ファイル (framework.jar など) を最適化していない場合、パッケージ スコープ チェックで、現在の DEX のクラスがファイルがブートストラップ クラス ローダーによってロードされていません。

たとえばjava.lang.Stuff、アプリで呼び出されるクラスを作成できます。dexopt では、すべてが単一のローダーによってロードされるためjava.lang、ローダーを微調整しなければ、他のクラスのパッケージ プライベートな要素に触れることができます。実際にアプリを実行すると、java.langクラスはブートストラップ ローダーから取得され、Stuffクラスはアプリ ローダーから取得されるため、それらのアクセスは禁止する必要があります。

それがコードが行うことです。特定の問題に関する限り、同じローダーを使用して両方の DEX ファイルをロードする限り、呼び出しが機能すると思います。1 つの DEX がアプリ フレームワークによって読み込まれ、もう 1 つの DEX がカスタムによって読み込まれる場合、DexClassLoaderそれが機能するとは思えません。

追加の注意: に貼り付けたエラーには、同じパッケージではないcom.fish47.multidex.Fooとの両方が記載されています。com.fish47.multidex.core.Fooそれが関係しているかどうかはわかりません。また、追加VFYのメッセージがある場合は、それらが少しわかりにくかったとしても、それらを含めることをお勧めします。また、この性質のものについては、使用している Android のバージョンを指定することも重要です。これはしばらく変更されていませんが、十分に遡ると非常に異なっています。

于 2013-08-16T17:48:43.993 に答える