4

Robolectric(Androidユニットテストフレームワーク)でライブラリプロジェクトのサポートを実装しようとしています。ライブラリプロジェクトのすべてのリソースをロードするフレームワークがあり、これが正常に機能することをテストしました。プロセスは非常に単純です。project.propertiesで読み取ったRobolectricConfigから、ループ内でandroid.library.reference.xの値を探し、各プロジェクトを繰り返します。

トリッキーな部分は、ライブラリプロジェクトコードベースから実行時にR参照を解決することと関係があります。たとえば、次のような1つのライブラリプロジェクトを持つアプリケーションがあるとします。

ライブラリcom.example.libに依存するcom.example.app

どちらのプロジェクトにもリソースがあります。com.example.appプロジェクトの下には、次のものがあります。

gen / com.example.app.R

gen / com.example.lib.R

com.example.app.Rは、com.example.lib.Rのすべての定義と、独自のリソースのために追加された定義が含まれているため、com.example.lib.Rのスーパーセットです。もともとは同じだと思っていたのですが、間違っていました。実際は違います。ただし、Rの内部クラス((R.string、R.color、R.attrなど)の場合、名前/値は、アプリプロジェクトのcom.example.app.Rの対応する値と同じようにマップされます。ライブラリプロジェクトのRクラスただし、値はアプリケーションプロジェクトの値と同じようにはマップされません。

簡単に言うと、次のようになります。

package com.example.app;

public final class R {
      public static final class string {
            public static final int a = 0x7f050001;
            public static final int a = 0x7f050002;
            public static final int c = 0x7f050003;

     }

}

//this file is in the application project
package com.example.lib;

public final class R {
     public static final class string {
            public static final int c = 0x7f050003;

     }
}

次に、ライブラリプロジェクトの下にgen / com.example.lib.Rがあります:

//this file is in the library project
package com.example.lib;

public final class R {
     public static final class string {
            public static final int c = 0x7f040003;
     }
}

つまり、ライブラリにはstring.xmlでcが定義されていますが、プロジェクトには定義されていません。ライブラリの値のR.string.cの値は、アプリケーションのR値と同じではありません(アプリのc = 0x7f050003、ライブラリのc = 0x7f040003)。ライブラリはアプリケーションについて認識していないため、ライブラリ自体のRクラスが同じ値を生成できない可能性があることを理解しています。したがって、アプリケーションクラスの値を再マップする必要があります。私が知りたいのは、実行時に、ライブラリのcom.example.lib.Rクラスで定義されている値ではなく、アプリケーションプロジェクトで定義されているcom.example.lib.R値をどのように使用できるかということです。

失敗するのは、Robolectric Testランナーがテストを実行しているときに、ライブラリプロジェクトのコードベースに入るときに、次のように文字列cを検索することです。

 resources.getString(R.string.c);

したがって、ルックアップを実行すると、0x7f050003ではなく0x7f040003が得られます。ほとんどの値は正確に0x10000高くマップされているようですが、それが常にうまくいくとは限らないので、私はそれに頼ることができません。

私が理解していないのは、同じパッケージ名の2つのクラスがあり、アプリケーションの1つがクラスパスの最初に来る場合です(実行時にSystem.getProperty( "java.class.path"を出力して確認しました)。 )実行時)、なぜライブラリプロジェクトは、アプリケーションプロジェクトの同等のバージョンではなく、独自のcom.example.lib.R定義を使用するのですか?どちらもまったく同じ正規名です。

クラスローダーの何かが、このクラス(MyLibraryActivityと呼びましょう)が依存しているcom.example.lib.Rクラスについてのみ知っており、そのクラスをロードすると言っていると想像します。おそらく、セキュリティマネージャか何かがその決定を下しますか?よくわかりません。しかし、どういうわけか、この動作を変更して、ライブラリプロジェクト内からのライブラリプロジェクトリソースルックアップをアプリケーションプロジェクトのバージョンに解決できるようにしたいと思います(結局のところ、クラスパスにあるため)。たぶん、システムがそれをリロードしようとしないように、このクラスを事前に強制的にロードする方法がありますか?

ライブラリプロジェクトとアプリケーションプロジェクトの間に依存関係がないことを理解しており、その依存関係を絶対に追加したくありませんが、実行時の値は、ライブラリRクラスではなくアプリケーションのRクラスから取得する必要があります。 。

なぜこれが発生するのか、また、アプリケーションRクラスをアプリケーションプロジェクトが参照するクラスにする方法があるかどうか、誰かが知っていますか?

- アップデート -

少し考えてみると、問題は、Javaコンパイラがこれらの値をインライン化することです。これらの値は最終的な静的変数であり、クラスの読み込みの変更に関する問題を修正する方法がないためです。

ある時点で私が持っていたもう1つのアイデアは、ライブラリプロジェクトのすべてのRクラスをロードし、変数をRクラスのアプリケーションプロジェクト変数にマップすることでした。getValueが呼​​び出されるたびに、(実行時に)スタックを分析して、呼び出し元が誰であり、どのRクラスがその呼び出し元に関連付けられているかを判断します。そこから、関連付けられたIDがアプリケーションプロジェクトからのものであるかどうかを判断でき、ルックアップは期待どおりに機能します。

これが可能かどうか誰かが考えていますか?私は実行時にスタックトレースを取得する方法を知っており、呼び出し元が誰であるかを把握するためのロジックを組み込むことができますが、どのRクラスがその呼び出し元に関連付けられているかを把握することは複雑で遅く、おそらくエラーが発生しやすいようです。

-再度更新-http ://www.csg.is.titech.ac.jp/~chiba/javassist/を使用して実行時にクラスファイルを変更し、必要に応じて値を置き換えてみてはどうでしょうか。これは驚くべきことですが、おそらくかなり難しいでしょう。

4

2 に答える 2

2

Androidライブラリプロジェクトはそれ自体では実行されません。依存するアプリケーションでライブラリを参照し、そのアプリケーションをビルドすることにより、常に間接的にコンパイルされます。

ライブラリプロジェクトのgenフォルダに生成されたR.javaの唯一の目的は、ライブラリプロジェクト自体を実行することです。これresources.getString(R.string.c);により、ライブラリプロジェクトのようにコードが表示されたときに、IDEがコンパイルエラーをポップしません。ライブラリプロジェクトのR.javaは一時ファイルに似ており、アプリケーションプロジェクトのapkファイルにプッシュされることはありません。

ライブラリプロジェクトは、純粋なJavaソース(my-lib / src /)とAndroidリソース(my-lib / res /)の2つの部分と考えてください。アプリケーションプロジェクトでライブラリプロジェクトを参照すると、純粋なJavaソースがコンパイルされ、最終的にmy-lib / bin / my-lib.jar(このいわゆる一時jarにはR.classファイルがないことに注意してください)になります。 、アプリケーションプロジェクトの依存関係としてmy-lib.jarを追加します(Eclipse Package Explorerでチェックアウトしmy-app -> Android Dependencies -> my-lib.jarます)。Androidリソースは、後でADTがそうする時期であると判断したとき、通常はアプリケーションプロジェクトが実行/デバッグ用にビルドされたときに、グローバルにマージおよびコンパイルされます。

コンパイルされて最終的なapkにパックされるRjavaファイルは、アプリケーションプロジェクトのgenフォルダーの下にあり、apkファイルでは、次のように対応するパッケージの下に挿入されます。

com/
  example/
    lib/
      R       <- com.example.lib.R.java
      ... ... <- package/class from my-lib.jar
    app/
      R       <- com.example.app.R.java
      ... ... <- package/class from application project

ライブラリとアプリケーションプロジェクト間で生成される一貫性のないR値は問題ではありません。これは、SDKの設計によって保証されています。Robolectricプルリクエストの場合は、常にアプリケーションプロジェクトのRファイルを適宜使用してください。

私が言及した情報のほとんどは、公式の開発ガイドのライブラリプロジェクトと開発に関する考慮事項のセクションにあります。

于 2012-12-10T22:52:30.860 に答える
2

ライブラリプロジェクトのリソースは常にアプリケーションプロジェクトに含まれているため、ライブラリプロジェクトのリソースにはいつでも固定値を割り当てることができます。

それはあなたの問題を解決することができます。public.xmlを宣言して固定値を割り当て、同じ値がアプリケーションプロジェクトにもインポートされるようにすることができます。

ライブラリ内のリソースの数によっては、これは面倒な場合があります。

res/valuesにpublic.xmlを作成する必要があります

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <public type="string" name="c" id="0x7f050003" />
</resources>

詳細については、この投稿を確認してください

于 2012-12-17T16:25:07.607 に答える