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/を使用して実行時にクラスファイルを変更し、必要に応じて値を置き換えてみてはどうでしょうか。これは驚くべきことですが、おそらくかなり難しいでしょう。