LoginSignup
0
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-10-16

概要

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

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

実装

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)
        }
    }
}
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)
    }
}

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が追加される。

Screenshot_1562491445-576x1024.png

注意点

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

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

Github

https://github.com/ftsukimaru/SampleLibrary

https://github.com/ftsukimaru/SampleAcessOtherApk

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3