概要
Androidで複数のAPKでコードを共有する方法に、ライブラリプロジェクトを作成し、AARファイルをアプリに組み込むという方法があるが、AARファイルは複数のAPKの内部に取り込まれるため、容量は圧迫される。
この問題を回避する方法として、他のAPKのクラスファイルを読み込んで、実行する方法を紹介する。(要リフレクション)
Contextにコードを含めるためにContext.CONTEXT_INCLUDE_CODEを使用するが、このフラグはAPI level 1から使用でき、API level 29で動くことを確認した。
##実装
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val libraryContext = applicationContext.createPackageContext(
"com.aishihai.library",
Context.CONTEXT_IGNORE_SECURITY or Context.CONTEXT_INCLUDE_CODE
)
val loader = libraryContext.classLoader
val clazz = Class.forName("com.aishihai.library.Util", true, loader)
val instance = clazz.newInstance()
val getStringMethod = clazz.getDeclaredMethod("getText", Context::class.java)
val addTextViewMethod =
clazz.getDeclaredMethod("addTextView", Context::class.java, Activity::class.java, Int::class.java)
val startActivityMethod = clazz.getDeclaredMethod("startActivity", Context::class.java, Context::class.java)
get_string.setOnClickListener {
Toast.makeText(
applicationContext,
getStringMethod.invoke(instance, libraryContext) as String,
Toast.LENGTH_LONG
).show()
}
add_view.setOnClickListener {
addTextViewMethod.invoke(instance, libraryContext, this, R.id.root)
}
start_browser.setOnClickListener {
startActivityMethod.invoke(instance, libraryContext, applicationContext)
}
}
}
class Util {
fun getText(context:Context):String {
return context.resources.getString(R.string.library_string)
}
fun addTextView(context: Context, activity: Activity, id: Int) {
val view = activity.findViewById<ViewGroup>(id)
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.just_text_view, view)
}
fun startActivity(libraryContext:Context, accessApkContext:Context) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(libraryContext.getString(R.string.google_url)))
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
accessApkContext.startActivity(intent)
}
}
MainActivity.ktとUtil.ktは別のapkに含まれるクラスである。Util.ktは一般的なクラスの作成方法で特に問題はない。
MainActivityでは、Util.ktが含まれるAPKのコンテキストを取得し、そのContextからClassLoaderを作成する必要がある。そのコードが以下の部分だ。
val libraryContext = applicationContext.createPackageContext(
"com.aishihai.library",
Context.CONTEXT_IGNORE_SECURITY or Context.CONTEXT_INCLUDE_CODE
)
val loader = libraryContext.classLoader
val clazz = Class.forName("com.aishihai.library.Util", true, loader)
Contextを作成する際のフラグには Context.CONTEXT_IGNORE_SECURITYとContext.CONTEXT_INCLUDE_CODE の両方を指定する。そのContextからClassLoaderとClassを作成する。後は一般的なリフレクションの方法で、コードを呼び出すことができる。
以下の画像はビュー追加ボタンを押した結果である。Util.ktのaddTextViewが呼び出され、ボタンの最後尾にTextViewが追加される。
##注意点
Contextを混同しないようにする必要がある。リソースアクセス系のメソッドでContextを引数として受け取る場合、渡す必要があるのはリソースが含まれるapkのContextである。
またこの方法は他のAPKにあるクラスを自分のアプリのプロセスにロードしているだけで、別のプロセス上で動いているわけではない。ServiceやContentProviderなどを使用する場合とはその点で異なる。
##Github