Giới thiệu
IMEI (International Mobile Equipment Identity) là mã định danh duy nhất của thiết bị di động. Từ **Android 10 (API 29)**, Google **hạn chế chặt chẽ** việc truy cập IMEI:
- Chỉ **system apps** hoặc **device owner/profile owner apps** mới được đọc IMEI.
- App thông thường (non-system) sẽ nhận `SecurityException` hoặc trả về `null`/empty string.
- Cần quyền
READ_PRIVILEGED_PHONE_STATE(chỉ system app mới được cấp).
Bài viết trình bày cách lấy IMEI trên các phiên bản Android cũ (để tham khảo) và giải pháp thay thế cho app hiện đại.
Các bước thực hiện (Chỉ hoạt động trên Android < 10 hoặc System App)
Bước 1: Tạo dự án mới
Android Studio → File → New Project → Empty View Activity.
Bước 2: Cấu hình AndroidManifest.xml
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- Chỉ system app mới được cấp quyền dưới đây -->
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
Bước 3: Thiết kế giao diện (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center"
tools:context=".MainActivity">
<Button
android:id="@+id/btnGetImei"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lấy số IMEI" />
<TextView
android:id="@+id/tvImei"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:textSize="16sp"
android:textStyle="bold"
android:text="IMEI: Chưa lấy" />
</LinearLayout>
Bước 4: Code lấy IMEI (MainActivity.kt)
package com.example.getimei
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.telephony.TelephonyManager
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.getimei.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val REQUEST_CODE = 101
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnGetImei.setOnClickListener {
requestImei()
}
}
private fun requestImei() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), REQUEST_CODE)
} else {
fetchImei()
}
}
private fun fetchImei() {
val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
try {
val imei: String?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // API 26+
imei = telephonyManager.imei
} else {
// Deprecated API < 26
@Suppress("DEPRECATION")
imei = telephonyManager.deviceId
}
if (imei.isNullOrEmpty()) {
binding.tvImei.text = "IMEI: Không thể truy cập (Android 10+ hạn chế)"
Toast.makeText(this, "App thông thường không thể đọc IMEI trên Android 10+", Toast.LENGTH_LONG).show()
} else {
binding.tvImei.text = "IMEI: $imei"
}
} catch (e: SecurityException) {
binding.tvImei.text = "IMEI: SecurityException - Không có quyền đặc quyền"
Log.e("IMEI", "SecurityException", e)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
fetchImei()
} else {
Toast.makeText(this, "Quyền bị từ chối", Toast.LENGTH_SHORT).show()
}
}
}
Thay thế IMEI cho App thông thường (Android 10+)
Vì không thể lấy IMEI, hãy dùng các định danh thay thế:
1. Instance ID / Firebase Installation ID (Khuyến nghị)
// build.gradle.kts
implementation("com.google.firebase:firebase-installations:17.2.0")
implementation("com.google.firebase:firebase-installations-ktx:17.2.0")
// Lấy Installation ID (unique per app install)
FirebaseInstallations.getInstance().id.addOnSuccessListener { installationId ->
// Gửi lên server để identify device
Log.d("DeviceID", "Installation ID: $installationId")
}
2. Advertising ID (Google Play Services)
// build.gradle.kts
implementation("com.google.android.gms:play-services-ads-identifier:18.0.1")
// Lấy Advertising ID (user có thể reset)
AdvertisingIdClient.getAdvertisingIdInfo(this).addOnSuccessListener { info ->
val adId = info.id
val isLimitAdTracking = info.isLimitAdTrackingEnabled
}
3. Android ID (Settings.Secure.ANDROID_ID)
val androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
// Lưu ý: Có thể reset sau factory reset, hoặc khác nhau giữa work profile/personal profile
4. Tự generate UUID và lưu trong SharedPreferences
private fun getOrCreateDeviceId(): String {
val prefs = getSharedPreferences("device_id", MODE_PRIVATE)
return prefs.getString("device_id", "").let {
if (it.isNullOrBlank()) {
val newId = UUID.randomUUID().toString()
prefs.edit().putString("device_id", newId).apply()
newId
} else it
}
}
So sánh các định danh thiết bị
| Định danh | Độ bền vững | Quyền cần | Ghi chú |
|---|---|---|---|
| IMEI | Cực kỳ bền (đời thiết bị) | READ_PRIVILEGED_PHONE_STATE (system only) | Chỉ system app |
| Firebase Installation ID | Bền (trong vòng đời app install) | Không (Internet) | Khuyến nghị cho app hiện đại |
| Advertising ID | User có thể reset | Không (Play Services) | Dùng cho ads/analytics |
| Android ID (SSAID) | Reset sau factory reset | Không | Khác nhau per profile/user |
| Custom UUID (SharedPrefs) | Reset khi clear data/uninstall | Không | Đơn giản, tự kiểm soát |
Lưu ý quan trọng
- Không hardcode IMEI vào logic business quan trọng (license, tracking) - sẽ fail trên Android 10+.
- Dùng **Firebase Installation ID** hoặc **Custom UUID** cho việc identify device/app install.
- Nếu app là **Device Owner** (MDM, kiosk), có thể xin quyền
READ_PRIVILEGED_PHONE_STATE. - Tuân thủ **Google Play Policy**: Không dùng identifier để tracking cross-app mà không có consent.