¿Cómo cambiar un icono de aplicación mediante programación en Android?

153

¿Es posible cambiar el icono de una aplicación directamente desde el programa?
Quiero decir, cambiar icon.pngen la res\drawablecarpeta.
Me gustaría permitir que los usuarios cambien el ícono de la aplicación desde el programa para que la próxima vez vean el ícono previamente seleccionado en el iniciador.

systemmpuntoout
fuente

Respuestas:

80

Es una pregunta antigua, pero aún activa, ya que no hay una función explícita de Android. Y los chicos de Facebook encontraron una solución, de alguna manera. Hoy, encontré una manera que funciona para mí. No es perfecto (vea las observaciones al final de esta respuesta) ¡pero funciona!

La idea principal es que actualizo el icono del acceso directo de mi aplicación, creado por el iniciador en mi pantalla de inicio. Cuando quiero cambiar algo en el icono de acceso directo, lo elimino primero y lo vuelvo a crear con un nuevo mapa de bits.

Aquí está el código. Tiene un botón increment. Cuando se presiona, el acceso directo se reemplaza por uno que tiene un nuevo número de conteo.

Primero necesita estos dos permisos en su manifiesto:

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

Entonces necesita estos dos métodos para instalar y desinstalar accesos directos. El shortcutAddmétodo crea un mapa de bits con un número. Esto es solo para demostrar que realmente cambia. Probablemente quieras cambiar esa parte con algo que quieras en tu aplicación.

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

Y finalmente, aquí hay dos oyentes para agregar el primer acceso directo y actualizar el acceso directo con un contador incremental.

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

Observaciones:

  • Esta manera también funciona si su aplicación controla más accesos directos en la pantalla de inicio, por ejemplo, con diferentes extras en el Intent. Solo necesitan nombres diferentes para que se desinstale y reinstale el correcto.

  • El manejo programático de accesos directos en Android es una característica de Android bien conocida, ampliamente utilizada pero no oficialmente compatible. Parece funcionar en el iniciador predeterminado y nunca lo probé en ningún otro lado. Así que no me culpen, cuando reciban estos correos electrónicos de usuario "No funciona en mi teléfono XYZ, doble rooteado, súper arruinado"

  • El iniciador escribe un Toastcuando se instaló un acceso directo y uno cuando se desinstaló un acceso directo. Entonces obtengo dos Toasts cada vez que cambio el ícono. Esto no es perfecto, pero bueno, siempre y cuando el resto de mi aplicación sea perfecta ...

jboi
fuente
9
La aplicación Calendario de hoy contiene íconos todos los días sin hacer un brindis.
Jim McKeeth
1
@ Jim (creo) esto es realmente un widget entonces
JacksOnF1re 05 de
3
shortcutDel desafortunadamente ya no funciona en Marshmallow, vea también stackoverflow.com/a/33731620/1545993
Taifun
2
esto reemplaza el ícono de acceso directo, no el ícono del
iniciador
3
En las líneas siguientes, qué es Play.class y Constants.ACTION_PLAY Intent shortcutIntent = new Intent (getApplicationContext (), Play.class); shortcutIntent.setAction (Constants.ACTION_PLAY);
Dasharath Singh Bajroliya
136

Prueba esto, funciona bien para mí:

1) Modifique su MainActivitysección AndroidManifest.xml, elimínela, alinee con la MAINcategoría en la intent-filtersección

<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) Crea <activity-alias>, para cada uno de tus íconos. Me gusta esto

<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) Establecer mediante programación: establecer el atributo ENABLE para el apropiadoactivity-alias

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

Tenga en cuenta que al menos uno debe estar habilitado en todo momento.

Pensilvania
fuente
3
lamentablemente funciona de manera diferente en todos los dispositivos que he probado. Funciona en HTC Desire 2.2, pero no es confiable en Galaxy Nexus 4.2.2 y Nexus 7 4.3. En el Galaxy Nexus, puede hacer que desaparezcan todos los íconos de la aplicación y también que se eliminen los widgets. Lástima, así que tuve que eliminar esta funcionalidad en dispositivos posteriores.
Richard Le Mesurier
77
no puedo iniciar mi aplicación desde que eliminé esto: <action android: name = "android.intent.action.MAIN" /> <category android: name = "android.intent.category.LAUNCHER" />
noobProgrammer
1
Esto funciona perfectamente para mí para cambiar entre los íconos del iniciador en la bandeja de aplicaciones y la pantalla de inicio, pero descubrí que el ícono utilizado en el nivel del sistema operativo (como para multitarea, la ventana emergente de desinstalación o en la lista del Administrador de aplicaciones) el original o se convierte en el ícono genérico predeterminado de Android si no ha configurado uno en el nivel de aplicación en el manifiesto. ¿Ha encontrado una solución a esto o simplemente tolera una falta de coincidencia (o ausencia) en el icono en el nivel del sistema operativo?
jokeefe
2
Error al ejecutar la aplicación Actividad predeterminada no encontrada.
CopsOnRoad
1
Error al ejecutar la aplicación Actividad predeterminada no encontrada
Kamil Ibadov
36

No puede cambiar el manifiesto o el recurso en el APK firmado y sellado, excepto a través de una actualización de software.

CommonsWare
fuente
2
@Nanne: es un widget de aplicación o función de pantalla de inicio, no un ícono de aplicación. Aún no puede cambiar el manifiesto o un recurso, excepto a través de una actualización de software.
CommonsWare
1
? No, quiero decir al revés: no es (anunciado como) un widget. Lo agrego como un atajo de aplicación. Pero, como usted dice, solo porque este material no básico implica que es solo un ícono, eso no significa que sea :)
Nanne
2
@NeTeInStEiN: no funcionará para todas las pantallas de inicio (por ejemplo, aquellas que no prestan atención a los cambios habilitados para componentes).
CommonsWare
1
Ya no es cierto. Google Calendar en Android 6+ cambia diariamente en el iniciador. Hoy, el ícono es un "2", ayer fue un "1". Por lo general, había un "31" en el ícono. Pero ya no, cambia. Alguien sabe cómo es esto posible?
UeliDeSchwert
1
@Bobby: Quiero decir que hay cientos, si no miles, de implementaciones de pantalla de inicio en Play Store, además de los cientos de diferentes pantallas de inicio preinstaladas en los miles de modelos de dispositivos Android que existen. Las implementaciones de la pantalla de inicio son bienvenidas para tener ganchos que permiten el reemplazo dinámico del icono del iniciador. Sin embargo, no todas las pantallas de inicio tienen que ofrecer esto. El hecho de que vea este comportamiento para una aplicación en una pantalla de inicio en un dispositivo no significa que esté disponible para todas las aplicaciones en todas las pantallas de inicio en todos los dispositivos.
CommonsWare
17

Programáticamente, es posible que desee publicar el iniciador de la aplicación usted mismo:

Nota: este método ya no funciona a partir de Android 8.0 - Oreo

En su AndroidManifest.xml, agregue:

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

Entonces necesitas crear tu intención de iniciador de aplicaciones:

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

Cree una intención de acceso directo de instalación con el iniciador de su aplicación y el icono personalizado:

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

Y finalmente lanzar la intención de transmisión:

getApplicationContext().sendBroadcast(intent);
Andrei Marcut
fuente
esto reemplaza el icono de acceso directo, no en el icono lanzador
user1506104
10

Suponiendo que quiere decir cambiar el icono que se muestra en la pantalla de inicio, esto podría hacerse fácilmente creando un widget que haga exactamente esto. Aquí hay un artículo que demuestra cómo se puede lograr para una aplicación de tipo "mensajes nuevos" similar a iPhone:

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

Marius Kjeldahl
fuente
9

La solución de @ PA me funciona parcialmente. Detalla mis hallazgos a continuación:

1) El primer fragmento de código es incorrecto, vea a continuación:

<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) Debe usar el siguiente código para deshabilitar todos los íconos antes de habilitar otro, de lo contrario agregará un ícono nuevo, en lugar de reemplazarlo.

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

PERO, si usa el código anterior, ¡se eliminará el acceso directo en la pantalla de inicio! Y no se volverá a agregar automáticamente. Es posible que pueda volver a agregar el icono mediante programación, pero probablemente no se mantendrá en la misma posición que antes.

3) Tenga en cuenta que el ícono no cambiará de inmediato, puede tomar varios segundos. Si hace clic justo después de cambiar, puede recibir un error que dice: "La aplicación no está instalada".

Entonces, en mi humilde opinión, esta solución solo es adecuada para cambiar el ícono solo en el iniciador de aplicaciones, no para los accesos directos (es decir, el ícono en la pantalla de inicio)

Deqing
fuente
2
Error al ejecutar la aplicación Actividad predeterminada no encontrada.
CopsOnRoad
Si elimina el iniciador, ¿cómo encontrará la actividad predeterminada @Deqing?
j2emanue
5

Prueba esta solución

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

Agregue el siguiente código cuando desee cambiar el ícono de su aplicación

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

AndroidManifest.xml ejemplo:

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

Luego siga el código dado a continuación en 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
fuente
Estoy recibiendo una com.pritesh.resourceidentifierexample.MainActivity-Red doesn't exist in com.pritesh.resourceidentifierexampleexcepción. aquí he usado tu nombre de manifiesto solo para demostrar mi problema
Tejas Pandya
0

Para que Markus funcionara, necesitaba la primera intención:

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

Aplicando las sugerencias mencionadas, me he enfrentado al problema de que la aplicación sea eliminada cada vez que el ícono predeterminado se cambia a nuevo. Así que he implementado el código con algunos ajustes. Paso 1). En el archivo AndroidManifest.xml, cree una actividad predeterminada con android: enabled = "true" y otros alias con android: enabled = "false". Tu no contendrá pero agregará aquellos con 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>

Paso 2). Cree un método que se utilizará para deshabilitar el primer alias de actividad que contiene el ícono predeterminado y habilitar el segundo alias que contiene el ícono debe cambiarse.

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

Paso 3). Ahora llame a este método según sus requisitos, digamos al hacer clic en el botón o en una fecha específica o en condiciones específicas de la ocasión, simplemente como -

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

Espero que esto ayude a aquellos que enfrentan el problema de que la aplicación muera en el cambio de ícono. Happy Coding :)

Abhijeet
fuente