⚠️ สำคัญ: การเปลี่ยนแปลงนโยบาย Privacy (Android 10+)
ตั้งแต่ Android 10 (API 29) Google จำกัดการเข้าถึง Hardware Identifier (IMEI, Serial Number, MAC Address) อย่างเครดง เพื่อความเป็นส่วนตัว:
- ต้องมี Permission
READ_PRIVILEGED_PHONE_STATE (System App เท่านั้น) - App ธรรมดา ไม่สามารถอ่าน IMEI ได้แล้ว
- ใช้ Alternatives แทน (ดูด้านล่าง)
รหัสตัวตนตัวเลือก (Alternatives)
| Identifier | API | ความคงที่ | Permission | ใช้ได้ Android 10+ |
|---|---|---|---|---|
| SSAID (Android ID) | Settings.Secure.ANDROID_ID | ต่ออุปกรณ์ (Reset เมื่อ Factory Reset) | ไม่ต้อง Permission | ✅ |
| Advertising ID (GAID) | AdvertisingIdClient | ต่อ User (Reset ได้ใน Settings) | com.google.android.gms.permission.AD_ID (Play Services) | ✅ |
| Instance ID / Firebase Installation ID | FirebaseInstallations | ต่อ App Install | ไม่ต้อง Permission | ✅ |
| UUID Random (App-specific) | Generate เอง + เก็บ SharedPreferences | ต่อ App Install (ลบ App = หาย) | ไม่ต้อง Permission | ✅ |
| IMEI/MEID | TelephonyManager.getImei() | Hardware (ถาวร) | READ_PRIVILEGED_PHONE_STATE | ❌ (App ธรรมดา) |
| Serial Number | Build.getSerial() | Hardware | READ_PRIVILEGED_PHONE_STATE | ❌ |
วิธีที่แนะนำ: ใช้ Android ID (SSAID)
import android.content.Context;
import android.provider.Settings.Secure;
public class DeviceIdHelper {
public static String getAndroidId(Context context) {
return Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
}
}
// ใช้งาน
String androidId = DeviceIdHelper.getAndroidId(this);
// ผลลัพธ์: "a1b2c3d4e5f6g7h8" (64-bit hex string)
> ⚠️ **ข้อควรระวัง Android ID:**
> - เปลี่ยนเมื่อ Factory Reset
> - อาจซ้ำกันได้บนอุปกรณ์บางรุ่น (Bug รุ่นเก่า)
> - ไม่ควรใช้เป็น Primary Key สำหรับ Server Auth เดียว
วิธีที่ 2: Advertising ID (GAID) - สำหรับ Ads/Analytics
// build.gradle
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
// ใช้งาน (ต้องรันบน Background Thread)
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import java.io.IOException;
public class AdIdHelper {
public static void getAdvertisingId(Context context, OnAdIdListener listener) {
new Thread(() -> {
try {
AdvertisingIdClient.Info adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context);
String adId = adInfo.getId();
boolean limitAdTracking = adInfo.isLimitAdTrackingEnabled();
listener.onAdIdReceived(adId, limitAdTracking);
} catch (GooglePlayServicesNotAvailableException |
GooglePlayServicesRepairableException | IOException e) {
listener.onError(e);
}
}).start();
}
public interface OnAdIdListener {
void onAdIdReceived(String adId, boolean limitAdTracking);
void onError(Exception e);
}
}
วิธีที่ 3: Firebase Installation ID (FID) - สำหรับ Firebase Apps
// build.gradle
implementation 'com.google.firebase:firebase-installations:17.2.0'
// ใช้งาน
FirebaseInstallations.getInstance().getId()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
String fid = task.getResult();
// ใช้ FID เป็น Device/App Identifier
}
});
วิธีที่ 4: สร้าง UUID เอง (App-specific ID) - แนะนำที่สุดสำหรับ General Use
import android.content.Context;
import android.content.SharedPreferences;
import java.util.UUID;
public class AppDeviceId {
private static final String PREFS_NAME = "device_id_prefs";
private static final String KEY_DEVICE_ID = "device_id";
public static String getDeviceId(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String deviceId = prefs.getString(KEY_DEVICE_ID, null);
if (deviceId == null) {
deviceId = UUID.randomUUID().toString();
prefs.edit().putString(KEY_DEVICE_ID, deviceId).apply();
}
return deviceId;
}
public static void resetDeviceId(Context context) {
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit().remove(KEY_DEVICE_ID).apply();
}
}
Legacy Code: TelephonyManager (Android 9 และก่อน / System Apps)
เก็บไว้เพื่อ Maintenance Legacy Code เท่านั้น
Permission ใน Manifest
Java Code (Deprecated API)
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.telephony.TelephonyManager;
import androidx.core.app.ActivityCompat;
public class LegacyDeviceId {
@SuppressLint("MissingPermission")
public static String getImei(Context context) {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
// Check Permission (Runtime Permission สำหรับ Android 6-9)
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
return null; // หรือ Request Permission
}
// API 26+ (Android 8.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return telephonyManager.getImei(); // Slot 1
// telephonyManager.getImei(0); // Slot 1
// telephonyManager.getImei(1); // Slot 2 (Dual SIM)
} else {
// Deprecated ใน API 26
return telephonyManager.getDeviceId();
}
}
}
Best Practices สำหรับ Device Identification
- ใช้ App-specific UUID สำหรับการระบุ User/Device ใน Backend ของตัวเอง (Simple, Reliable, Privacy-friendly)
- ใช้ Android ID (SSAID) สำหรับ Cross-app Identification (Anti-fraud, Analytics)
- ใช้ Advertising ID (GAID) สำหรับ Ads/Marketing เท่านั้น (User สามารถ Reset/Opt-out ได้)
- ใช้ Firebase Installation ID สำหรับ Firebase Ecosystem (FCM, Analytics, Crashlytics)
- ไม่ควร Hardcode IMEI/Serial ใน Logic ใหม่ (เข้าถึงไม่ได้ใน Android 10+)
- Respect User Privacy: ระบุใน Privacy Policy ว่าเก็บ Identifier อะไร
สรุป
| Use Case | Recommended Identifier |
|---|---|
| User/Device ID สำหรับ Backend ของตัวเอง | App-specific UUID (generate + store) |
| Cross-app Tracking / Fraud Detection | Android ID (SSAID) |
| Advertising / Marketing | Advertising ID (GAID) |
| Firebase Services (FCM, Analytics) | Firebase Installation ID (FID) |
| Legacy System App (Carrier/OEM) | IMEI (ต้อง Privileged Permission) |