Androidで他のAPKのコードにアクセスする

## 概要
<p>Androidで複数のAPKでコードを共有する方法に、ライブラリプロジェクトを作成し、AARファイルをアプリに組み込むという方法があるが、AARファイルは複数のAPKの内部に取り込まれるため、容量は圧迫される。</p>
<!-- /wp:paragraph -->


この問題を回避する方法として、他のAPKのクラスファイルを読み込んで、実行する方法を紹介する。（要リフレクション）
Contextにコードを含めるためにContext.CONTEXT_INCLUDE_CODEを使用するが、このフラグはAPI level 1から使用でき、API level 29で動くことを確認した。

##実装

```kotlin:MainActivity.kt
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)
        }
    }
}
```
```kotlin:Util.kt
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)
    }
}
```


<!-- wp:paragraph -->
<p>MainActivity.ktとUtil.ktは別のapkに含まれるクラスである。Util.ktは一般的なクラスの作成方法で特に問題はない。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>MainActivityでは、Util.ktが含まれるAPKのコンテキストを取得し、そのContextからClassLoaderを作成する必要がある。そのコードが以下の部分だ。</p>
<!-- /wp:paragraph -->

<pre> 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)
</pre>

<!-- wp:paragraph -->
<p>Contextを作成する際のフラグには Context.CONTEXT_IGNORE_SECURITYとContext.CONTEXT_INCLUDE_CODE の両方を指定する。そのContextからClassLoaderとClassを作成する。後は一般的なリフレクションの方法で、コードを呼び出すことができる。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>以下の画像はビュー追加ボタンを押した結果である。Util.ktのaddTextViewが呼び出され、ボタンの最後尾にTextViewが追加される。</p>
<!-- /wp:paragraph -->

![Screenshot_1562491445-576x1024.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/515138/e336da33-9c10-db7e-9439-d0de2120c2cc.png)

##注意点

<!-- wp:paragraph -->
<p>Contextを混同しないようにする必要がある。リソースアクセス系のメソッドでContextを引数として受け取る場合、渡す必要があるのはリソースが含まれるapkのContextである。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>またこの方法は他のAPKにあるクラスを自分のアプリのプロセスにロードしているだけで、別のプロセス上で動いているわけではない。ServiceやContentProviderなどを使用する場合とはその点で異なる。</p>
<!-- /wp:paragraph -->


##Github

<p><a href="https://github.com/ftsukimaru/SampleLibrary">https://github.com/ftsukimaru/SampleLibrary</a></p>
<p><a href="https://github.com/ftsukimaru/SampleAcessOtherApk">https://github.com/ftsukimaru/SampleAcessOtherApk</a></p>