Android には activity-alias というアクティビティに別名をつける仕組みがあります。この機能をうまく使うことでアプリのアイコンやタイトルを動的に変更してみたので、そのメモ書きです。これは iOS 10.3 から追加された User-Selectable App Icons に近いことを実現できそうです。
他にも以下の機能でランチャーにアプリのショートカットを作成するということでアプリアイコンとタイトルを変更したように見せる方法もありますが、この記事では触れません。
- Android 7.1 以前:
com.android.launcher.action.INSTALL_SHORTCUT
インテント - Android 8 以降: android.content.pm.ShortcutManager の requestPinShortcut メソッド
以降のコードや実行結果は下記の環境のものとなります。
- Android Studio : 3.3.2
- Kotlin : 1.3.21
- Target SDK : 28
アプリアイコンとタイトルを変更する手順
マニフェストファイルにメインアクティビティのエイリアスを作成する
AndroidManifest.xml に次のように記述を変更します。
- メインアクティビティから
<intent-filter>
削除 - デフォルトで利用するアプリアイコンとタイトルを指定した
<activity-alias>
を追加-
android:icon
とandroid:label
がそれぞれアプリアイコンとタイトル -
android:name
はエイリアス名なので、実在しないクラスでOK -
android:targetActivity
が実体となるアクティビティ -
android:enabled
はデフォルト使用するものなのでtrue
としておく -
<intent-filter>
はこのエイリアスの方で指定する
-
- デフォルトの代替として利用するアプリアイコンとタイトルを指定した
<activity-alias>
を追加-
android:icon
とandroid:label
に代替となるアプリアイコンとタイトルを指定する -
android:enabled
はfalse
としておく
-
このように、指定するアイコンやタイトルは静的なものですので、ダウンロードしたアイコンを指定するというようなことはおそらくできないと思われます。(この点も、iOS の User-Selectable App Icon と似ていますね)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.appiconchanger">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- メインアクティビティ -->
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<!-- intent-filter は記述しません -->
</activity>
<!-- メインアクティビティのエイリアス1 (デフォルトアイコン/タイトル) -->
<activity-alias android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:name=".MainActivity_default"
android:enabled="true"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<!-- メインアクティビティのエイリアス2 (代替アイコン/タイトル) -->
<activity-alias android:label="@string/app_name_beta"
android:icon="@mipmap/ic_launcher_beta"
android:name=".MainActivity_beta"
android:enabled="false"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
</manifest>
記述に不整合があると Android Studio でのビルド時に Manifest の Merge エラーが出ます。その際は、AndroidManifest.xml の Merged Manifest タブを表示することでエラー内容を確認することができますので、それを頼りにエラーを解消しましょう。(下図はメインアクティビティ本体の方に <intent-filter>
が記述されていた時のエラー)
有効なエイリアスを切り替えるコードを書く
下記のコードは、MainActivity に配置した FAB を押すたびにデフォルトエイリアスと代替エイリアスを切り替える例です。
- エイリアスは
ComponentName
のインスタンスにより指定します - android.content.pm.PackageManager の getComponentEnabledSetting メソッドでエイリアスの有効/無効状態を判断できます
- android.content.pm.PackageManager の setComponentEnabledSetting メソッドでエイリアスの有効/無効状態を変更できます
- 下記のコードでは、デフォルトエイリアスが有効な場合は、代替エイリアスを有効にし、デフォルトエイリアスを無効にする(逆も同様)というようにしています
package com.example.appiconchanger
import android.content.ComponentName
import android.content.pm.PackageManager
import android.os.Bundle
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity;
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
// ボタンを押すたびにエイリアスを切り替える
fab.setOnClickListener { view ->
// デフォルト ComponentName
val defaultComponentName = ComponentName(packageName, packageName + ".MainActivity_default")
// 代替 ComponentName
val substituteComponentName = ComponentName(packageName, packageName + ".MainActivity_substitute")
// エイリアスを切り替える
val state = packageManager.getComponentEnabledSetting(substituteComponentName)
if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
packageManager.setComponentEnabledSetting(
substituteComponentName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP)
packageManager.setComponentEnabledSetting(defaultComponentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP)
Snackbar.make(view, "Switch to substitute alias.", Snackbar.LENGTH_LONG).show()
} else {
packageManager.setComponentEnabledSetting(
defaultComponentName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP)
packageManager.setComponentEnabledSetting(
substituteComponentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP)
Snackbar.make(view, "Switch to default alias.", Snackbar.LENGTH_LONG).show()
}
}
}
}
検証
実行結果
下図のようにアプリのアイコンとタイトルを変更することに成功しました。左画像がデフォルトエイリアス、右画像が代替エイリアスです。
いくつかのデバイスで試してみた
ランチャーにアプリのショートカットアイコンを作成する方法の場合、デバイスや Android のバージョンによって動作しないケースがあったため、今回のエイリアスによる方法を手元にあるデバイスで試してみた結果が下記の表です。
デバイス | Android ver. | 結果 | 備考 |
---|---|---|---|
Google Nexus 5X | 8.1 | ◯ | |
Galaxy S6 edge (SCV31) | 7.0 | ◯ | |
Google Nexus 5 | 6.0.1 | △ | 変更後にランチャーがクラッシュ?して再起動された。変更はされている。 |
Google Nexus 5 | 5.1.1 | ◯ |
Google Nexus 5 (6.0.1) のみランチャーがクラッシュしてしまう問題がありましたので、他のデバイスでも何らかの問題が起きる可能性がありそうです。