Comment changer une icône d'application par programme dans Android?

154

Est-il possible de changer une icône d'application directement depuis le programme?
Je veux dire, changez icon.pngdans le res\drawabledossier.
Je voudrais permettre aux utilisateurs de changer l'icône de l'application à partir du programme afin que la prochaine fois, ils voient l'icône précédemment sélectionnée dans le lanceur.

systempuntoout
la source

Réponses:

81

C'est une vieille question, mais toujours active car il n'y a pas de fonctionnalité Android explicite. Et les gars de Facebook ont ​​trouvé un travail - en quelque sorte. Aujourd'hui, j'ai trouvé un moyen qui fonctionne pour moi. Pas parfait (voir remarques à la fin de cette réponse) mais ça marche!

L'idée principale est de mettre à jour l'icône du raccourci de mon application, créée par le lanceur sur mon écran d'accueil. Lorsque je veux changer quelque chose sur l'icône de raccourci, je le supprime d'abord et le recrée avec un nouveau bitmap.

Voici le code. Il a un bouton increment. Lorsqu'il est enfoncé, le raccourci est remplacé par un raccourci doté d'un nouveau numéro de comptage.

Vous avez d'abord besoin de ces deux autorisations dans votre manifeste:

<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />

Ensuite, vous avez besoin de ces deux méthodes pour installer et désinstaller des raccourcis. La shortcutAddméthode crée une image bitmap contenant un nombre. C'est juste pour démontrer que cela change réellement. Vous voulez probablement changer cette partie avec quelque chose que vous voulez dans votre application.

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

Et enfin, voici deux écouteurs pour ajouter le premier raccourci et mettre à jour le raccourci avec un compteur incrémentiel.

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

Remarques:

  • Cette méthode fonctionne également si votre application contrôle plus de raccourcis sur l'écran d'accueil, par exemple avec différents extras dans le Intent. Ils ont juste besoin de noms différents pour que le bon soit désinstallé et réinstallé.

  • La gestion programmatique des raccourcis sous Android est une fonctionnalité Android bien connue, largement utilisée mais non officiellement prise en charge. Cela semble fonctionner sur le lanceur par défaut et je ne l'ai jamais essayé nulle part ailleurs. Alors ne me blâmez pas, lorsque vous recevez ces e-mails d'utilisateur "Cela ne fonctionne pas sur mon téléphone XYZ, double rooté, super foutu"

  • Le lanceur écrit un Toastlorsqu'un raccourci a été installé et un autre lorsqu'un raccourci a été désinstallé. Je reçois donc deux Toasts à chaque fois que je change d'icône. Ce n'est pas parfait, mais bon, tant que le reste de mon application est parfait ...

jboi
la source
9
L'application Calendrier d'aujourd'hui peut utiliser des icônes tous les jours sans porter de pain grillé.
Jim McKeeth
1
@ Jim (je pense) c'est en fait un widget alors
JacksOnF1re
3
shortcutDel ne fonctionne malheureusement plus dans Marshmallow, voir aussi stackoverflow.com/a/33731620/1545993
Taifun
2
cela remplace l'icône de raccourci, pas l'icône du lanceur
user1506104
3
Dans les lignes ci-dessous, qu'est-ce que Play.class et Constants.ACTION_PLAY Intent shortcutIntent = new Intent (getApplicationContext (), Play.class); shortcutIntent.setAction (Constantes.ACTION_PLAY);
Dasharath Singh Bajroliya
136

Essayez ceci, cela fonctionne bien pour moi:

1 . Modifiez votre MainActivitysection dans AndroidManifest.xml, supprimez-la, ligne avec MAINcatégorie dans intent-filtersection

<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. Créez <activity-alias>, pour chacune de vos icônes. Comme ça

<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. Définir par programme: définissez l'attribut ENABLE pour leactivity-alias

 getPackageManager().setComponentEnabledSetting(
        new ComponentName("ru.quickmessage.pa", "ru.quickmessage.pa.MainActivity-Red"), 
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

Remarque, au moins un doit être activé à tout moment.

Pennsylvanie
la source
3
fonctionne malheureusement différemment sur les appareils que j'ai essayés. Fonctionne sur HTC Desire 2.2, mais peu fiable sur Galaxy Nexus 4.2.2 et Nexus 7 4.3. Sur le Galaxy Nexus, toutes les icônes de l'application peuvent disparaître, ainsi que tous les widgets supprimés. Dommage, j'ai donc dû supprimer cette fonctionnalité sur les appareils plus récents.
Richard Le Mesurier
7
Je ne peux pas lancer mon application depuis que j'ai supprimé ceci: <action android: name = "android.intent.action.MAIN" /> <category android: name = "android.intent.category.LAUNCHER" />
noobProgrammer
1
Cela fonctionne parfaitement pour moi pour basculer entre les icônes du lanceur dans la barre des applications et l'écran d'accueil, mais j'ai trouvé que l'icône utilisée au niveau du système d'exploitation (comme pour le multitâche, la fenêtre contextuelle de désinstallation ou dans la liste du gestionnaire d'applications) reste l'original ou devient l'icône Android générique par défaut si vous n'en avez pas défini au niveau de l'application dans le manifeste. Avez-vous trouvé une solution à cela ou tolérez-vous simplement une discordance (ou une absence) dans l'icône au niveau du système d'exploitation?
jokeefe
2
Erreur lors de l'exécution de l'application. Activité par défaut introuvable.
CopsOnRoad
1
Erreur lors de l'exécution de l'application. Activité par défaut introuvable
Kamil Ibadov
36

Vous ne pouvez pas modifier le manifeste ou la ressource dans l'APK signé et scellé, sauf par le biais d'une mise à niveau logicielle.

CommonsWare
la source
2
@Nanne: C'est un widget d'application ou une fonction d'écran d'accueil, pas une icône d'application. Vous ne pouvez toujours pas modifier le manifeste ou une ressource, sauf via une mise à niveau logicielle.
CommonsWare
1
? Non, je veux dire l'inverse: ce n'est pas (annoncé comme) un widget. Je l'ajoute en tant que raccourci d'application. Mais, comme vous le dites, ce n'est pas parce que ce truc non stocké implique que c'est juste une icône, cela ne veut pas dire que c'est :)
Nanne
2
@NeTeInStEiN: Cela ne fonctionnera pas pour tous les écrans d'accueil (par exemple, ceux qui ne font pas attention aux changements activés par les composants).
CommonsWare
1
Ce n'est plus vrai. Google Agenda sur Android 6+ change quotidiennement dans le lanceur. Aujourd'hui, l'icône est un "2", hier c'était un "1". Habituellement, il y avait un "31" sur l'icône. Mais plus maintenant, ça change. Quelqu'un sait comment cela est possible?
UeliDeSchwert
1
@Bobby: Je veux dire qu'il y a des centaines, sinon des milliers, d'implémentations d'écran d'accueil sur le Play Store, en plus des centaines d'écrans d'accueil préinstallés différents sur les milliers de modèles d'appareils Android existants. Ces implémentations d'écran d'accueil sont les bienvenues pour avoir des crochets qui permettent le remplacement dynamique de l'icône du lanceur. Cependant, tous les écrans d'accueil ne doivent pas offrir cela. Ce n'est pas parce que vous voyez ce comportement pour une application sur un écran d'accueil sur un appareil qu'il est disponible pour toutes les applications sur tous les écrans d'accueil sur tous les appareils.
CommonsWare
17

Par programme, vous souhaiterez peut-être publier vous-même le lanceur d'applications:

Remarque: cette méthode ne fonctionne plus à partir d'Android 8.0 - Oreo

Dans votre AndroidManifest.xml, ajoutez:

<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>

Ensuite, vous devez créer votre intention de lanceur d'applications:

Intent myLauncherIntent = new Intent();
myLauncherIntent.setClassName("your.package.name", "YourLauncherActivityName");
myLauncherIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

Créez une intention de raccourci d'installation avec votre lanceur d'application et l'icône personnalisée:

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

Et enfin lancez l'intention de diffusion:

getApplicationContext().sendBroadcast(intent);
Andrei Mărcuţ
la source
cela remplace l'icône de raccourci, pas l'icône du lanceur
user1506104
10

En supposant que vous vouliez dire changer l'icône affichée sur l'écran d'accueil, cela pourrait facilement être fait en créant un widget qui fait exactement cela. Voici un article qui montre comment cela peut être accompli pour une application de type "nouveaux messages" similaire à l'iPhone:

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

Marius Kjeldahl
la source
9

La solution de @ PA fonctionne partiellement pour moi. Détaillez mes conclusions ci-dessous:

1) Le premier extrait de code est incorrect, voir ci-dessous:

<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) Devrait utiliser le code suivant pour désactiver toutes les icônes avant d'en activer une autre, sinon il ajoutera une nouvelle icône, au lieu de la remplacer.

getPackageManager().setComponentEnabledSetting(
        getComponentName(), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

MAIS, si vous utilisez le code ci-dessus, le raccourci sur l'écran d'accueil sera supprimé! Et il ne sera pas automatiquement ajouté. Vous pourrez peut-être ajouter une icône par programme, mais elle ne restera probablement pas dans la même position qu'avant.

3) Notez que l'icône ne sera pas modifiée immédiatement, cela peut prendre plusieurs secondes. Si vous cliquez dessus juste après la modification, vous pourriez obtenir une erreur disant: "L'application n'est pas installée".

Donc, à mon humble avis, cette solution ne convient que pour changer d'icône dans le lanceur d'application uniquement, pas pour les raccourcis (c'est-à-dire l'icône sur l'écran d'accueil)

Deqing
la source
2
Erreur lors de l'exécution de l'application. Activité par défaut introuvable.
CopsOnRoad
vous supprimez le lanceur, comment trouvera-t-il l'activité par défaut @Deqing?
j2emanue
5

Essayez cette solution

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

Ajoutez le code suivant lorsque vous souhaitez modifier l'icône de votre application

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);
Faxriddin Abdullayev
la source
4

AndroidManifest.xml exemple:

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

Ensuite, suivez le code ci-dessous dans 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);
Pritesh Patel
la source
Je reçois une com.pritesh.resourceidentifierexample.MainActivity-Red doesn't exist in com.pritesh.resourceidentifierexampleexception. ici, j'ai utilisé votre nom manifeste juste pour démontrer mon problème
Tejas Pandya
0

Pour que la solution de Markus fonctionne, j'avais besoin de la première intention, alors soyez:

Intent myLauncherIntent = new Intent(Intent.ACTION_MAIN);
            myLauncherIntent.setClassName(this,  this.getClass().getName());
            myLauncherIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PuZZleDucK
la source
0

En appliquant les suggestions mentionnées, j'ai rencontré le problème de la suppression de l'application chaque fois que l'icône par défaut est remplacée par une nouvelle icône. J'ai donc implémenté le code avec quelques ajustements. Étape 1). Dans le fichier AndroidManifest.xml, créez pour l'activité par défaut avec android: enabled = "true" et autre alias avec android: enabled = "false". Votre ne contiendra pas mais les ajoutera avec 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>

Étape 2). Créez une méthode qui sera utilisée pour désactiver le 1er alias d'activité qui contient l'icône par défaut et activer le 2ème alias qui contient l'icône doit être changé.

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

Étape 3). Maintenant, appelez cette méthode en fonction de vos besoins, par exemple, un clic sur un bouton ou des conditions spécifiques à une date ou à une occasion, simplement comme -

// Switch app icon to new icon
    GeneralUtils.changeAppIconDynamically(EditProfileActivity.this, true);
// Switch app icon to default icon
            GeneralUtils.changeAppIconDynamically(EditProfileActivity.this, false);

J'espère que cela aidera ceux qui sont confrontés au problème de la mort d'une application lors du changement d'icône. Bon codage :)

Abhijeet
la source