Как программно изменить значок приложения в Android?
Можно ли изменить значок приложения прямо из программы?
Я имею в виду, изменить icon.png
в res\drawable
папка.
Я хотел бы позволить пользователям изменять значок приложения из программы, чтобы в следующий раз они увидели ранее выбранный значок в панели запуска.
11 ответов
Это старый вопрос, но он все еще активен, так как нет явной функции Android. А ребята из фейсбука нашли обходной путь - как угодно. Сегодня я нашел способ, который работает для меня. Не идеально (см. Примечания в конце этого ответа), но это работает!
Основная идея заключается в том, что я обновляю значок ярлыка моего приложения, созданный средством запуска на домашнем экране. Когда я хочу что-то изменить на ярлыке, я сначала удаляю его и воссоздаю с новым растровым изображением.
Вот код Есть кнопка increment
, При нажатии ярлык заменяется на новый, имеющий новый счетный номер.
Сначала вам понадобятся эти два разрешения в вашем манифесте:
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
Затем вам нужно это два метода для установки и удаления ярлыков. shortcutAdd
Метод создает растровое изображение с номером в нем. Это просто для того, чтобы продемонстрировать, что это на самом деле меняется. Вы, вероятно, хотите изменить эту часть с чем-то, что вы хотите в своем приложении.
private void shortcutAdd(String name, int number) {
// Intent to be send, when shortcut is pressed by user ("launched")
Intent shortcutIntent = new Intent(getApplicationContext(), Play.class);
shortcutIntent.setAction(Constants.ACTION_PLAY);
// Create bitmap with number in it -> very default. You probably want to give it a more stylish look
Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Paint paint = new Paint();
paint.setColor(0xFF808080); // gray
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(50);
new Canvas(bitmap).drawText(""+number, 50, 50, paint);
((ImageView) findViewById(R.id.icon)).setImageBitmap(bitmap);
// Decorate the shortcut
Intent addIntent = new Intent();
addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
// Inform launcher to create shortcut
addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
getApplicationContext().sendBroadcast(addIntent);
}
private void shortcutDel(String name) {
// Intent to be send, when shortcut is pressed by user ("launched")
Intent shortcutIntent = new Intent(getApplicationContext(), Play.class);
shortcutIntent.setAction(Constants.ACTION_PLAY);
// Decorate the shortcut
Intent delIntent = new Intent();
delIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
delIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
// Inform launcher to remove shortcut
delIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
getApplicationContext().sendBroadcast(delIntent);
}
И, наконец, вот два слушателя, которые добавляют первый ярлык и обновляют ярлык с помощью увеличивающегося счетчика.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
findViewById(R.id.add).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
shortcutAdd("changeIt!", count);
}
});
findViewById(R.id.increment).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
shortcutDel("changeIt!");
count++;
shortcutAdd("changeIt!", count);
}
});
}
Примечания:
Этот способ работает также, если ваше приложение контролирует больше ярлыков на главном экране, например, с различными дополнительными
Intent
, Им просто нужны разные имена, так что правильное имя удаляется и переустанавливается.Программная обработка ярлыков в Android - это широко известная, широко используемая, но официально не поддерживаемая функция Android. Кажется, он работает на лончере по умолчанию, и я нигде больше не пробовал. Так что не вини меня, когда ты получаешь это электронное письмо от пользователя "Это не работает на моем XYZ, с двойным рутом, супер взорванном телефоне"
Лаунчер пишет
Toast
когда ярлык был установлен и один, когда ярлык был удален. Итак, я получаю дваToast
с каждый раз, когда я меняю значок. Это не идеально, но хорошо, пока остальная часть моего приложения идеально...
Попробуйте это, у меня это отлично работает:
1 Изменить ваш MainActivity
раздел в AndroidManifest.xml
удалите из него строку с MAIN
категория в intent-filter
раздел
<activity android:name="ru.quickmessage.pa.MainActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:label="@string/app_name"
android:theme="@style/CustomTheme"
android:launchMode="singleTask">
<intent-filter>
==> <action android:name="android.intent.action.MAIN" /> <== Delete this line
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
2 Создайте <activity-alias>
, для каждой из ваших иконок. Как это
<activity-alias android:label="@string/app_name"
android:icon="@drawable/icon"
android:name=".MainActivity-Red"
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>
3 Установить программно: установить атрибут ENABLE для соответствующего activity-alias
getPackageManager().setComponentEnabledSetting(
new ComponentName("ru.quickmessage.pa", "ru.quickmessage.pa.MainActivity-Red"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
Обратите внимание, по крайней мере, один должен быть включен всегда.
Вы не можете изменить манифест или ресурс в подписанном и запечатанном APK, кроме как через обновление программного обеспечения.
С программной точки зрения вы можете самостоятельно опубликовать панель запуска приложений:
В вашем AndroidManifest.xml добавьте:
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
Затем вам нужно создать намерение запуска приложения:
Intent myLauncherIntent = new Intent();
myLauncherIntent.setClassName("your.package.name", "YourLauncherActivityName");
myLauncherIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Создайте ярлык установки с помощью панели запуска вашего приложения и пользовательского значка:
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, myLauncherIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Application Name");
intent.putExtra
(
Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext
(
getApplicationContext(),
R.drawable.app_icon
)
);
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
И, наконец, запустить намерение вещания:
getApplicationContext().sendBroadcast(intent);
Примечание: этот метод больше не работает, начиная с Android 8.0 - Oreo
Предполагая, что вы имеете в виду изменение значка, отображаемого на главном экране, это можно легко сделать, создав виджет, который делает именно это. Вот статья, которая демонстрирует, как это можно сделать для приложения типа "новые сообщения", похожего на iPhone:
Решение @PA частично работает для меня. Подробно мои выводы ниже:
1) Первый фрагмент кода неверен, см. Ниже:
<activity
...
<intent-filter>
==> <action android:name="android.intent.action.MAIN" /> <== This line shouldn't be deleted, otherwise will have compile error
<category android:name="android.intent.category.LAUNCHER" /> //DELETE THIS LINE
</intent-filter>
</activity>
2) Следует использовать следующий код, чтобы отключить все значки перед включением другого, в противном случае он добавит новый значок вместо его замены.
getPackageManager().setComponentEnabledSetting(
getComponentName(), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
НО, если вы используете код выше, то ярлык на домашнем экране будет удален! И он не будет автоматически добавлен обратно. Возможно, вы сможете программно добавить иконку назад, но она, вероятно, не будет оставаться в том же положении, что и раньше.
3) Обратите внимание, что значок не будет немедленно изменен, это может занять несколько секунд. Если вы щелкнете по нему сразу после изменения, вы можете получить сообщение об ошибке: "Приложение не установлено".
Итак, ИМХО, это решение подходит только для изменения значка в панели запуска приложений, а не для ярлыков (то есть значок на рабочем столе)
Как упоминалось ранее, вам нужно использовать
<activity-alias>
для изменения значка приложения. Чтобы избежать завершения работы приложения после включения соответствующего псевдонима активности, вам необходимо сделать это после завершения работы приложения. Чтобы узнать, было ли убито приложение, вы можете использовать этот метод
- Создание псевдонимов действий в AndroidManifest.xml
<activity android:name=".ui.MainActivity"/>
<activity-alias
android:name=".one"
android:icon="@mipmap/ic_launcher_one"
android:targetActivity=".ui.MainActivity"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias
android:name=".two"
android:icon="@mipmap/ic_launcher_two"
android:targetActivity=".ui.MainActivity"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
- Создайте сервис, который изменит активный псевдоним активности после закрытия приложения. Вам нужно где-то сохранить имя нового активного псевдонима активности (например, SharedPreferences)
class ChangeAppIconService: Service() {
private val aliases = arrayOf(".one", ".two")
override fun onBind(intent: Intent?): IBinder? = null
override fun onTaskRemoved(rootIntent: Intent?) {
changeAppIcon()
stopSelf()
}
fun changeAppIcon() {
val sp = getSharedPreferences("appSettings", Context.MODE_PRIVATE)
sp.getString("activeActivityAlias", ".one").let { aliasName ->
if (!isAliasEnabled(aliasName)) {
setAliasEnabled(aliasName)
}
}
}
private fun isAliasEnabled(aliasName: String): Boolean {
return packageManager.getComponentEnabledSetting(
ComponentName(
this,
"${BuildConfig.APPLICATION_ID}$aliasName"
)
) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
}
private fun setAliasEnabled(aliasName: String) {
aliases.forEach {
val action = if (it == aliasName)
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
else
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
packageManager.setComponentEnabledSetting(
ComponentName(
this,
"${BuildConfig.APPLICATION_ID}$aliasName"
),
action,
PackageManager.DONT_KILL_APP
)
}
}
}
- Добавить сервис в AndroidManifest.xml
<service
android:name=".ChangeAppIconService"
android:stopWithTask="false"
/>
- Начинать
ChangeAppIconService
вMainActivity.onCreate
class MainActivity: Activity {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
startService(Intent(this, ChangeAppIconService::class.java))
...
}
...
}
Попробуйте это решение
<activity android:name=".SplashActivity"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity-alias android:label="ShortCut"
android:icon="@drawable/ic_short_cut"
android:name=".SplashActivityAlias"
android:enabled="false"
android:targetActivity=".SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
Добавьте следующий код, когда вы хотите изменить значок приложения
PackageManager pm = getPackageManager();
pm.setComponentEnabledSetting(
new ComponentName(YourActivity.this,
"your_package_name.SplashActivity"),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(
new ComponentName(YourActivity.this,
"your_package_name.SplashActivityAlias"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
AndroidManifest.xml
пример:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.pritesh.resourceidentifierexample.MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<!--<category android:name="android.intent.category.LAUNCHER"/>-->
</intent-filter>
</activity>
<activity-alias android:label="RED"
android:icon="@drawable/ic_android_red"
android:name="com.pritesh.resourceidentifierexample.MainActivity-Red"
android:enabled="true"
android:targetActivity="com.pritesh.resourceidentifierexample.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:label="GREEN"
android:icon="@drawable/ic_android_green"
android:name="com.pritesh.resourceidentifierexample.MainActivity-Green"
android:enabled="false"
android:targetActivity="com.pritesh.resourceidentifierexample.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:label="BLUE"
android:icon="@drawable/ic_android_blue"
android:name="com.pritesh.resourceidentifierexample.MainActivity-Blue"
android:enabled="false"
android:targetActivity="com.pritesh.resourceidentifierexample.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
Затем следуйте приведенному ниже коду в MainActivity
:
ImageView imageView = (ImageView)findViewById(R.id.imageView);
int imageResourceId;
String currentDateTimeString = DateFormat.getDateTimeInstance().format(new Date());
int hours = new Time(System.currentTimeMillis()).getHours();
Log.d("DATE", "onCreate: " + hours);
getPackageManager().setComponentEnabledSetting(
getComponentName(), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
if(hours == 13)
{
imageResourceId = this.getResources().getIdentifier("ic_android_red", "drawable", this.getPackageName());
getPackageManager().setComponentEnabledSetting(
new ComponentName("com.pritesh.resourceidentifierexample", "com.pritesh.resourceidentifierexample.MainActivity-Red"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}else if(hours == 14)
{
imageResourceId = this.getResources().getIdentifier("ic_android_green", "drawable", this.getPackageName());
getPackageManager().setComponentEnabledSetting(
new ComponentName("com.pritesh.resourceidentifierexample", "com.pritesh.resourceidentifierexample.MainActivity-Green"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}else
{
imageResourceId = this.getResources().getIdentifier("ic_android_blue", "drawable", this.getPackageName());
getPackageManager().setComponentEnabledSetting(
new ComponentName("com.pritesh.resourceidentifierexample", "com.pritesh.resourceidentifierexample.MainActivity-Blue"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
imageView.setImageResource(imageResourceId);
Применяя упомянутые предложения, я столкнулся с проблемой отключения приложения всякий раз, когда значок по умолчанию меняется на новый значок. Итак, реализовали код с некоторыми настройками. Шаг 1). В файле AndroidManifest.xml создайте действие по умолчанию с android:enabled="true" и другой псевдоним с android:enabled="false". Вы не будете содержать их, но добавите их с помощью android:enabled="true".
<activity
android:name=".activities.SplashActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme">
</activity>
<!-- <activity-alias used to change app icon dynamically> : default icon, set enabled true -->
<activity-alias
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:name=".SplashActivityAlias1" <!--put any random name started with dot-->
android:enabled="true"
android:targetActivity=".activities.SplashActivity"> <!--target activity class path will be same for all alias-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<!-- <activity-alias used to change app icon dynamically> : sale icon, set enabled false initially -->
<activity-alias
android:label="@string/app_name"
android:icon="@drawable/ic_store_marker"
android:roundIcon="@drawable/ic_store_marker"
android:name=".SplashActivityAlias" <!--put any random name started with dot-->
android:enabled="false"
android:targetActivity=".activities.SplashActivity"> <!--target activity class path will be same for all alias-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
Шаг 2). Создайте метод, который будет использоваться для отключения 1-го псевдонима действия, содержащего значок по умолчанию, и включения 2-го псевдонима, который содержит значок, который необходимо изменить.
/**
* method to change the app icon dynamically
*
* @param context
* @param isNewIcon : true if new icon need to be set; false to set default
* icon
*/
public static void changeAppIconDynamically(Context context, boolean isNewIcon) {
PackageManager pm = context.getApplicationContext().getPackageManager();
if (isNewIcon) {
pm.setComponentEnabledSetting(
new ComponentName(context,
"com.example.dummy.SplashActivityAlias1"), //com.example.dummy will be your package
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(
new ComponentName(context,
"com.example.dummy.SplashActivityAlias"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
} else {
pm.setComponentEnabledSetting(
new ComponentName(context,
"com.example.dummy.SplashActivityAlias1"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(
new ComponentName(context,
"com.example.dummy.SplashActivityAlias"),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
}
Шаг 3). Теперь вызовите этот метод в зависимости от ваших требований, скажем, при нажатии кнопки, конкретной дате или конкретных условиях, например:
// Switch app icon to new icon
GeneralUtils.changeAppIconDynamically(EditProfileActivity.this, true);
// Switch app icon to default icon
GeneralUtils.changeAppIconDynamically(EditProfileActivity.this, false);
Надеюсь, это поможет тем, кто сталкивается с проблемой гибели приложения при смене значка. Удачного кодирования:)
Чтобы заставить работать решение Маркуса, мне понадобилось первое намерение, поэтому:
Intent myLauncherIntent = new Intent(Intent.ACTION_MAIN);
myLauncherIntent.setClassName(this, this.getClass().getName());
myLauncherIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);