Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách lấy số IMEI thiết bị Android (yêu cầu quyền READ_PHONE_STATE)

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ữngQuyền cầnGhi chú
IMEICực kỳ bền (đời thiết bị)READ_PRIVILEGED_PHONE_STATE (system only)Chỉ system app
Firebase Installation IDBền (trong vòng đời app install)Không (Internet)Khuyến nghị cho app hiện đại
Advertising IDUser có thể resetKhông (Play Services)Dùng cho ads/analytics
Android ID (SSAID)Reset sau factory resetKhôngKhác nhau per profile/user
Custom UUID (SharedPrefs)Reset khi clear data/uninstallKhô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.