Как программно изменить значок приложения в Android?

154

Можно ли изменить значок приложения прямо из программы?
Я имею в виду, изменить icon.pngв res\drawableпапке.
Я хотел бы позволить пользователям изменять значок приложения из программы, чтобы в следующий раз они увидели ранее выбранный значок в панели запуска.

systempuntoout
источник

Ответы:

81

Это старый вопрос, но он все еще активен, поскольку в нем нет явной функции 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с каждый раз, когда я меняю значок. Это не идеально, но хорошо, пока остальная часть моего приложения идеально ...

jboi
источник
9
Сегодняшнее приложение «Календарь» может ежедневно отображать иконки без тостов.
Джим Маккит
1
@ Джим (я думаю) тогда это на самом деле виджет
JacksOnF1re
3
ShortcutDel, к сожалению, больше не работает в зефире, см. также stackoverflow.com/a/33731620/1545993
Taifun
2
это заменяет значок ярлыка, а не значок запуска
user1506104
3
В следующих строках что такое Play.class и Constants.ACTION_PLAY Intent shortcutIntent = new Intent (getApplicationContext (), Play.class); shortcutIntent.setAction (Constants.ACTION_PLAY);
Дашарат Сингх Баджролия
136

Попробуйте это, у меня это нормально работает:

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

Обратите внимание, по крайней мере, один должен быть включен всегда.

Пенсильвания
источник
3
к сожалению, по-разному работает на устройствах, которые я пробовал. Работает на HTC Desire 2.2, но ненадежен на Galaxy Nexus 4.2.2 и Nexus 7 4.3. На Galaxy Nexus может привести к исчезновению всех значков приложения, а также удалению всех виджетов. Жаль, поэтому мне пришлось удалить эту функцию на более поздних устройствах.
Ричард Ле Мезурье
7
я не могу запустить свое приложение, так как я удалил это: <action android: name = "android.intent.action.MAIN" /> <категория android: name = "android.intent.category.LAUNCHER" />
noobProgrammer
1
Это прекрасно работает для меня при переключении между значками запуска на панели приложений и домашнем экране, но я обнаружил, что значок, используемый на уровне ОС (например, для многозадачности, всплывающего окна удаления или в списке диспетчера приложений), остается. исходный или становится стандартным значком Android по умолчанию, если вы не установили его на уровне приложения в манифесте. Вы нашли решение этой проблемы или просто терпите несоответствие (или отсутствие) значка на уровне ОС?
шутка
2
Ошибка запуска приложения. Активность по умолчанию не найдена.
CopsOnRoad
1
Ошибка запуска приложения. Активность по умолчанию не найдена
Камиль Ибадов
36

Вы не можете изменить манифест или ресурс в подписанном и запечатанном APK, кроме как через обновление программного обеспечения.

CommonsWare
источник
2
@Nanne: это виджет приложения или функция домашнего экрана, а не значок приложения. Вы по-прежнему не можете изменить манифест или ресурс, кроме как с помощью обновления программного обеспечения.
CommonsWare
1
? Нет, я имею в виду наоборот: это не (рекламируется как) виджет. Я добавляю это как ярлык приложения. Но, как вы говорите, просто потому, что этот материал, не являющийся товаром, подразумевает просто иконку, это не значит, что это так :)
Nanne
2
@NeTeInStEiN: он не будет работать для всех домашних экранов (например, тех, которые не обращают внимания на изменения с поддержкой компонентов).
CommonsWare
1
Больше не правда. Календарь Google на Android 6+ ежедневно меняется в панели запуска. Сегодня значок - «2», вчера - «1». Обычно на иконке была надпись «31». Но не больше, это меняется. Кто-нибудь знает, как это возможно?
UeliDeSchwert
1
@Bobby: Я имею в виду, что в Play Store есть сотни, если не тысячи реализаций домашнего экрана, в дополнение к сотням различных предустановленных домашних экранов на тысячах существующих моделей устройств Android. Те реализации домашнего экрана могут иметь хуки, которые позволяют заменять иконки динамического запуска. Однако не все домашние экраны могут предложить это. То, что вы наблюдаете такое поведение для одного приложения на одном домашнем экране на одном устройстве, не означает, что оно доступно для всех приложений на всех домашних экранах на всех устройствах.
CommonsWare
17

С программной точки зрения вы можете самостоятельно опубликовать панель запуска приложений:

Примечание: этот метод больше не работает, начиная с Android 8.0 - Oreo

В вашем 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);
Андрей Маркуц
источник
это заменяет значок ярлыка, а не значок запуска
user1506104
10

Предполагая, что вы имеете в виду изменение значка, отображаемого на главном экране, это можно легко сделать, создав виджет, который делает именно это. Вот статья, которая демонстрирует, как это можно сделать для приложения типа «новые сообщения», похожего на iPhone:

http://www.cnet.com/8301-19736_1-10278814-251.html

Мариус Кьельдаль
источник
9

Решение @ 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) Обратите внимание, что значок не будет немедленно изменен, это может занять несколько секунд. Если вы щелкнете по нему сразу после изменения, вы можете получить сообщение об ошибке: «Приложение не установлено».

Итак, ИМХО, это решение подходит только для изменения значка в панели запуска приложений, а не для ярлыков (то есть значок на рабочем столе)

Дэцин
источник
2
Ошибка запуска приложения. Активность по умолчанию не найдена.
CopsOnRoad
вы удаляете лаунчер, как он найдет активность по умолчанию @Deqing?
j2emanue
5

Попробуйте это решение

<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);
Факсриддин Абдуллаев
источник
4

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);
Притеш Патель
источник
Я получаю com.pritesh.resourceidentifierexample.MainActivity-Red doesn't exist in com.pritesh.resourceidentifierexampleисключение. здесь я использовал ваше манифестное имя, чтобы продемонстрировать свою проблему
Tejas
0

Чтобы заставить решение работать Маркусом, мне понадобилось первое намерение, поэтому:

Intent myLauncherIntent = new Intent(Intent.ACTION_MAIN);
            myLauncherIntent.setClassName(this,  this.getClass().getName());
            myLauncherIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PuZZleDucK
источник
0

Применяя упомянутые выше предложения, я столкнулся с проблемой того, что приложение будет убито, когда значок по умолчанию сменится на новый значок. Так что реализовали код с некоторыми настройками. Шаг 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). Создайте метод, который будет использоваться для отключения первого псевдонима активности, который содержит значок по умолчанию, и включения второго псевдонима, который содержит значок, который необходимо изменить.

/**
 * 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);

Надеюсь, что это поможет тем, кто сталкивается с проблемой гибели приложения при смене значка. Удачного кодирования :)

Abhijeet
источник