アンドロイド - コードでアプリ(apk)のインストール、削除する方法

By JS | Last updated: August 02, 2019

自分のアプリからのapkファイルをインストールしたいときがあります。アンドロイドのPackageInstallerServiceはデバイスにapkをインストールできるAPIを提供します。 アプリは、このAPIを使用すると、apkをインストールすることができます。しかし、このAPIを使用するには、android.permission.INSTALL_PACKAGESというパーミッションが必要です。 このパーミッションは、システム(privileged)アプリまたはプラットフォームkeyで署名されたエプマン受けることができますので、一般的なアプリは、この方法を使用することができません。

それでは、どのようアプリをインストールする必要がでしょうか?システムには、PackageInstallerというアプリがあります。このアプリは、上記のパーミッションを持っており、PackageInstallerServiceのAPIを使用してapkをインストールすることができます。私たちは、このアプリにどのapkファイルを代わりにインストールくれ求めることができます。いくつかの条件が、満足されると、PackageInstallerアプリは、私たちのapkを代わりに設置します。

アプリがPackageInstallerアプリのインストールを要求する方法について説明します。

この記事で使われたコードは、すべての鼻間違って作成しました。

PackageInstallerアプリは何ですか?

このアプリのパッケージ名は com.google.android.packageinstallerです。このアプリは、Googleが提供するappです。すべてのデバイスには、このアプリがインストールされています。 私たちは、このアプリにインテントを送信インストールを要求することができます。

Apk準備する

アプリのインストールを要求する前に、apkファイルが必要です。皆さんのアプリは、サーバから取得したapk、または事前に準備されたapkをインストールするシナリオを持っていることがあります。 私はAssetsにapkを保存しておき、これをアプリのfilesフォルダにコピーした後PackageInstallerにインストールを要求するように実装しました。

次のコードは、 /assets/app.apkファイルをアプリのfilesフォルダである「/data/data/comd.codechacha.sample/files/app.apk」にコピーするコードです。

val inputStream = assets.open("app.apk")
val outPath= filesDir.absolutePath + "/app.apk"
val outputStream = FileOutputStream(outPath)
while (true) {
    val data = inputStream.read()
    if (data == -1) {
        break
    }
    outputStream.write(data)
}
inputStream.close()
outputStream.close()

adb shellで目的の場所にファイルがコピーされたことを確認しました。

generic_x86:/data/data/com.codechacha.sample/files # ls
app.apk

Apkインストールを求める

今までのアプリのデータフォルダーにapkを受けた状態です。 これで、このファイルをPackageInstallerにインストールを要請します。

まず、アプリのAndroidManifest.xmlに以下のようにパーミッションを登録する必要があります。 アプリがインストールされるように、この権限を受けません。この権限は、ユーザーが設定アプリから直接付与する必要が得ることができます。 権限を取得する方法は、以下に説明します。

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

次のコードは、アプリのapkをPackageInstallerにインストールを要請するコードです。

val apkPath= filesDir.absolutePath + "/app.apk"
val apkUri =
    FileProvider.getUriForFile(applicationContext,
        BuildConfig.APPLICATION_ID + ".fileprovider", File(apkPath))  // 1

val intent = Intent(Intent.ACTION_VIEW)   // 2
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK  
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)  // 3
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")  // 4
startActivity(intent)  // 6

"// 1"のように番号で表記したものを以下に詳細に説明しました。

  1. UriはApkファイルの位置情報を持っています。ここFileProviderを使用したがね。

Androidは、異なるアプリのデータフォルダーに直接アクセスすることを禁止するからです。 FileProviderを使用すると、アプリ同士でデータを共有することができます。 2. PackageInstallerを実行するためには、暗黙的インテントのactionはACTION_VIEWに設定します。 3.インテントを受信したPackageInstallerが自分のアプリのデータにアクセスするための権限を付与します。 4. Dataは上記で作成しUriを入れて、インテントのタイプは、"application/vnd.android.package-archive"に設定します。 タイプはPackageInstallerを暗黙的インテントに見つける必要があります。

上記のコードでapkファイルをPackageInstallerに提供する際にFileProviderを利用しました。

FileProviderで、他のアプリとファイルを共有するには、AndroidManifest.xmlに以下のようにproviderが定義されます。

<provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
    <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
</provider>

@xml/file_pathsres/xml/file_paths.xmlファイルを指し、次のように定義すると、されます。 このように設定すると、私のアプリのデータフォルダーの ../files/app.apkファイルを他のアプリと共有することができます。

<paths>
    <files-path path="/" name="app.apk" />
</paths>

上記のコードを実行すると、次の画面が出ます。この画面は、セッティングに行ってアプリにインストールを要求することができる権限を与えなければならないということです。 セキュリティ上の理由から、アプリがapkのインストールを要求するユーザーに許可を取らなければします。一度だけ設定すると、その次からは出ません。 package installer

Settingsボタンを押すと、次の画面が出ます。 Allow from this sourceを押して有効にすると、このアプリは、apkをインストール要求する権限を与えられます。 package installer

再インストール画面に戻ると、次のような画面が表示されます。今 Installボタンを押すと、インストールがされます。 package installer

次の画面が表示されたら、インストールがすべて完了です。 package installer

パーミッションの設定画面オフセット

アプリのインストールを要求したとき、上から見たように、権限がない場合の設定画面に移動するように案内メッセージが表示されます。 しかし、アプリでプレビュー表示し権限を受けたい場合があります。

次のコードは、権限を持っているかどうかチェックしていない場合はセッティング画面を浮かべるコードです。

if (packageManager.canRequestPackageInstalls()) {  // 1
    // request to install apk
} else {
    val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,  // 2
            Uri.parse("package:$packageName"))
    startActivityForResult(intent, REQUEST_INSTALL_PERMISSION)  // 3
}

1.権限がある場合はcanRequestPackageInstalls()はtrueを返します。 2.権限を付与するセッティング画面のインテントです。 3.実行すると、設定画面が表示されます。

セッティング画面が終了したら、onActivityResult()が呼び出され、アプリをインストールしたり、他の処理を行うことができます。

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_INSTALL_PERMISSION) {
      //....
    }
}

アプリの削除依頼する

PackageManagerにどのアプリを削除を要請すると、ポップアップが開いたまま、ユーザーが許可をするアプリは削除されます。

アプリが削除を要求する前に、次のようなパーミッションを持っている必要があります。 インストールの権限とは異なり、削除の権限は、AndroidManifest.xmlに登録するだけで、アプリがインストールされたときの権限を受け取ります。

<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />

そして、次のようなコードでどのパッケージに削除を要求します。

val packageURI = Uri.parse("package:com.komorebi.memo")  // 1
val uninstallIntent = Intent(Intent.ACTION_DELETE, packageURI)  // 2
startActivity(uninstallIntent)  // 3
  1. "package:[削除したいパッケージ名]"の形式でUriを作成します。
  2. インテントのActionは「ACTION_DELETE」を使用します。
  3. インテントを実行すると、削除済みの画面が表示されます。

上記のコードが実行されると、次のような画面が開いて、ユーザーが[OK]ボタンを押すと、アプリが削除されます。 uninstall package

まとめ

アプリでapkをインストールしたり、削除する方法について説明しました。 プラットフォームAPIを直接使用することができないため、PackageInstallerというアプリを介して削除とインストールを行うことができます。 PackageInstallerを使用してインストールする理由は、ユーザーこっそり悪意のあるアプリがインストールされたり、アプリが削除されることを防止するためです。 したがって、いくつかのアプリをインストールしたい場合は、ユーザーにこのアプリがインストールされた理由を十分に納得できるようにUXを設定する必要があります。

この記事で作成したサンプルアプリは、GitHubで確認することができます。

参考

Related Posts

codechachaCopyright ©2019 codechacha