Cómo obtener el Android ID o IMEI con Kotlin

En el dinámico mundo del desarrollo de aplicaciones Android, la necesidad de identificar un dispositivo de forma única surge con frecuencia. Ya sea para análisis, seguimiento de instalaciones, prevención de fraudes o personalización de la experiencia del usuario, tener un identificador fiable es una tarea común. Sin embargo, obtener el Android ID o el IMEI (International Mobile Equipment Identity) con Kotlin no es tan sencillo como podría parecer a primera vista, especialmente con las crecientes restricciones de privacidad implementadas por Google en las versiones más recientes de Android.

Este post tiene como objetivo desglosar las complejidades, los métodos, las mejores prácticas y, sobre todo, las consideraciones éticas y de privacidad que conlleva la obtención de estos identificadores. Nos adentraremos en las APIs de Android con ejemplos de código en Kotlin, discutiremos los cambios de comportamiento a través de las diferentes versiones del sistema operativo y exploraremos alternativas más adecuadas para la mayoría de los casos de uso. Prepárese para comprender no solo cómo obtener estos identificadores, sino también cuándo y por qué debería (o no debería) hacerlo.

Entendiendo el Android ID y el IMEI

Captivating sunset clouds with dramatic colors against a blue sky.

Antes de sumergirnos en el código, es fundamental comprender qué son exactamente el Android ID y el IMEI, cuáles son sus características y cómo se han visto afectados por las políticas de privacidad de Android a lo largo del tiempo. Conocer estos detalles nos ayudará a tomar decisiones informadas sobre su uso.

¿Qué es el Android ID?

El Android ID es un identificador único de 64 bits (en formato de cadena hexadecimal) que se genera cuando un usuario configura por primera vez su dispositivo. Es específico de cada dispositivo y se almacena en Settings.Secure.ANDROID_ID.

Históricamente, el Android ID era un identificador bastante persistente. Se mantenía igual incluso después de un restablecimiento de fábrica del dispositivo, a menos que el dispositivo se formateara por completo y se instalara una ROM diferente. Sin embargo, esto cambió significativamente. A partir de Android 8.0 (API 26), el Android ID se reinicia si la aplicación se desinstala y se vuelve a instalar. Esto significa que ya no es un identificador de dispositivo globalmente persistente desde la perspectiva de una aplicación específica. En otras palabras, para una aplicación particular, el Android ID es un identificador único, pero solo mientras esa aplicación esté instalada en el dispositivo. Si el usuario desinstala la aplicación y la vuelve a instalar, el Android ID que obtenga la aplicación será diferente. Además, para dispositivos con múltiples perfiles de usuario, cada perfil tendrá su propio Android ID.

Considero que este cambio es un paso muy positivo hacia la mejora de la privacidad del usuario, ya que limita la capacidad de las aplicaciones para rastrear a los usuarios a largo plazo si deciden desinstalar y reinstalar una app. Para la mayoría de los desarrolladores, esto implica que el Android ID es más útil como un identificador de "instalación de la aplicación en el dispositivo" que como un identificador de "dispositivo único global".

¿Qué es el IMEI?

El IMEI, o International Mobile Equipment Identity, es un número único de 15 o 17 dígitos que identifica de forma global a un dispositivo móvil. Este número es intrínseco al hardware del teléfono y está asociado con el módem GSM/UMTS/LTE del dispositivo. Es importante destacar que solo los dispositivos que tienen la capacidad de conectarse a una red móvil (es decir, aquellos con una tarjeta SIM) tienen un IMEI. Los dispositivos solo Wi-Fi (como muchas tablets o dispositivos sin capacidad de celular) no tienen IMEI.

El IMEI es utilizado por las redes móviles para identificar dispositivos, bloquear teléfonos robados y garantizar que solo los dispositivos autorizados se conecten a la red. Es un identificador extremadamente persistente y globalmente único para el hardware del dispositivo.

Diferencias clave y consideraciones de privacidad

La principal diferencia radica en su alcance y persistencia. El Android ID es un identificador que ahora está scopeado a la aplicación y al dispositivo (con reinicios tras desinstalación/reinstalación en versiones modernas), mientras que el IMEI es un identificador de hardware global y persistente que no cambia con las instalaciones de aplicaciones ni los restablecimientos de fábrica.

Desde el punto de vista de la privacidad, el IMEI es mucho más sensible. Su persistencia y unicidad global permiten un rastreo casi inmutable del dispositivo a lo largo del tiempo, lo cual plantea preocupaciones significativas si cae en las manos equivocadas o se usa sin consentimiento explícito. Por esta razón, Google ha implementado restricciones muy estrictas para acceder al IMEI. El Android ID, con su nueva naturaleza de reinicio, es considerablemente menos invasivo.

Obtención del Android ID con Kotlin

Obtener el Android ID es relativamente sencillo, pero hay que tener en cuenta las implicaciones de las versiones de Android que ya hemos mencionado.

El método recomendado: `Settings.Secure.ANDROID_ID`

Para obtener el Android ID, utilizamos la clase Settings.Secure. Este método no requiere ningún permiso de tiempo de ejecución (runtime permission) en el AndroidManifest.xml de su aplicación, lo cual lo hace muy atractivo y fácil de implementar.

Aquí tienes un ejemplo de cómo obtenerlo en Kotlin:

import android.content.Context
import android.provider.Settings

fun getAndroidId(context: Context): String? {
    // El Android ID es un String de 64 bits (en formato hexadecimal)
    // Puede cambiar si la aplicación es desinstalada y reinstalada en Android 8.0+
    // y para diferentes perfiles de usuario.
    return Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
}

// Ejemplo de uso en una actividad o fragmento:
// val androidId = getAndroidId(applicationContext)
// if (androidId != null) {
//     Log.d("DeviceID", "Android ID: $androidId")
// } else {
//     Log.d("DeviceID", "No se pudo obtener el Android ID.")
// }

Consideraciones sobre el alcance y la persistencia:

Como ya se mencionó, en Android 8.0 (API 26) y posteriores, el Android ID es un valor que se "restablece" si la aplicación que lo consulta se desinstala y luego se vuelve a instalar en el mismo dispositivo. Esto significa que si un usuario desinstala su aplicación y luego la reinstala, su aplicación verá un nuevo Android ID. Esto lo hace menos útil para el seguimiento a largo plazo de un dispositivo específico, pero muy adecuado para identificar una "instalación" particular de su aplicación.

Mi opinión personal es que, para la mayoría de los casos de uso que requieren identificar una instalación de aplicación (por ejemplo, para guardar preferencias únicas de usuario o configuraciones), el Android ID es una opción perfectamente válida y segura. Su facilidad de acceso y la ausencia de permisos lo convierten en una solución elegante y menos intrusiva. Sin embargo, si necesita un identificador más persistente que sobreviva a las desinstalaciones, deberá explorar otras alternativas que discutiremos más adelante.

Permisos necesarios (ninguno explícito para `ANDROID_ID`)

Una de las grandes ventajas de usar Settings.Secure.ANDROID_ID es que no requiere ningún permiso especial en el AndroidManifest.xml. Esto simplifica enormemente su implementación y minimiza la fricción con el usuario, ya que no se le solicitará ningún permiso sensible.

Puede consultar la documentación oficial de Android para Settings.Secure.ANDROID_ID para obtener más detalles: Documentación de Settings.Secure.ANDROID_ID

Obtención del IMEI con Kotlin (consideraciones y limitaciones)

El acceso al IMEI es un tema mucho más delicado debido a sus implicaciones de privacidad. Google ha implementado restricciones progresivas para limitar el acceso no autorizado a este identificador.

Desafíos actuales y cambios en Android 10+

A partir de Android 10 (API 29), las aplicaciones ya no pueden acceder directamente al IMEI (ni a otros identificadores no reiniciables como el número de serie) sin permisos privilegiados o específicos del sistema. Esto significa que las aplicaciones de terceros no pueden obtener el IMEI en Android 10 o versiones superiores, incluso si declaran el permiso READ_PHONE_STATE.

Si su aplicación se ejecuta en Android 10 o superior y usted intenta acceder a getDeviceId() (que solía ser el método para obtener el IMEI), el sistema lanzará un SecurityException o devolverá un valor nulo/vacío, dependiendo de la situación y del OEM. Este es un cambio crucial que refleja el compromiso de Google con la privacidad del usuario.

Permisos necesarios para versiones antiguas (`READ_PHONE_STATE`)

Para versiones de Android anteriores a la 10 (API < 29), era posible obtener el IMEI declarando el permiso READ_PHONE_STATE en el AndroidManifest.xml y solicitándolo en tiempo de ejecución.

Paso 1: Añadir permiso en AndroidManifest.xml

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

Paso 2: Solicitar y usar el permiso en Kotlin (para API < 29)

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.telephony.TelephonyManager
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat

class MainActivity : AppCompatActivity() {

    private val requestPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
            if (isGranted) {
                // Permiso concedido, intenta obtener el IMEI
                val imei = getImei(this)
                if (imei != null) {
                    Log.d("DeviceID", "IMEI (API < 29): $imei")
                } else {
                    Log.d("DeviceID", "No se pudo obtener el IMEI.")
                }
            } else {
                // Permiso denegado, manejar el caso
                Log.w("DeviceID", "Permiso READ_PHONE_STATE denegado.")
            }
        }

    // Función para obtener el IMEI (solo para API < 29)
    fun getImei(context: Context): String? {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // A partir de Android 10 (API 29), el acceso directo al IMEI está muy restringido.
            // Siempre devolverá null o lanzará SecurityException para aplicaciones de terceros.
            Log.w("DeviceID", "El acceso directo al IMEI está restringido en Android 10+.")
            return null
        }

        if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) ==
            PackageManager.PERMISSION_GRANTED) {
            val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            return telephonyManager.deviceId // getDeviceId() está deprecado y restringido en API 29+
        }
        return null
    }

    override fun onCreate(savedInstanceState) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Llama a la lógica de permisos si es necesario
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) !=
                PackageManager.PERMISSION_GRANTED) {
                // Solicitar el permiso si aún no se ha concedido
                requestPermissionLauncher.launch(Manifest.permission.READ_PHONE_STATE)
            } else {
                // El permiso ya está concedido, obtén el IMEI
                val imei = getImei(this)
                if (imei != null) {
                    Log.d("DeviceID", "IMEI (API < 29): $imei")
                } else {
                    Log.d("DeviceID", "No se pudo obtener el IMEI.")
                }
            }
        } else {
            Log.w("DeviceID", "No se puede obtener el IMEI directamente en Android 10+ para apps de terceros.")
        }
    }
}

Importante: Este código solo funcionará para dispositivos con Android 9 (API 28) o inferior. En versiones superiores, incluso con el permiso, el acceso estará denegado.

El enfoque para Android 10 (API 29) y posteriores

Como hemos visto, para Android 10 y versiones posteriores, TelephonyManager.getDeviceId() ha sido efectivamente "deprecado" para aplicaciones de terceros en el sentido de que no pueden acceder a él para obtener el IMEI. En su lugar, si una aplicación necesita identificadores para fines como la prevención de fraudes o la analítica, se espera que utilice identificadores específicos de la suscripción (como getSubscriberId() o getImeiForSlot(), pero estos también tienen sus propias restricciones y no son un sustituto directo del IMEI del dispositivo) o identificadores de la aplicación.

Mi opinión aquí es clara: para las aplicaciones de terceros, la búsqueda del IMEI se ha vuelto prácticamente obsoleta. Intentar sortear estas restricciones no solo es una mala práctica, sino que también va en contra de la filosofía de privacidad de Android. Como desarrolladores, debemos adaptarnos a estos cambios y buscar soluciones que respeten la privacidad del usuario, incluso si eso significa replantear la forma en que identificamos los dispositivos.

Para más información sobre los identificadores únicos en Android, puede consultar la guía oficial de desarrolladores: Identificadores únicos de usuario de Android

Alternativas y mejores prácticas para la identificación de dispositivos

Dado que el Android ID tiene sus limitaciones de persistencia y el IMEI está mayormente fuera de alcance, es crucial explorar alternativas que sean más adecuadas y respetuosas con la privacidad del usuario.

Identificadores de instancia de instalación (GUID)

Para la mayoría de los casos de uso donde se necesita un identificador persistente para una instalación de la aplicación, un GUID (Global Unique Identifier) generado por la propia aplicación y almacenado localmente es la mejor opción.

Cómo implementarlo:

  1. Generar un GUID: La primera vez que la aplicación se ejecuta, se genera un nuevo UUID (Universally Unique Identifier).
  2. Almacenar el GUID: Este UUID se guarda en SharedPreferences o en un almacenamiento interno, asegurando que persista a través de reinicios de la aplicación y del dispositivo.
  3. Recuperar el GUID: En ejecuciones posteriores, la aplicación lee el GUID almacenado. Si no existe (primera ejecución o datos de la aplicación borrados), se genera uno nuevo.
import android.content.Context
import java.util.UUID

object DeviceInstallationId {
    private const val PREF_NAME = "app_installation_prefs"
    private const val INSTALLATION_ID_KEY = "installation_id"

    @Volatile private var installationId: String? = null

    @Synchronized
    fun getId(context: Context): String {
        if (installationId == null) {
            val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
            installationId = prefs.getString(INSTALLATION_ID_KEY, null)
            if (installationId == null) {
                installationId = UUID.randomUUID().toString()
                prefs.edit().putString(INSTALLATION_ID_KEY, installationId).apply()
            }
        }
        return installationId!!
    }
}

// Ejemplo de uso:
// val appInstallationId = DeviceInstallationId.getId(applicationContext)
// Log.d("DeviceID", "Installation ID: $appInstallationId")

Ventajas:

  • Persistencia: Sobrevive a reinicios de la aplicación y del dispositivo.
  • Control: La aplicación tiene control total sobre el identificador.
  • Privacidad: Está scopeado a la aplicación; no es un identificador de dispositivo global que otras aplicaciones puedan acceder.
  • Sin permisos: No requiere ningún permiso especial.

Mi opinión es que este método debería ser la opción por defecto para la mayoría de los desarrolladores. Es una solución robusta, privada y fácil de implementar, que resuelve la necesidad de identificación de una instancia de aplicación sin invadir la privacidad del usuario.

IDs de publicidad

El Advertising ID de Google es un identificador único, reiniciable y no persistente que Google Play Services proporciona. Está diseñado específicamente para la publicidad y el análisis de usuarios. Los usuarios pueden restablecerlo o desactivar la personalización de anuncios en la configuración de Google de su dispositivo.

Cómo obtenerlo:

Se requiere la biblioteca de Google Play Services para acceder a él.

import android.content.Context
import com.google.android.gms.ads.identifier.AdvertisingIdClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

suspend fun getAdvertisingId(context: Context): String? = withContext(Dispatchers.IO) {
    try {
        val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context)
        // adInfo.isLimitAdTrackingEnabled() indica si el usuario ha limitado el seguimiento de anuncios
        return@withContext adInfo?.id
    } catch (e: Exception) {
        Log.e("DeviceID", "Error al obtener el Advertising ID: ${e.message}")
        return@withContext null
    }
}

// Ejemplo de uso (desde una corrutina o un contexto asíncrono):
// lifecycleScope.launch {
//     val adId = getAdvertisingId(applicationContext)
//     if (adId != null) {
//         Log.d("DeviceID", "Advertising ID: $adId")
//     } else {
//         Log.d("DeviceID", "No se pudo obtener el Advertising ID.")
//     }
// }

Consideraciones:

  • Requiere Google Play Services: No funcionará en dispositivos sin GMS.
  • Reseteable por el usuario: Los usuarios tienen control sobre él.
  • Propósito específico: Diseñado para publicidad y análisis, no para identificar el dispositivo para funciones principales de la
Diario Tecnología