มีรหัสอุปกรณ์ Android ที่ไม่ซ้ำกันหรือไม่


2751

อุปกรณ์ Android มีรหัสเฉพาะหรือไม่ถ้าอย่างนั้นวิธีง่ายๆในการเข้าถึงโดยใช้ Java คืออะไร?


38
หากคุณใช้ANDROID_IDโปรดอ่านคำตอบนี้และข้อผิดพลาดนี้
Dheeraj Vepakomma

หากคุณต้องการโซลูชันในปี 2020 คุณควรอ่านตัวระบุ Android ในปี 2020
Nikita Kurtin

คำตอบ:


2024

Settings.Secure#ANDROID_IDส่งคืน ID Android เป็นค่าเฉพาะสำหรับผู้ใช้สตริง hex 64 บิตแต่ละราย

import android.provider.Settings.Secure;

private String android_id = Secure.getString(getContext().getContentResolver(),
                                                        Secure.ANDROID_ID); 

476
บางครั้งมันเป็นโมฆะเป็นเอกสารว่า "สามารถเปลี่ยนแปลงได้เมื่อรีเซ็ตเป็นค่าจากโรงงาน" ใช้ความเสี่ยงของคุณเองและสามารถเปลี่ยนแปลงได้อย่างง่ายดายบนโทรศัพท์ที่รูท
Seva Alekseyev


18
ฉันคิดว่าเราต้องระวังเกี่ยวกับการใช้ ANDROID_ID ในแฮชในคำตอบแรกเพราะอาจไม่ได้ตั้งค่าเมื่อแอพทำงานครั้งแรกอาจถูกตั้งค่าในภายหลังหรืออาจเปลี่ยนแปลงตามทฤษฎีดังนั้น ID ที่ไม่ซ้ำกันอาจเปลี่ยน

46
ระวังข้อ จำกัด ขนาดใหญ่ด้วยโซลูชันนี้: android-developers.blogspot.com/2011/03/…
emmby

35
ANDROID_ID ไม่ระบุอุปกรณ์อีกต่อไปโดยเฉพาะ (จาก 4.2): stackoverflow.com/a/13465373/150016
Tom

1145

UPDATE : สำหรับ Android เวอร์ชันล่าสุดปัญหามากมายที่ANDROID_IDได้รับการแก้ไขแล้วและฉันเชื่อว่าวิธีการนี้ไม่จำเป็นอีกต่อไป โปรดดูที่คำตอบแอนโทนี

การเปิดเผยข้อมูลเต็มรูปแบบ: app ของฉันใช้ด้านล่างวิธีการเดิม แต่ไม่ใช้วิธีนี้และตอนนี้เราใช้วิธีการที่ระบุไว้ในบล็อกของนักพัฒนา Androidรายการที่emmby คำตอบของการเชื่อมโยงไป (กล่าวคือการสร้างและการประหยัดUUID#randomUUID())


มีคำตอบมากมายสำหรับคำถามนี้ซึ่งส่วนใหญ่จะใช้เวลา "บางส่วน" เท่านั้นและน่าเสียดายที่ยังไม่ดีพอ

จากการทดสอบอุปกรณ์ของฉัน (โทรศัพท์ทุกเครื่องอย่างน้อยหนึ่งอันไม่ได้เปิดใช้งาน):

  1. อุปกรณ์ทั้งหมดที่ทดสอบได้คืนค่ามา TelephonyManager.getDeviceId()
  2. อุปกรณ์ GSM ทั้งหมด (ทดสอบทั้งหมดด้วยซิม) คืนค่าเป็น TelephonyManager.getSimSerialNumber()
  3. อุปกรณ์ CDMA ทั้งหมดส่งคืนค่า null สำหรับgetSimSerialNumber()(ตามที่คาดไว้)
  4. อุปกรณ์ทั้งหมดที่เพิ่มบัญชี Google ได้คืนค่าแล้ว ANDROID_ID
  5. อุปกรณ์ CDMA ทั้งหมดส่งคืนค่าเดียวกัน (หรือสืบทอดมาจากค่าเดียวกัน) สำหรับทั้งสองANDROID_IDและTelephonyManager.getDeviceId()-ตราบใดที่มีการเพิ่มบัญชี Google ในระหว่างการตั้งค่า
  6. ฉันยังไม่มีโอกาสทดสอบอุปกรณ์ GSM ที่ไม่มีซิมอุปกรณ์ GSM ที่ไม่มีบัญชี Google หรืออุปกรณ์ใด ๆ ในโหมดเครื่องบิน

ดังนั้นหากคุณต้องการบางสิ่งบางอย่างที่ไม่ซ้ำกับอุปกรณ์นั้นTM.getDeviceId() ควรจะเพียงพอ เห็นได้ชัดว่าผู้ใช้บางคนมีความหวาดระแวงมากกว่าผู้อื่นดังนั้นจึงอาจมีประโยชน์ในการแฮช 1 ตัวหรือมากกว่าของตัวระบุเหล่านี้เพื่อให้สตริงยังคงเป็นเอกลักษณ์เฉพาะกับอุปกรณ์ แต่ไม่ได้ระบุอุปกรณ์จริงของผู้ใช้อย่างชัดเจน ตัวอย่างเช่นการใช้String.hashCode()รวมกับ UUID:

final TelephonyManager tm = (TelephonyManager) getBaseContext().getSystemService(Context.TELEPHONY_SERVICE);

final String tmDevice, tmSerial, androidId;
tmDevice = "" + tm.getDeviceId();
tmSerial = "" + tm.getSimSerialNumber();
androidId = "" + android.provider.Settings.Secure.getString(getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);

UUID deviceUuid = new UUID(androidId.hashCode(), ((long)tmDevice.hashCode() << 32) | tmSerial.hashCode());
String deviceId = deviceUuid.toString();

อาจส่งผลให้บางสิ่งเช่น: 00000000-54b3-e7c7-0000-000046bffd97

มันทำงานได้ดีพอสำหรับฉัน

ตามที่ Richard กล่าวไว้ด้านล่างอย่าลืมว่าคุณต้องได้รับอนุญาตให้อ่านTelephonyManagerคุณสมบัติดังนั้นเพิ่มสิ่งนี้ลงในรายการของคุณ:

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

นำเข้า libs

import android.content.Context;
import android.telephony.TelephonyManager;
import android.view.View;

151
ID จากโทรศัพท์จะไม่ปรากฏบนอุปกรณ์แท็บเล็ตใช่มั้ย
Seva Alekseyev

22
ดังนั้นทำไมฉันบอกว่าส่วนใหญ่จะไม่ทำงานตลอดเวลา :) ฉันยังไม่เห็นคำตอบสำหรับคำถามนี้ที่เชื่อถือได้สำหรับอุปกรณ์ทั้งหมดทุกประเภทอุปกรณ์และการกำหนดค่าฮาร์ดแวร์ทั้งหมด นั่นเป็นเหตุผลที่คำถามนี้อยู่ที่นี่เพื่อเริ่มต้นด้วย ค่อนข้างชัดเจนว่าไม่มีวิธีแก้ปัญหาแบบ end-all-be-all ผู้ผลิตอุปกรณ์แต่ละรายอาจมีหมายเลขซีเรียลของอุปกรณ์ แต่ไม่ได้ให้เราใช้และไม่เป็นข้อกำหนด ดังนั้นเราจึงเหลือสิ่งที่มีอยู่สำหรับเรา
Joe

31
ตัวอย่างโค้ดใช้งานได้ดี อย่าลืมเพิ่มลง<uses-permission android:name="android.permission.READ_PHONE_STATE" />ในไฟล์รายการ หากการจัดเก็บในฐานข้อมูลสตริงที่ส่งคืนมีความยาว 36 ตัวอักษร
ริชาร์ด

10
ระวังข้อ จำกัด ขนาดใหญ่ด้วยโซลูชันนี้: android-developers.blogspot.com/2011/03/…
emmby

18
@ ซอฟท์: ฉันเชื่อว่าสิ่งที่คุณอ้างถึงคือบล็อกนักพัฒนา Android ที่ emmby เชื่อมโยงไปแล้วซึ่งจะอธิบายสิ่งที่คุณพยายามจะพูดดังนั้นบางทีคุณควรจะยกระดับความคิดเห็นของเขาแทน ทั้งสองวิธีดังที่ emmby กล่าวถึงในคำตอบของเขายังคงมีปัญหาแม้กับข้อมูลบล็อก คำถามจะถามตัวระบุอุปกรณ์ที่ไม่ซ้ำกัน(ไม่ใช่ตัวระบุการติดตั้ง) ดังนั้นฉันจึงไม่เห็นด้วยกับคำสั่งของคุณ บล็อกกำลังตั้งสมมติฐานว่าสิ่งที่คุณต้องการไม่จำเป็นต้องติดตามอุปกรณ์ในขณะที่คำถามจะถามเพียงว่า ฉันเห็นด้วยกับบล็อกเป็นอย่างอื่น
Joe

438

อัปเดตครั้งล่าสุด: 6/2/15


หลังจากอ่านโพสต์ Stack Overflow ทุกครั้งเกี่ยวกับการสร้าง ID ที่ไม่ซ้ำกันบล็อกของนักพัฒนาซอฟต์แวร์ของ Google และ Android ฉันรู้สึกราวกับว่า 'Pseudo ID' เป็นตัวเลือกที่ดีที่สุด

ปัญหาหลัก: ฮาร์ดแวร์กับซอฟต์แวร์

ฮาร์ดแวร์

  • ผู้ใช้สามารถเปลี่ยนฮาร์ดแวร์แท็บเล็ต Android หรือโทรศัพท์ดังนั้นรหัสที่ไม่ซ้ำกันซึ่งอ้างอิงตามฮาร์ดแวร์จึงไม่ใช่แนวคิดที่ดีสำหรับผู้ใช้ที่ติดตาม
  • สำหรับการติดตามฮาร์ดแวร์นี่เป็นความคิดที่ยอดเยี่ยม

ซอฟต์แวร์

  • ผู้ใช้สามารถล้าง / เปลี่ยน ROM ได้หากถูกรูท
  • คุณสามารถติดตามผู้ใช้ข้ามแพลตฟอร์ม (iOS, Android, Windows และ Web)
  • ความต้องการที่ดีที่สุดในการติดตามผู้ใช้รายบุคคลด้วยความยินยอมของพวกเขาคือเพียงให้พวกเขาเข้าสู่ระบบ (ทำให้สิ่งนี้ราบรื่นโดยใช้ OAuth)

ความล้มเหลวโดยรวมกับ Android

- รับประกันความเป็นเอกลักษณ์ (รวมถึงอุปกรณ์ที่รูท) สำหรับ API> = 9/10 (99.5% ของอุปกรณ์ Android)

- ไม่มีสิทธิ์พิเศษ

รหัส Psuedo:

if API >= 9/10: (99.5% of devices)

return unique ID containing serial id (rooted devices may be different)

else

return the unique ID of build information (may overlap data - API < 9)

ขอบคุณ @stansult สำหรับการโพสต์ตัวเลือกทั้งหมดของเรา (ในคำถาม Stack Overflow)

รายการตัวเลือก - เหตุผลทำไม / ทำไมไม่ใช้:

  • อีเมลผู้ใช้ - ซอฟต์แวร์

  • หมายเลขโทรศัพท์ของผู้ใช้ - ซอฟต์แวร์

    • ผู้ใช้สามารถเปลี่ยนหมายเลขโทรศัพท์ - ไม่น่าเป็นไปได้สูง
    • <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  • IMEI - ฮาร์ดแวร์ (เฉพาะโทรศัพท์ที่ต้องการandroid.permission.READ_PHONE_STATE)

    • ผู้ใช้ส่วนใหญ่เกลียดความจริงที่ว่า "การโทร" ในการอนุญาต ผู้ใช้บางรายให้คะแนนที่ไม่ดีเพราะพวกเขาเชื่อว่าคุณเพียงแค่ขโมยข้อมูลส่วนบุคคลของพวกเขาเมื่อคุณต้องการเพียงแค่ติดตั้งอุปกรณ์ติดตาม เป็นที่ชัดเจนว่าคุณกำลังรวบรวมข้อมูล
    • <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  • Android ID - ฮาร์ดแวร์ (อาจเป็นโมฆะสามารถเปลี่ยนได้เมื่อรีเซ็ตจากโรงงานสามารถแก้ไขได้บนอุปกรณ์ที่รูท)

    • เนื่องจากอาจเป็น 'โมฆะ' เราสามารถตรวจสอบ 'โมฆะ' และเปลี่ยนค่าได้ แต่หมายความว่าจะไม่ซ้ำกันอีกต่อไป
    • หากคุณมีผู้ใช้ที่มีอุปกรณ์รีเซ็ตจากโรงงานค่าอาจมีการเปลี่ยนแปลงหรือเปลี่ยนแปลงบนอุปกรณ์ที่ถูกรูทดังนั้นอาจมีรายการที่ซ้ำกันหากคุณกำลังติดตามการติดตั้งของผู้ใช้
  • ที่อยู่ WLAN MAC - ฮาร์ดแวร์ (ต้องการandroid.permission.ACCESS_WIFI_STATE)

    • นี่อาจเป็นตัวเลือกที่ดีที่สุดอันดับสอง แต่คุณยังคงรวบรวมและจัดเก็บตัวระบุเฉพาะที่มาจากผู้ใช้โดยตรง ชัดเจนว่าคุณกำลังรวบรวมข้อมูล
    • <uses-permission android:name="android.permission.ACCESS_WIFI_STATE "/>
  • ที่อยู่ MAC Bluetooth - ฮาร์ดแวร์ (อุปกรณ์ที่มีบลูทู ธ ต้องการandroid.permission.BLUETOOTH)

    • แอปพลิเคชันส่วนใหญ่ในตลาดไม่ได้ใช้บลูทู ธ ดังนั้นหากแอปพลิเคชันของคุณไม่ได้ใช้บลูทู ธ และคุณรวมสิ่งนี้ไว้ผู้ใช้อาจสงสัย
    • <uses-permission android:name="android.permission.BLUETOOTH "/>
  • Pseudo-Unique ID - ซอฟต์แวร์ (สำหรับอุปกรณ์ Android ทั้งหมด)

    • เป็นไปได้มากอาจมีการชนกัน - ดูวิธีการของฉันโพสต์ด้านล่าง!
    • วิธีนี้ช่วยให้คุณมี ID 'เกือบจะไม่ซ้ำกัน' จากผู้ใช้โดยไม่ต้องทำสิ่งใดที่เป็นส่วนตัว คุณสามารถสร้าง ID นิรนามของคุณเองได้จากข้อมูลอุปกรณ์

ฉันรู้ว่าไม่มีวิธี 'สมบูรณ์แบบ' ในการรับ ID เฉพาะโดยไม่ต้องใช้การอนุญาต อย่างไรก็ตามบางครั้งเราจำเป็นต้องติดตามการติดตั้งอุปกรณ์เท่านั้น เมื่อมันมาถึงการสร้าง ID ที่ไม่ซ้ำกันเราสามารถสร้าง 'pseudo unique id' ตามข้อมูลที่ Android API ให้กับเราโดยไม่ต้องใช้สิทธิ์พิเศษ ด้วยวิธีนี้เราสามารถแสดงความเคารพต่อผู้ใช้และพยายามเสนอประสบการณ์ผู้ใช้ที่ดีเช่นกัน

ด้วยรหัสปลอมที่ไม่ซ้ำใครคุณจะพบความจริงว่าอาจมีการซ้ำซ้อนตามความจริงที่ว่ามีอุปกรณ์ที่คล้ายกัน คุณสามารถปรับแต่งวิธีการรวมเพื่อให้มีความพิเศษมากขึ้น อย่างไรก็ตามนักพัฒนาซอฟต์แวร์บางรายจำเป็นต้องติดตามการติดตั้งอุปกรณ์และสิ่งนี้จะทำการหลอกลวงหรือประสิทธิภาพตามอุปกรณ์ที่คล้ายกัน

API> = 9:

หากอุปกรณ์ Android ของพวกเขาคือ API 9 หรือสูงกว่าสิ่งนี้รับประกันว่าจะไม่ซ้ำกันเนื่องจากฟิลด์ 'Build.SERIAL'

โปรดจำไว้ว่าคุณจะพลาดเพียง 0.5% ของผู้ใช้ที่มี API <9เท่านั้น ดังนั้นคุณสามารถมุ่งเน้นที่เหลือ: นี่คือ 99.5% ของผู้ใช้!

API <9:

หากอุปกรณ์ Android ของผู้ใช้ต่ำกว่า API 9; หวังว่าพวกเขายังไม่ได้รีเซ็ตเป็นค่าเริ่มต้นจากโรงงานและ 'Secure.ANDROID_ID' ของพวกเขาจะถูกเก็บไว้หรือไม่เป็น 'โมฆะ' (ดูhttp://developer.android.com/about/dashboards/index.html )

หากล้มเหลวทั้งหมด:

หากทุกอย่างล้มเหลวหากผู้ใช้มีต่ำกว่า API 9 (ต่ำกว่า Gingerbread) ได้รีเซ็ตอุปกรณ์หรือ 'Secure.ANDROID_ID' ส่งคืน 'null' เพียงแค่ ID ที่ส่งคืนจะถูกยึดตามข้อมูลอุปกรณ์ Android ของพวกเขาเพียงผู้เดียว นี่คือที่ที่การชนสามารถเกิดขึ้นได้

การเปลี่ยนแปลง:

  • นำ 'Android.SECURE_ID' ออกเนื่องจากการรีเซ็ตจากโรงงานอาจทำให้ค่าเปลี่ยนไป
  • แก้ไขรหัสเพื่อเปลี่ยนแปลงบน API
  • เปลี่ยนหลอก

โปรดดูวิธีการด้านล่าง:

/**
 * Return pseudo unique ID
 * @return ID
 */
public static String getUniquePsuedoID() {
    // If all else fails, if the user does have lower than API 9 (lower
    // than Gingerbread), has reset their device or 'Secure.ANDROID_ID'
    // returns 'null', then simply the ID returned will be solely based
    // off their Android device information. This is where the collisions
    // can happen.
    // Thanks http://www.pocketmagic.net/?p=1662!
    // Try not to use DISPLAY, HOST or ID - these items could change.
    // If there are collisions, there will be overlapping data
    String m_szDevIDShort = "35" + (Build.BOARD.length() % 10) + (Build.BRAND.length() % 10) + (Build.CPU_ABI.length() % 10) + (Build.DEVICE.length() % 10) + (Build.MANUFACTURER.length() % 10) + (Build.MODEL.length() % 10) + (Build.PRODUCT.length() % 10);

    // Thanks to @Roman SL!
    // https://stackoverflow.com/a/4789483/950427
    // Only devices with API >= 9 have android.os.Build.SERIAL
    // http://developer.android.com/reference/android/os/Build.html#SERIAL
    // If a user upgrades software or roots their device, there will be a duplicate entry
    String serial = null;
    try {
        serial = android.os.Build.class.getField("SERIAL").get(null).toString();

        // Go ahead and return the serial for api => 9
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    } catch (Exception exception) {
        // String needs to be initialized
        serial = "serial"; // some value
    }

    // Thanks @Joe!
    // https://stackoverflow.com/a/2853253/950427
    // Finally, combine the values we have found by using the UUID class to create a unique identifier
    return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}

ใหม่ (สำหรับแอพที่มีโฆษณาและบริการ Google Play):

จากคอนโซลของนักพัฒนาซอฟต์แวร์ Google Play:

ตั้งแต่วันที่ 1 สิงหาคม 2014 นโยบายโปรแกรมนักพัฒนาซอฟต์แวร์ Google Play ต้องมีการอัปโหลดและอัปเดตแอปใหม่ทั้งหมดเพื่อใช้รหัสโฆษณาแทนตัวระบุถาวรอื่น ๆ เพื่อวัตถุประสงค์ในการโฆษณา เรียนรู้เพิ่มเติม

การใช้งาน :

สิทธิ์:

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

รหัส:

import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
import com.google.android.gms.common.GooglePlayServicesAvailabilityException;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import java.io.IOException;
...

// Do not call this function from the main thread. Otherwise, 
// an IllegalStateException will be thrown.
public void getIdThread() {

  Info adInfo = null;
  try {
    adInfo = AdvertisingIdClient.getAdvertisingIdInfo(mContext);

  } catch (IOException exception) {
    // Unrecoverable error connecting to Google Play services (e.g.,
    // the old version of the service doesn't support getting AdvertisingId).

  } catch (GooglePlayServicesAvailabilityException exception) {
    // Encountered a recoverable error connecting to Google Play services. 

  } catch (GooglePlayServicesNotAvailableException exception) {
    // Google Play services is not available entirely.
  }
  final String id = adInfo.getId();
  final boolean isLAT = adInfo.isLimitAdTrackingEnabled();
}

แหล่งที่มา / เอกสาร:

http://developer.android.com/google/play-services/id.html http://developer.android.com/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.html

สิ่งสำคัญ:

มีวัตถุประสงค์เพื่อให้รหัสโฆษณาแทนที่การใช้งานตัวระบุอื่น ๆ ที่มีอยู่เพื่อวัตถุประสงค์โฆษณาอย่างสมบูรณ์ (เช่นการใช้ ANDROID_ID ในการตั้งค่าการรักษาความปลอดภัย) เมื่อ Google Play Services พร้อมใช้งาน กรณีที่บริการ Google Play ไม่สามารถใช้งานได้จะถูกระบุโดย GooglePlayServicesNotAvailableException ที่ถูกโยนโดย getAdvertisingIdInfo ()

คำเตือนผู้ใช้สามารถรีเซ็ต:

http://en.kioskea.net/faq/34732-android-reset-your-advertising-id

ฉันพยายามอ้างอิงทุกลิงก์ที่ฉันนำข้อมูลมา หากคุณขาดหายไปและจำเป็นต้องรวมโปรดแสดงความคิดเห็น!

Google Player Services InstanceID

https://developers.google.com/instance-id/


แต่Buildคลาสจะไม่เปลี่ยนแปลงเมื่ออัปเดตระบบปฏิบัติการหรือไม่ โดยเฉพาะอย่างยิ่งถ้า API ได้รับการปรับปรุง? ถ้าเป็นเช่นนั้นคุณจะรับประกันได้อย่างไรว่าสิ่งนี้มีลักษณะเฉพาะ (พูดเกี่ยวกับวิธีการที่คุณเขียน)
LuckyMe

2
ฉันใช้วิธีการของคุณในแอพเพื่อส่งความคิดเห็น ฉันมีข่าวร้าย น่าเสียดายที่ PsuedoID นั้นไม่เหมือนกันทั้งหมด เซิร์ฟเวอร์ของฉันบันทึกมากกว่า 100 สำหรับ 5 ID และมากกว่า 30 สำหรับเกือบ 30 ID ID ที่ซ้ำกันมากที่สุดคือ 'ffffffff-fc8f-6093-ffff-ffffd8' (บันทึก 159) และ 'ffffffff-fe99-b334-ffff-ffffef' (154 เวลา) ขึ้นอยู่กับเวลาและความคิดเห็นที่เห็นได้ชัดว่ามีคนที่แตกต่างกัน ระเบียนทั้งหมดจนถึงขณะนี้คือ 10,000 โปรดแจ้งให้เราทราบว่าทำไมสิ่งนี้จึงเกิดขึ้น รถถัง
hojjat reyhane

1
ฉันเขียนสิ่งนี้มากกว่า 1.5 ปีมาแล้ว ฉันไม่แน่ใจว่าทำไมมันไม่ซ้ำสำหรับคุณ คุณสามารถลองใช้รหัสโฆษณา ถ้าไม่คุณสามารถหาวิธีแก้ปัญหาของคุณเองได้
Jared Burrows

2
sorta .. ฉันขอขอบคุณจริงๆถ้าคุณผ่านคำถามและให้ความคิดของคุณเกี่ยวกับเรื่องนี้
Durai Amuthan

1
@ user1587329 ขอบคุณ ฉันพยายามที่จะทำให้เรื่องนี้ทันสมัยสำหรับทุกคน คำถามนี้เป็นเรื่องยากเมื่อมันมาถึงฮาร์ดแวร์ vs ซอฟต์แวร์และข้ามแพลตฟอร์ม
Jared Burrows

340

ในฐานะที่เป็นเดฟเวบบ์กล่าวถึงบล็อกนักพัฒนา Android มีบทความที่ครอบคลุมเรื่องนี้ โซลูชันที่พวกเขาต้องการคือการติดตามการติดตั้งแอพมากกว่าอุปกรณ์และจะใช้งานได้ดีสำหรับกรณีส่วนใหญ่ โพสต์บล็อกจะแสดงรหัสที่จำเป็นสำหรับการทำงานและฉันขอแนะนำให้คุณลองดู

อย่างไรก็ตามโพสต์บล็อกยังคงมีอยู่เพื่อหารือเกี่ยวกับการแก้ปัญหาหากคุณต้องการตัวระบุอุปกรณ์แทนที่จะเป็นตัวระบุการติดตั้งแอป ฉันได้พูดคุยกับใครบางคนใน Google เพื่อขอความกระจ่างเพิ่มเติมเกี่ยวกับบางรายการในเหตุการณ์ที่คุณต้องทำ นี่คือสิ่งที่ฉันค้นพบเกี่ยวกับตัวระบุอุปกรณ์ที่ไม่ได้กล่าวถึงในบล็อกโพสต์ดังกล่าว:

  • ANDROID_ID เป็นตัวระบุอุปกรณ์ที่ต้องการ ANDROID_ID เชื่อถือได้อย่างสมบูรณ์ในเวอร์ชั่น Android <= 2.1 หรือ> = 2.3 2.2 เท่านั้นมีปัญหาที่กล่าวถึงในโพสต์
  • อุปกรณ์จำนวนมากจากผู้ผลิตหลายรายได้รับผลกระทบจากข้อผิดพลาด ANDROID_ID ใน 2.2
  • เท่าที่ฉันสามารถระบุได้อุปกรณ์ที่ได้รับผลกระทบทั้งหมดจะมีANDROID_ID เดียวกันซึ่งก็คือ9774d56d682e549c 9774d56d682e549cซึ่งเป็นรหัสอุปกรณ์เดียวกันที่รายงานโดยอีมูเลเตอร์ btw
  • Google เชื่อว่า OEM ได้แก้ไขปัญหาสำหรับอุปกรณ์ส่วนใหญ่หรือส่วนใหญ่แล้ว แต่ฉันสามารถตรวจสอบได้ว่าตั้งแต่ต้นเดือนเมษายน 2011 อย่างน้อยมันก็ค่อนข้างง่ายที่จะหาอุปกรณ์ที่มี ANDROID_ID ที่ใช้งานไม่ได้

ตามคำแนะนำของ Google ฉันใช้คลาสที่จะสร้าง UUID ที่ไม่ซ้ำกันสำหรับอุปกรณ์แต่ละเครื่องโดยใช้ ANDROID_ID เป็นเมล็ดพันธุ์ตามความเหมาะสมย้อนกลับไปที่ TelephonyManager.getDeviceId () ตามความจำเป็นและหากล้มเหลวหันไปใช้ UUID ที่สร้างขึ้นแบบสุ่ม ที่ยังคงมีอยู่ระหว่างการรีสตาร์ทแอป (แต่ไม่ใช่การติดตั้งแอปใหม่)

โปรดทราบว่าสำหรับอุปกรณ์ที่ต้องย้อนกลับไปที่ ID อุปกรณ์ ID ที่ไม่ซ้ำกันจะยังคงมีอยู่ในการรีเซ็ตจากโรงงาน นี่คือสิ่งที่ต้องระวัง หากคุณต้องการตรวจสอบให้แน่ใจว่าการรีเซ็ตเป็นค่าเริ่มต้นจากโรงงานจะรีเซ็ต ID ที่ไม่ซ้ำกันของคุณคุณอาจต้องการพิจารณาย้อนกลับไปที่ UUID สุ่มแทน ID อุปกรณ์

รหัสนี้ใช้สำหรับรหัสอุปกรณ์ไม่ใช่รหัสการติดตั้งแอป สำหรับสถานการณ์ส่วนใหญ่ ID การติดตั้งแอปอาจเป็นสิ่งที่คุณกำลังมองหา แต่ถ้าคุณต้องการรหัสอุปกรณ์รหัสต่อไปนี้อาจใช้ได้กับคุณ

import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;

import java.io.UnsupportedEncodingException;
import java.util.UUID;

public class DeviceUuidFactory {

    protected static final String PREFS_FILE = "device_id.xml";
    protected static final String PREFS_DEVICE_ID = "device_id";
    protected volatile static UUID uuid;

    public DeviceUuidFactory(Context context) {
        if (uuid == null) {
            synchronized (DeviceUuidFactory.class) {
                if (uuid == null) {
                    final SharedPreferences prefs = context
                            .getSharedPreferences(PREFS_FILE, 0);
                    final String id = prefs.getString(PREFS_DEVICE_ID, null);
                    if (id != null) {
                        // Use the ids previously computed and stored in the
                        // prefs file
                        uuid = UUID.fromString(id);
                    } else {
                        final String androidId = Secure.getString(
                            context.getContentResolver(), Secure.ANDROID_ID);
                        // Use the Android ID unless it's broken, in which case
                        // fallback on deviceId,
                        // unless it's not available, then fallback on a random
                        // number which we store to a prefs file
                        try {
                            if (!"9774d56d682e549c".equals(androidId)) {
                                uuid = UUID.nameUUIDFromBytes(androidId
                                        .getBytes("utf8"));
                            } else {
                                final String deviceId = (
                                    (TelephonyManager) context
                                    .getSystemService(Context.TELEPHONY_SERVICE))
                                    .getDeviceId();
                                uuid = deviceId != null ? UUID
                                    .nameUUIDFromBytes(deviceId
                                            .getBytes("utf8")) : UUID
                                    .randomUUID();
                            }
                        } catch (UnsupportedEncodingException e) {
                            throw new RuntimeException(e);
                        }
                        // Write the value out to the prefs file
                        prefs.edit()
                                .putString(PREFS_DEVICE_ID, uuid.toString())
                                .commit();
                    }
                }
            }
        }
    }

    /**
     * Returns a unique UUID for the current android device. As with all UUIDs,
     * this unique ID is "very highly likely" to be unique across all Android
     * devices. Much more so than ANDROID_ID is.
     * 
     * The UUID is generated by using ANDROID_ID as the base key if appropriate,
     * falling back on TelephonyManager.getDeviceID() if ANDROID_ID is known to
     * be incorrect, and finally falling back on a random UUID that's persisted
     * to SharedPreferences if getDeviceID() does not return a usable value.
     * 
     * In some rare circumstances, this ID may change. In particular, if the
     * device is factory reset a new device ID may be generated. In addition, if
     * a user upgrades their phone from certain buggy implementations of Android
     * 2.2 to a newer, non-buggy version of Android, the device ID may change.
     * Or, if a user uninstalls your app on a device that has neither a proper
     * Android ID nor a Device ID, this ID may change on reinstallation.
     * 
     * Note that if the code falls back on using TelephonyManager.getDeviceId(),
     * the resulting ID will NOT change after a factory reset. Something to be
     * aware of.
     * 
     * Works around a bug in Android 2.2 for many devices when using ANDROID_ID
     * directly.
     * 
     * @see http://code.google.com/p/android/issues/detail?id=10603
     * 
     * @return a UUID that may be used to uniquely identify your device for most
     *         purposes.
     */
    public UUID getDeviceUuid() {
        return uuid;
    }
}

6
คุณไม่ควร hashing รหัสต่าง ๆ เพื่อให้พวกเขามีขนาดเท่ากันหรือไม่ นอกจากนี้คุณควรแฮชรหัสอุปกรณ์เพื่อไม่ให้เปิดเผยข้อมูลส่วนตัวโดยไม่ตั้งใจ
Steve Pomeroy

2
คะแนนที่ดีสตีฟ ฉันอัปเดตรหัสเพื่อส่งคืน UUID เสมอ เพื่อให้แน่ใจว่า a) รหัสที่สร้างขึ้นนั้นมีขนาดเท่ากันเสมอและ b) android และรหัสอุปกรณ์จะถูกแฮชก่อนที่จะถูกส่งกลับเพื่อหลีกเลี่ยงการเปิดเผยข้อมูลส่วนบุคคลโดยไม่ตั้งใจ ฉันได้อัปเดตคำอธิบายเพื่อให้ทราบว่า ID อุปกรณ์จะคงอยู่ในการรีเซ็ตจากโรงงานและอาจไม่เป็นที่ต้องการสำหรับผู้ใช้บางราย
emmby

1
ฉันเชื่อว่าคุณไม่ถูกต้อง โซลูชันที่ต้องการคือการติดตามการติดตั้งไม่ใช่ตัวระบุอุปกรณ์ รหัสของคุณมีความยาวและซับซ้อนกว่าในโพสต์บล็อกเป็นอย่างมากและไม่ชัดเจนสำหรับฉันที่จะเพิ่มมูลค่าใด ๆ
ทิมเบรย์

7
จุดดีฉันได้อัปเดตความเห็นเพื่อขอแนะนำให้ผู้ใช้ใช้รหัสการติดตั้งแอปแทนรหัสอุปกรณ์ อย่างไรก็ตามฉันคิดว่าโซลูชันนี้ยังคงมีประโยชน์สำหรับผู้ที่ต้องการอุปกรณ์มากกว่า ID การติดตั้ง
emmby

8
ANDROID_ID สามารถเปลี่ยนได้จากการรีเซ็ตเป็นค่าเริ่มต้นจากโรงงานดังนั้นจึงไม่สามารถระบุอุปกรณ์ได้เช่นกัน
Samuel

180

นี่คือรหัสที่ Reto Meier ใช้ในการนำเสนอGoogle I / Oในปีนี้เพื่อรับรหัสเฉพาะสำหรับผู้ใช้:

private static String uniqueID = null;
private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";

public synchronized static String id(Context context) {
    if (uniqueID == null) {
        SharedPreferences sharedPrefs = context.getSharedPreferences(
                PREF_UNIQUE_ID, Context.MODE_PRIVATE);
        uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
        if (uniqueID == null) {
            uniqueID = UUID.randomUUID().toString();
            Editor editor = sharedPrefs.edit();
            editor.putString(PREF_UNIQUE_ID, uniqueID);
            editor.commit();
        }
    }
    return uniqueID;
}

หากคุณจับคู่สิ่งนี้ด้วยกลยุทธ์การสำรองข้อมูลเพื่อส่งการตั้งค่าไปยังคลาวด์ (อธิบายไว้ในการพูดคุยของ Reto คุณควรมี ID ที่เชื่อมโยงกับผู้ใช้และเกาะติดหลังจากที่อุปกรณ์ถูกเช็ดหรือเปลี่ยนไปแล้ว ในการวิเคราะห์ไปข้างหน้า (ในคำอื่น ๆ ฉันยังไม่ได้ทำบิตนั้น :)


ฉันใช้วิธีการของ @Lenn Dolling โดยมีการเพิ่มเวลาปัจจุบันต่อท้ายด้วยรหัสเฉพาะ แต่ดูเหมือนง่ายและเชื่อถือได้มากขึ้น ขอขอบคุณ Reto Meier และ Antony Nolan
GökhanBarış Aker

มันยอดเยี่ยม แต่สิ่งที่เกี่ยวกับอุปกรณ์รูท? พวกเขาสามารถเข้าถึงสิ่งนี้และเปลี่ยน uid เป็นอันที่แตกต่างได้อย่างง่ายดาย
tasomaniac

3
ตัวเลือกที่ยอดเยี่ยมหากคุณไม่ต้องการ ID ที่ไม่ซ้ำเพื่อคงอยู่หลังจากถอนการติดตั้งและติดตั้งใหม่ (เช่นกิจกรรมส่งเสริมการขาย / เกมที่คุณมีโอกาสชนะสามครั้งระยะเวลา)
Kyle Clegg

2
งานนำเสนอ Meier ขึ้นอยู่กับการใช้ตัวจัดการการสำรองข้อมูลของ Android ซึ่งจะขึ้นอยู่กับผู้ใช้ที่เลือกที่จะเปิดคุณสมบัตินั้น นั่นเป็นสิ่งที่ดีสำหรับการตั้งค่าผู้ใช้แอป (การใช้งานของไมเออร์) เพราะหากผู้ใช้ไม่ได้เลือกตัวเลือกนั้นเธอก็จะไม่ได้รับการสำรองข้อมูลเหล่านั้น อย่างไรก็ตามคำถามเดิมเกี่ยวกับการสร้าง ID เฉพาะสำหรับอุปกรณ์และ ID นี้ถูกสร้างขึ้นสำหรับแต่ละแอพและไม่ได้ทำการติดตั้งเพียงอย่างเดียวต่ออุปกรณ์และเนื่องจากมันขึ้นอยู่กับผู้ใช้ที่เลือกตัวเลือกการสำรองข้อมูล ค่ากำหนด (เช่นสำหรับการทดลองใช้แบบ จำกัด เวลา) มี จำกัด
Carl

12
สิ่งนี้จะไม่ทำงานในการถอนการติดตั้งหรือล้างข้อมูล
John Shelley

106

นอกจากนี้คุณอาจพิจารณาที่อยู่ MAC ของอะแดปเตอร์ Wi-Fi เรียกคืนดังนั้น:

WifiManager wm = (WifiManager)Ctxt.getSystemService(Context.WIFI_SERVICE);
return wm.getConnectionInfo().getMacAddress();

ต้องได้รับอนุญาตandroid.permission.ACCESS_WIFI_STATEในรายการ

รายงานว่าพร้อมใช้งานแม้ว่าจะไม่ได้เชื่อมต่อ Wi-Fi หากโจจากคำตอบข้างต้นให้ลองใช้อุปกรณ์นี้ของเขาดูว่าจะดีหรือไม่

ในบางอุปกรณ์จะไม่สามารถใช้งานได้เมื่อปิด Wi-Fi

หมายเหตุ:จาก Android 6.x จะส่งคืนที่อยู่ mac ปลอมที่สอดคล้องกัน:02:00:00:00:00:00


8
สิ่งนี้ต้องการandroid.permission.ACCESS_WIFI_STATE
ohhorob

5
ฉันคิดว่าคุณจะพบว่ามันไม่สามารถใช้งานได้เมื่อปิด WiFi ในอุปกรณ์ Android เกือบทั้งหมด การปิด WiFi จะลบอุปกรณ์ในระดับเคอร์เนล
chrisdowney

12
@Sanandrea - มาเผชิญหน้ากันบนอุปกรณ์ที่รูททุกอย่างสามารถถูกหลอกได้
ocodo

5
การเข้าถึงที่อยู่ WiFi MAC ได้ถูกบล็อคใน Android M: stackoverflow.com/questions/31329733//
breez

6
จาก Android 6.x จะส่งคืนที่อยู่ mac ปลอมที่สอดคล้องกัน:02:00:00:00:00:00
Behrouz.M

87

มีข้อมูลที่เป็นประโยชน์ค่อนข้างเป็นที่นี่

ครอบคลุมห้าประเภท ID ที่แตกต่างกัน:

  1. IMEI (สำหรับอุปกรณ์ Android ที่ใช้โทรศัพท์ต้องมีandroid.permission.READ_PHONE_STATE)
  2. Pseudo-Unique ID (สำหรับอุปกรณ์ Android ทั้งหมด)
  3. Android ID (อาจเป็นโมฆะสามารถเปลี่ยนได้เมื่อรีเซ็ตเป็นโรงงานสามารถเปลี่ยนแปลงได้บนโทรศัพท์ที่รูท)
  4. สตริงที่อยู่ WLAN MAC (ต้องการandroid.permission.ACCESS_WIFI_STATE)
  5. BT MAC Address string (อุปกรณ์ที่มี Bluetooth, ต้องการandroid.permission.BLUETOOTH)

2
จุดสำคัญออกไปแล้ว (ที่นี่และในบทความ): คุณไม่สามารถรับ WLAN หรือ BT MAC ได้หากไม่ได้เปิดใช้! มิฉะนั้นฉันคิดว่า WLAN MAC จะเป็นตัวระบุที่สมบูรณ์แบบ คุณไม่รับประกันว่าผู้ใช้จะเปิด Wi-Fi ของพวกเขาและฉันไม่คิดว่ามัน 'เหมาะสม' ที่จะเปิดใช้งานด้วยตัวคุณเอง
ทอม

1
@ คุณผิด คุณยังสามารถอ่าน WLAN หรือ BT MAC ได้แม้จะปิดอยู่ อย่างไรก็ตามไม่มีการรับประกันว่าอุปกรณ์จะมีโมดูล WLAN หรือ BT
Marqs

2
ส่วนใหญ่แล้วที่อยู่ Local WiFi และ Bluetooth MAC จะไม่สามารถใช้ได้อีกต่อไปเมธอด getMacAddress () ของวัตถุ aWifiInfo และ BluetoothAdapter.getDefaultAdapter () วิธี getAddress () จะส่งคืนทั้ง 02: 00: 00: 00: 00 จากนี้เป็นต้นไป
sarika kate

4
@sarikakate มันเป็นจริงเฉพาะใน 6.0 Marshmallow และเหนือ ... มันยังคงทำงานตามที่คาดไว้ใน 6.0 Marshmallow
Smeet

@Smeet ใช่คุณพูดถูกฉันลืมที่จะพูดถึงว่ามันทำงานต่ำกว่า 6.0
sarika kate

51

อย่างเป็นทางการ Android Developers Blog ตอนนี้มีบทความเต็มรูปแบบเพียงเกี่ยวกับเรื่องนี้มากระบุการติดตั้ง App


4
และจุดสำคัญของการโต้แย้งนั้นคือถ้าคุณพยายามเอารหัสเฉพาะออกจากฮาร์ดแวร์คุณอาจทำผิดพลาด
ทิมเบรย์

3
และหากคุณอนุญาตให้ล็อคอุปกรณ์ของคุณเพื่อรีเซ็ตโดยรีเซ็ตเป็นค่าเริ่มต้นโมเดลรุ่นทดลองใช้ของคุณจะดีเหมือนเดิม
Seva Alekseyev

43

ที่Google I / O Reto Meier เปิดตัวคำตอบที่มีประสิทธิภาพเกี่ยวกับวิธีการเข้าถึงสิ่งนี้ซึ่งน่าจะตอบสนองความต้องการของนักพัฒนาส่วนใหญ่ในการติดตามผู้ใช้ในการติดตั้ง แอนโทนี่โนแลนแสดงทิศทางในคำตอบของเขา แต่ฉันคิดว่าฉันเขียนแนวทางเต็มรูปแบบเพื่อให้ผู้อื่นสามารถเห็นวิธีการใช้งานได้อย่างง่ายดาย

วิธีนี้จะให้ ID ผู้ใช้ที่ไม่ระบุชื่อและปลอดภัยซึ่งจะคงอยู่ตลอดไปสำหรับผู้ใช้ในอุปกรณ์ต่าง ๆ (ตามบัญชี Google หลัก) และระหว่างการติดตั้ง วิธีการพื้นฐานคือการสร้าง ID ผู้ใช้แบบสุ่มและเก็บไว้ในการตั้งค่าที่แชร์ของแอพ จากนั้นคุณใช้ตัวแทนสำรองของ Google เพื่อจัดเก็บค่ากำหนดที่แชร์ซึ่งเชื่อมโยงกับบัญชี Google ในคลาวด์

เรามาดูวิธีการทั้งหมด ก่อนอื่นเราต้องสร้างการสำรองข้อมูลสำหรับ SharedPreferences โดยใช้บริการสำรองข้อมูล Android เริ่มต้นด้วยการลงทะเบียนแอปของคุณผ่านทางhttp://developer.android.com/google/backup/signup.htmlเริ่มต้นโดยการลงทะเบียนผ่านทางแอปของคุณ

Google จะให้รหัสบริการสำรองซึ่งคุณต้องเพิ่มในไฟล์ Manifest คุณต้องบอกแอปพลิเคชันเพื่อใช้ BackupAgent ดังนี้:

<application android:label="MyApplication"
         android:backupAgent="MyBackupAgent">
    ...
    <meta-data android:name="com.google.android.backup.api_key"
        android:value="your_backup_service_key" />
</application>

จากนั้นคุณต้องสร้างเอเจนต์สำรองและบอกให้ใช้เอเจนต์ผู้ช่วยเหลือสำหรับการแชร์ล่วงหน้า:

public class MyBackupAgent extends BackupAgentHelper {
    // The name of the SharedPreferences file
    static final String PREFS = "user_preferences";

    // A key to uniquely identify the set of backup data
    static final String PREFS_BACKUP_KEY = "prefs";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this,          PREFS);
        addHelper(PREFS_BACKUP_KEY, helper);
    }
}

เพื่อให้การสำรองข้อมูลเสร็จสมบูรณ์คุณต้องสร้างตัวอย่างของ BackupManager ในกิจกรรมหลักของคุณ:

BackupManager backupManager = new BackupManager(context);

สุดท้ายสร้าง ID ผู้ใช้หากยังไม่มีอยู่และเก็บไว้ใน SharedPreferences:

  public static String getUserID(Context context) {
            private static String uniqueID = null;
        private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";
    if (uniqueID == null) {
        SharedPreferences sharedPrefs = context.getSharedPreferences(
                MyBackupAgent.PREFS, Context.MODE_PRIVATE);
        uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
        if (uniqueID == null) {
            uniqueID = UUID.randomUUID().toString();
            Editor editor = sharedPrefs.edit();
            editor.putString(PREF_UNIQUE_ID, uniqueID);
            editor.commit();

            //backup the changes
            BackupManager mBackupManager = new BackupManager(context);
            mBackupManager.dataChanged();
        }
    }

    return uniqueID;
}

User_ID นี้จะคงอยู่ตลอดการติดตั้งแม้ว่าผู้ใช้จะย้ายอุปกรณ์

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการนี้ดูพูดคุย Reto ของ

และสำหรับรายละเอียดของวิธีการใช้ตัวแทนสำรองดูการสำรองข้อมูล ฉันขอแนะนำส่วนที่ด้านล่างของการทดสอบโดยเฉพาะอย่างยิ่งการสำรองข้อมูลไม่ได้เกิดขึ้นอย่างทันทีทันใดเพื่อทดสอบคุณต้องบังคับให้มีการสำรองข้อมูล


5
สิ่งนี้นำไปสู่อุปกรณ์หลายเครื่องที่มี id เดียวกันเมื่อผู้ใช้มีหลายอุปกรณ์หรือไม่ ยกตัวอย่างเช่นแท็บเล็ตและโทรศัพท์
Tosa

ต้องการเป้าหมายขั้นต่ำ 8 สำหรับสิ่งนี้
halxinate

นี่เป็นวิธีที่ต้องการในการสร้างเพย์โหลดการยืนยันเมื่อทำการซื้อในแอพใช่ไหม จากความคิดเห็นเกี่ยวกับโค้ดตัวอย่างการเรียกเก็บเงินในแอป: "ดังนั้นนักพัฒนาซอฟต์แวร์ที่ดีต้องมีคุณสมบัติเหล่านี้: 1. หากผู้ใช้สองคนซื้อรายการหนึ่งรายการ payload นั้นแตกต่างกันระหว่างพวกเขาดังนั้นการซื้อของผู้ใช้คนหนึ่ง 2. น้ำหนักบรรทุกจะต้องเป็นเช่นนั้นคุณสามารถตรวจสอบได้แม้ในขณะที่แอปไม่ใช่ผู้ที่เริ่มต้นขั้นตอนการสั่งซื้อ (เพื่อให้รายการที่ผู้ใช้ซื้อบนอุปกรณ์หนึ่งทำงานบนอุปกรณ์อื่นที่เป็นของผู้ใช้) "
TouchBoarder

@ Tosa ฉันมีคำถามเดียวกัน แต่เราไม่สามารถใช้เทคนิคเดียวกันนี้อีกครั้งเพื่อสร้างรหัสอุปกรณ์เสมือนและไม่สำรองข้อมูลด้วยวิธีเดียวกันได้หรือไม่ รหัสอุปกรณ์จะไม่คงอยู่ในการลบหรือติดตั้งใหม่ แต่ถ้าเรามี ID ผู้ใช้ถาวรเราอาจไม่ต้องการรหัสนั้นมากนัก
jwehrle

39

ฉันคิดว่านี่เป็นวิธีที่แน่นอนในการสร้างโครงกระดูกสำหรับรหัสเฉพาะ ... ลองดู

Pseudo-Unique ID ที่ทำงานบนอุปกรณ์ Android ทั้งหมดอุปกรณ์บางอย่างไม่มีโทรศัพท์ (เช่นแท็บเล็ต) หรือด้วยเหตุผลบางอย่างคุณไม่ต้องการรวมสิทธิ์ READ_PHONE_STATE คุณยังสามารถอ่านรายละเอียดเช่นเวอร์ชั่น ROM ชื่อผู้ผลิตประเภท CPU และรายละเอียดฮาร์ดแวร์อื่น ๆ ซึ่งจะเหมาะสมอย่างยิ่งหากคุณต้องการใช้ ID สำหรับการตรวจสอบรหัสอนุกรมหรือวัตถุประสงค์ทั่วไปอื่น ๆ ID ที่คำนวณด้วยวิธีนี้จะไม่ซ้ำกัน: เป็นไปได้ที่จะหาอุปกรณ์สองชิ้นที่มี ID เดียวกัน (ขึ้นอยู่กับฮาร์ดแวร์และอิมเมจ ROM เดียวกัน) แต่การเปลี่ยนแปลงในแอปพลิเคชันในโลกแห่งความเป็นจริงนั้นไม่มีความสำคัญ เพื่อจุดประสงค์นี้คุณสามารถใช้คลาส Build:

String m_szDevIDShort = "35" + //we make this look like a valid IMEI
            Build.BOARD.length()%10+ Build.BRAND.length()%10 +
            Build.CPU_ABI.length()%10 + Build.DEVICE.length()%10 +
            Build.DISPLAY.length()%10 + Build.HOST.length()%10 +
            Build.ID.length()%10 + Build.MANUFACTURER.length()%10 +
            Build.MODEL.length()%10 + Build.PRODUCT.length()%10 +
            Build.TAGS.length()%10 + Build.TYPE.length()%10 +
            Build.USER.length()%10 ; //13 digits

สมาชิก Build ส่วนใหญ่เป็นสายอักขระสิ่งที่เรากำลังทำที่นี่คือการใช้ความยาวและแปลงมันผ่านโมดูโลเป็นตัวเลข เรามี 13 หลักดังกล่าวและเรากำลังเพิ่มอีกสองหน้า (35) เพื่อให้มีขนาด ID เดียวกับ IMEI (15 หลัก) มีความเป็นไปได้อื่น ๆ ที่นี่ดีเพียงแค่ดูที่สตริงเหล่านี้ 355715565309247ผลตอบแทนที่ได้สิ่งที่ต้องการ ไม่จำเป็นต้องได้รับอนุญาตพิเศษทำให้วิธีนี้สะดวกมาก


(ข้อมูลเพิ่มเติม: เทคนิคที่ให้ไว้ข้างต้นถูกคัดลอกมาจากบทความในPocket Magic )


7
ทางออกที่น่าสนใจ ดูเหมือนว่านี่เป็นสถานการณ์ที่คุณควรจะเพียงแค่แฮ็ชข้อมูลทั้งหมดที่ต่อกันแทนที่จะพยายามหาฟังก์ชั่น "แฮช" ของคุณเอง มีหลายกรณีที่คุณจะได้รับการชนแม้ว่าจะมีข้อมูลมากมายที่แตกต่างกันสำหรับแต่ละค่า คำแนะนำของฉัน: ใช้ฟังก์ชั่นแฮชแล้วแปลงผลลัพธ์ไบนารีให้เป็นทศนิยมและตัดมันตามต้องการ เพื่อให้ถูกต้องแม้ว่าคุณควรใช้ UUID หรือสตริงแฮชแบบเต็ม
Steve Pomeroy

21
คุณควรให้เครดิตกับแหล่งที่มาของคุณ ... สิ่งนี้ได้รับการยกออกจากบทความต่อไปนี้: pocketmagic.net/?p=1662
Steve Haley

8
ID นี้เปิดให้มีการชนเหมือนที่คุณไม่รู้ รับประกันได้จริงว่าจะเหมือนกันในอุปกรณ์ที่เหมือนกันจากผู้ให้บริการเดียวกัน
Seva Alekseyev

7
สิ่งนี้อาจเปลี่ยนแปลงหากอุปกรณ์ได้รับการอัพเกรด
เดวิดให้

8
ทางออกที่เลวร้ายมาก ทดสอบกับ Nexus 5 สองเครื่อง ... ส่งคืนหมายเลขเดิม
Sinan Dizdarević

38

รหัสต่อไปนี้จะคืนค่าหมายเลขซีเรียลของอุปกรณ์โดยใช้ Android API ที่ซ่อนอยู่ แต่รหัสนี้ใช้ไม่ได้กับ Samsung Galaxy Tab เพราะไม่ได้ตั้ง "ro.serialno" ในอุปกรณ์นี้

String serial = null;

try {
    Class<?> c = Class.forName("android.os.SystemProperties");
    Method get = c.getMethod("get", String.class);
    serial = (String) get.invoke(c, "ro.serialno");
}
catch (Exception ignored) {

}

ฉันอ่านเพียงนักพัฒนา XDA ว่าจะใช้ในการสร้างro.serialno Settings.Secure.ANDROID_IDดังนั้นพวกเขาจึงเป็นตัวแทนที่แตกต่างกันของค่าเดียวกัน
Martin

@Martin: แต่อาจเป็นไปได้ว่าหมายเลขซีเรียลจะไม่เปลี่ยนแปลงเมื่อทำการรีเซ็ตอุปกรณ์ ไม่ใช่เหรอ ค่าใหม่ที่ANDROID_IDได้รับมาจากมัน
Ronnie

ที่จริงแล้วในทุกอุปกรณ์ฉันได้ทดสอบพวกมันในที่เดียวกัน หรืออย่างน้อยค่าแฮชที่เหมือนกัน (สำหรับเหตุผลความเป็นส่วนตัวฉันไม่ได้เขียนค่าจริงลงในไฟล์บันทึก)
Martin

ค่านี้เป็นเช่นเดียวกับandroid.os.Build.SERIAL
eugeneek

android.os.Build.SERIALจะเลิกใช้ใน Android O ดูandroid-developers.googleblog.com/2017/04/…
EpicPandaForce

32

มันเป็นคำถามง่าย ๆ ที่ไม่มีคำตอบง่ายๆ

ยิ่งไปกว่านั้นคำตอบที่มีอยู่ทั้งหมดนี้ไม่ว่าจะล้าสมัยหรือไม่น่าเชื่อถือ

ดังนั้นหากคุณกำลังค้นหาทางออกในปี 2020ถ้าคุณกำลังมองหาวิธีการแก้ปัญหาในปี 2020

ต่อไปนี้เป็นสิ่งที่ควรคำนึงถึง:

ตัวระบุฮาร์ดแวร์ทั้งหมด (SSAID, IMEI, MAC และอื่น ๆ ) ไม่น่าเชื่อถือสำหรับอุปกรณ์ที่ไม่ใช่ของ Google (ทุกอย่างยกเว้น Pixels และ Nexuses) ซึ่งเป็นอุปกรณ์ที่ใช้งานอยู่ทั่วโลกมากกว่า 50% ดังนั้นแนวทางปฏิบัติที่ดีที่สุดสำหรับ Androidอย่างเป็นทางการระบุไว้ชัดเจน:

หลีกเลี่ยงการใช้ตัวระบุฮาร์ดแวร์เช่น SSAID (Android ID), IMEI, ที่อยู่ MAC ฯลฯ ...

นั่นทำให้คำตอบส่วนใหญ่ข้างต้นไม่ถูกต้อง นอกจากนี้เนื่องจากการอัปเดตความปลอดภัยของ Android ที่แตกต่างกันบางรายการต้องการการอนุญาตรันไทม์ที่ใหม่กว่าและเข้มงวดกว่าซึ่งผู้ใช้สามารถปฏิเสธได้

เป็นตัวอย่างCVE-2018-9489ที่มีผลต่อเทคนิค WIFI ทั้งหมดที่กล่าวถึงข้างต้น

ซึ่งทำให้ตัวระบุเหล่านั้นไม่เพียง แต่ไม่น่าเชื่อถือ แต่ยังไม่สามารถเข้าถึงได้ในหลายกรณี

ดังนั้นในคำที่ง่ายกว่า: อย่าใช้เทคนิคเหล่านั้นไม่ได้ใช้เทคนิคเหล่านั้น

คำตอบอื่น ๆ อีกมากมายที่นี่แนะนำให้ใช้AdvertisingIdClientซึ่งยังไม่เข้ากันเนื่องจากการออกแบบควรใช้สำหรับการทำโปรไฟล์โฆษณาเท่านั้น มันยังระบุไว้ในเอกสารอ้างอิงอย่างเป็นทางการ

ใช้ ID โฆษณาสำหรับการทำโปรไฟล์ผู้ใช้หรือโฆษณาใช้เคสเท่านั้น

ไม่เพียง แต่ไม่น่าเชื่อถือสำหรับการระบุอุปกรณ์ แต่คุณยังต้องปฏิบัติตามนโยบายความเป็นส่วนตัวของผู้ใช้เกี่ยวกับการติดตามโฆษณาซึ่งระบุไว้อย่างชัดเจนว่าผู้ใช้สามารถรีเซ็ตหรือบล็อกได้ตลอดเวลา

ดังนั้นอย่าใช้ด้วย

เนื่องจากคุณไม่สามารถมีตัวระบุอุปกรณ์ที่เชื่อถือได้ทั่วโลกและเชื่อถือได้ การอ้างอิงอย่างเป็นทางการของ Android แนะนำ:

ใช้ FirebaseInstanceId หรือ GUID ที่เก็บไว้เป็นส่วนตัวเมื่อใดก็ตามที่เป็นไปได้สำหรับกรณีการใช้งานอื่น ๆ ทั้งหมดยกเว้นการป้องกันการฉ้อโกงการชำระเงินและการโทรศัพท์

มันไม่เหมือนใครสำหรับการติดตั้งแอปพลิเคชันบนอุปกรณ์ดังนั้นเมื่อผู้ใช้ถอนการติดตั้งแอป - มันถูกลบออกดังนั้นจึงไม่น่าเชื่อถือ 100% แต่เป็นสิ่งที่ดีที่สุดถัดไป

หากต้องการใช้FirebaseInstanceIdเพิ่มการพึ่งพาการส่งข้อความ Firebase ล่าสุดลงในระดับเสียงของคุณ

implementation 'com.google.firebase:firebase-messaging:20.2.0'

และใช้รหัสด้านล่างในชุดข้อความพื้นหลัง:

String reliableIdentifier = FirebaseInstanceId.getInstance().getId();

หากคุณต้องการจัดเก็บข้อมูลระบุอุปกรณ์บนเซิร์ฟเวอร์ระยะไกลของคุณอย่าเก็บไว้ตามที่เป็น (ข้อความธรรมดา) แต่มีแฮชที่ใส่เกลือกัญชาด้วยเกลือ

วันนี้มันไม่ใช่แค่วิธีปฏิบัติที่ดีที่สุดคุณต้องทำตามกฎหมายตามGDPR - ตัวระบุและข้อบังคับที่คล้ายกัน


3
สำหรับตอนนี้นี่เป็นคำตอบที่ดีที่สุดและประโยคแรกคือบทสรุปที่ดีที่สุด: "มันเป็นคำถามง่าย ๆ ที่ไม่มีคำตอบง่ายๆ - แค่รักมัน
b2mob

คุณต้องชื่นชมความคิดเห็น "ยกเว้นการป้องกันการฉ้อโกงการชำระเงินและการโทรศัพท์" โดยไม่มีคำตอบว่าจะจัดการกับกรณีการใช้งานอย่างไร
Eran Boudjnah

@EranBoudjnah นั่นอ้างจากการอ้างอิงอย่างเป็นทางการซึ่งมีการเชื่อมโยงในคำตอบ ฉันสามารถระบุที่อยู่ที่ใช้กรณี แต่เนื่องจากมันไม่เฉพาะคำถาม OP ตามนโยบายของ stackoverflow - ควรทำในคำถามแยกต่างหาก
Nikita Kurtin

ฉันแค่บอกว่าคำตอบนั้นไม่สมบูรณ์ แต่ฉันรู้ว่าไม่ใช่ความผิดของคุณเพราะเป็นคำพูด
Eran Boudjnah

1
@ M.UsmanKhan คำตอบถูกเขียนขึ้นหลังจากนั้น: " วันนี้มันไม่ใช่แค่การปฏิบัติที่ดีที่สุดเท่านั้นที่จริงคุณต้องทำตามกฎหมายตาม GDPR - ตัวระบุและข้อบังคับที่คล้ายกัน "
Nikita Kurtin

27

ใช้รหัสด้านล่างคุณจะได้รับรหัสอุปกรณ์ที่ไม่ซ้ำกันของอุปกรณ์ Android OS เป็นสตริง

deviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 

21

อนุกรมข้อมูลเพิ่มที่Buildการเรียนในระดับ API 9 (Android 2.3 - Gingerbread) เอกสารอธิบายว่าเป็นหมายเลขซีเรียลของฮาร์ดแวร์ ดังนั้นมันควรจะไม่ซ้ำกันถ้ามันมีอยู่ในอุปกรณ์

ฉันไม่รู้ว่ามันรองรับจริงหรือไม่ (= ไม่ใช่ null) โดยอุปกรณ์ทั้งหมดที่มีระดับ API> = 9


2
น่าเสียดายที่มันเป็น "ไม่ทราบ"
m0skit0

18

สิ่งหนึ่งที่ฉันจะเพิ่ม - ฉันมีหนึ่งในสถานการณ์พิเศษเหล่านั้น

โดยใช้:

deviceId = Secure.getString(this.getContext().getContentResolver(), Secure.ANDROID_ID);

ปรากฎว่าแม้ว่า Viewsonic G แท็บเล็ตของฉันจะรายงาน DeviceID ที่ไม่เป็นโมฆะ G แท็บเล็ตทุกเครื่องจะรายงานหมายเลขเดียวกัน

ทำให้การเล่น "Pocket Empires" น่าสนใจซึ่งช่วยให้คุณสามารถเข้าถึงบัญชีของใครบางคนโดยใช้ DeviceID "ที่ไม่ซ้ำใคร"

อุปกรณ์ของฉันไม่มีวิทยุมือถือ


ID คืออะไร มันอาจเกิดขึ้น9774d56d682e549c?
Mr_and_Mrs_D

ว้าวนั่นมันนานมาแล้วฉันนานตั้งแต่ได้โยนแท็บเล็ตนั้น ไม่สามารถพูดได้
Tony Maro

โรงงาน นอกจากนี้ยังมีขนาดเล็กกว่า ID ที่ฉันได้รับจาก UUID แบบสุ่ม
Treewallie

1
@Treewallie ใช้งานได้หรือไม่ คุณสามารถรับรหัสอุปกรณ์เดียวกันจากแอพต่างๆ
อาร์โนลด์บราวน์

@ArnoldBrown ใช่ อย่าลืมทดสอบมันให้ละเอียด ขอให้มีความสุขมาก ๆ ในวันนี้: D
Treewallie

16

สำหรับคำแนะนำรายละเอียดเกี่ยวกับวิธีการที่จะได้รับรหัสเฉพาะสำหรับแต่ละอุปกรณ์ Android ของแอพลิเคชันของคุณมีการติดตั้งจากดูอย่างเป็นทางการของนักพัฒนาซอฟต์แวร์ Android บล็อกโพสต์ระบุการติดตั้ง App

ดูเหมือนว่าวิธีที่ดีที่สุดคือให้คุณสร้างด้วยตัวคุณเองเมื่อทำการติดตั้งแล้วอ่านมันเมื่อเปิดใช้งานแอปพลิเคชันอีกครั้ง

ฉันเองพบว่ายอมรับได้ แต่ไม่เหมาะ ไม่มีตัวระบุหนึ่งตัวที่ Android จัดทำขึ้นในทุกกรณีเนื่องจากส่วนใหญ่ขึ้นอยู่กับสถานะวิทยุของโทรศัพท์ (เปิด / ปิด Wi-Fi, เปิด / ปิดสัญญาณโทรศัพท์มือถือ, เปิด / ปิดบลูทู ธ ) คนอื่น ๆ เช่นSettings.Secure.ANDROID_IDจะต้องดำเนินการโดยผู้ผลิตและไม่รับประกันว่าจะไม่ซ้ำกัน

ต่อไปนี้เป็นตัวอย่างของการเขียนข้อมูลไปยังไฟล์การติดตั้งที่จะถูกจัดเก็บพร้อมกับข้อมูลอื่น ๆ ที่แอปพลิเคชันบันทึกไว้ในเครื่อง

public class Installation {
    private static String sID = null;
    private static final String INSTALLATION = "INSTALLATION";

    public synchronized static String id(Context context) {
        if (sID == null) {
            File installation = new File(context.getFilesDir(), INSTALLATION);
            try {
                if (!installation.exists())
                    writeInstallationFile(installation);
                sID = readInstallationFile(installation);
            } 
            catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return sID;
    }

    private static String readInstallationFile(File installation) throws IOException {
        RandomAccessFile f = new RandomAccessFile(installation, "r");
        byte[] bytes = new byte[(int) f.length()];
        f.readFully(bytes);
        f.close();
        return new String(bytes);
    }

    private static void writeInstallationFile(File installation) throws IOException {
        FileOutputStream out = new FileOutputStream(installation);
        String id = UUID.randomUUID().toString();
        out.write(id.getBytes());
        out.close();
    }
}

หากคุณต้องการติดตามการติดตั้งแอพนี้เหมาะมาก แม้ว่าอุปกรณ์การติดตามจะมีเล่ห์เหลี่ยมมากและดูเหมือนจะไม่มีทางแก้ปัญหาอย่างแน่นหนา
Luca Spiller

แล้วอุปกรณ์ที่รูทแล้วล่ะ พวกเขาสามารถเปลี่ยนรหัสการติดตั้งนี้ได้อย่างง่ายดายใช่ไหม
tasomaniac

อย่างแน่นอน รูทสามารถเปลี่ยน ID การติดตั้งได้ คุณสามารถตรวจสอบรูทโดยใช้บล็อกโค้ดนี้: stackoverflow.com/questions/1101380/…
Kevin Parker

หากเรารีเซ็ตโรงงานไฟล์จะลบหรือไม่
Jamshid

หากคุณรีเซ็ตและลบหรือจัดรูปแบบพาร์ติชัน / data จากโรงงาน UUID จะแตกต่างกัน
Kevin Parker

12

เพิ่มรหัสด้านล่างในไฟล์คลาส:

final TelephonyManager tm = (TelephonyManager) getBaseContext()
            .getSystemService(SplashActivity.TELEPHONY_SERVICE);
    final String tmDevice, tmSerial, androidId;
    tmDevice = "" + tm.getDeviceId();
    Log.v("DeviceIMEI", "" + tmDevice);
    tmSerial = "" + tm.getSimSerialNumber();
    Log.v("GSM devices Serial Number[simcard] ", "" + tmSerial);
    androidId = "" + android.provider.Settings.Secure.getString(getContentResolver(),
            android.provider.Settings.Secure.ANDROID_ID);
    Log.v("androidId CDMA devices", "" + androidId);
    UUID deviceUuid = new UUID(androidId.hashCode(),
            ((long) tmDevice.hashCode() << 32) | tmSerial.hashCode());
    String deviceId = deviceUuid.toString();
    Log.v("deviceIdUUID universally unique identifier", "" + deviceId);
    String deviceModelName = android.os.Build.MODEL;
    Log.v("Model Name", "" + deviceModelName);
    String deviceUSER = android.os.Build.USER;
    Log.v("Name USER", "" + deviceUSER);
    String devicePRODUCT = android.os.Build.PRODUCT;
    Log.v("PRODUCT", "" + devicePRODUCT);
    String deviceHARDWARE = android.os.Build.HARDWARE;
    Log.v("HARDWARE", "" + deviceHARDWARE);
    String deviceBRAND = android.os.Build.BRAND;
    Log.v("BRAND", "" + deviceBRAND);
    String myVersion = android.os.Build.VERSION.RELEASE;
    Log.v("VERSION.RELEASE", "" + myVersion);
    int sdkVersion = android.os.Build.VERSION.SDK_INT;
    Log.v("VERSION.SDK_INT", "" + sdkVersion);

เพิ่มใน AndroidManifest.xml:

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

10

ID อุปกรณ์ที่ไม่ซ้ำกันของอุปกรณ์ Android OS เป็น String ซึ่งใช้TelephonyManagerและANDROID_IDได้รับโดย:

String deviceId;
final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephony.getDeviceId() != null) {
    deviceId = mTelephony.getDeviceId();
}
else {
    deviceId = Secure.getString(
                   getApplicationContext().getContentResolver(),
                   Secure.ANDROID_ID);
}

แต่ผมขอแนะนำวิธีการที่แนะนำโดย Google โปรดดูการระบุการติดตั้ง App


9

มีวิธีการที่แตกต่างกันมากมายในการANDROID_IDแก้ไขปัญหาเหล่านั้น(อาจเป็นnullบางครั้งหรืออุปกรณ์ของรุ่นเฉพาะส่งคืน ID เดียวกันเสมอ) ด้วยข้อดีข้อเสีย:

  • การใช้อัลกอริทึมการสร้าง ID แบบกำหนดเอง (ขึ้นอยู่กับคุณสมบัติของอุปกรณ์ที่ควรจะเป็นแบบสแตติกและจะไม่เปลี่ยนแปลง -> ใครจะรู้)
  • การใช้ ID อื่นในทางที่ผิดเช่นIMEI หมายเลขซีเรียลที่อยู่ Wi-Fi / Bluetooth-MAC (จะไม่มีอยู่ในอุปกรณ์ทั้งหมดหรือจำเป็นต้องมีการอนุญาตเพิ่มเติม)

ฉันชอบที่จะใช้การใช้งาน OpenUDID ที่มีอยู่ (ดูhttps://github.com/ylechelle/OpenUDID ) สำหรับ Android (ดูhttps://github.com/vieux/OpenUDID ) มันง่ายที่จะรวมและใช้ประโยชน์จากANDROID_IDกับ fallbacks สำหรับปัญหาที่กล่าวถึงข้างต้น


8

แล้วIMEIล่ะ นั่นเป็นเอกลักษณ์สำหรับ Android หรืออุปกรณ์มือถืออื่น ๆ


9
ไม่ใช่สำหรับแท็บเล็ตของฉันซึ่งไม่มี IMEI เนื่องจากไม่ได้เชื่อมต่อกับผู้ให้บริการมือถือของฉัน
Brill Pappin

2
ไม่ต้องพูดถึงอุปกรณ์ CDMA ที่มี ESN แทนที่จะเป็น IMEI
เดวิดให้

@David Given มี CDMA กับ Android ไหม?
Elzo Valugi

1
มันจะทำอย่างนั้นเพราะมันคือโทรศัพท์ :) แท็บเล็ตอาจไม่ได้
Brill Pappin

3
@ElzoValugi มันเป็น "วันนี้" แล้วและยังคงไม่ได้แท็บเล็ตทั้งหมดที่มีซิมการ์ด
Matthew Quiros

8

นี่คือวิธีที่ฉันสร้าง id เฉพาะ:

public static String getDeviceId(Context ctx)
{
    TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);

    String tmDevice = tm.getDeviceId();
    String androidId = Secure.getString(ctx.getContentResolver(), Secure.ANDROID_ID);
    String serial = null;
    if(Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) serial = Build.SERIAL;

    if(tmDevice != null) return "01" + tmDevice;
    if(androidId != null) return "02" + androidId;
    if(serial != null) return "03" + serial;
    // other alternatives (i.e. Wi-Fi MAC, Bluetooth MAC, etc.)

    return null;
}

ถ้าเราใช้ ReadPhoneState ในเวอร์ชั่น 6.0 ขออนุญาตรันไทม์
Harsha

8

สองเซ็นต์ของฉัน - NB นี่เป็นอุปกรณ์ (ผิดพลาด) รหัสที่ไม่ซ้ำกัน - ไม่ได้ติดตั้งอย่างใดอย่างหนึ่งตามที่กล่าวไว้ในบล็อกของนักพัฒนาของ Android

โปรดทราบว่าโซลูชันที่ให้บริการโดย @emmby จะกลับไปใช้ในแต่ละ ID แอปพลิเคชันเนื่องจาก SharedPreferences ไม่ได้รับการซิงโครไนซ์ข้ามกระบวนการ (ดูที่นี่และที่นี่ ) ดังนั้นฉันจึงหลีกเลี่ยงสิ่งนี้โดยสิ้นเชิง

แต่ฉันสรุปกลยุทธ์ต่าง ๆ ในการรับ (อุปกรณ์) ID ใน enum - การเปลี่ยนลำดับของค่าคงที่ enum มีผลต่อลำดับความสำคัญของวิธีต่างๆในการรับ ID ID ที่ไม่ใช่นัลแรกถูกส่งคืนหรือมีการโยนข้อยกเว้น (ตามแนวทางปฏิบัติที่ดีของ Java ที่ไม่ให้ความหมายเป็นโมฆะ) ตัวอย่างเช่นฉันมีโทรศัพท์หนึ่งตัวแรก - แต่ตัวเลือกเริ่มต้นที่ดีคือ ANDROID_ID เบต้า:

import android.Manifest.permission;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.wifi.WifiManager;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;

// TODO : hash
public final class DeviceIdentifier {

    private DeviceIdentifier() {}

    /** @see http://code.google.com/p/android/issues/detail?id=10603 */
    private static final String ANDROID_ID_BUG_MSG = "The device suffers from "
        + "the Android ID bug - its ID is the emulator ID : "
        + IDs.BUGGY_ANDROID_ID;
    private static volatile String uuid; // volatile needed - see EJ item 71
    // need lazy initialization to get a context

    /**
     * Returns a unique identifier for this device. The first (in the order the
     * enums constants as defined in the IDs enum) non null identifier is
     * returned or a DeviceIDException is thrown. A DeviceIDException is also
     * thrown if ignoreBuggyAndroidID is false and the device has the Android ID
     * bug
     *
     * @param ctx
     *            an Android constant (to retrieve system services)
     * @param ignoreBuggyAndroidID
     *            if false, on a device with the android ID bug, the buggy
     *            android ID is not returned instead a DeviceIDException is
     *            thrown
     * @return a *device* ID - null is never returned, instead a
     *         DeviceIDException is thrown
     * @throws DeviceIDException
     *             if none of the enum methods manages to return a device ID
     */
    public static String getDeviceIdentifier(Context ctx,
            boolean ignoreBuggyAndroidID) throws DeviceIDException {
        String result = uuid;
        if (result == null) {
            synchronized (DeviceIdentifier.class) {
                result = uuid;
                if (result == null) {
                    for (IDs id : IDs.values()) {
                        try {
                            result = uuid = id.getId(ctx);
                        } catch (DeviceIDNotUniqueException e) {
                            if (!ignoreBuggyAndroidID)
                                throw new DeviceIDException(e);
                        }
                        if (result != null) return result;
                    }
                    throw new DeviceIDException();
                }
            }
        }
        return result;
    }

    private static enum IDs {
        TELEPHONY_ID {

            @Override
            String getId(Context ctx) {
                // TODO : add a SIM based mechanism ? tm.getSimSerialNumber();
                final TelephonyManager tm = (TelephonyManager) ctx
                        .getSystemService(Context.TELEPHONY_SERVICE);
                if (tm == null) {
                    w("Telephony Manager not available");
                    return null;
                }
                assertPermission(ctx, permission.READ_PHONE_STATE);
                return tm.getDeviceId();
            }
        },
        ANDROID_ID {

            @Override
            String getId(Context ctx) throws DeviceIDException {
                // no permission needed !
                final String andoidId = Secure.getString(
                    ctx.getContentResolver(),
                    android.provider.Settings.Secure.ANDROID_ID);
                if (BUGGY_ANDROID_ID.equals(andoidId)) {
                    e(ANDROID_ID_BUG_MSG);
                    throw new DeviceIDNotUniqueException();
                }
                return andoidId;
            }
        },
        WIFI_MAC {

            @Override
            String getId(Context ctx) {
                WifiManager wm = (WifiManager) ctx
                        .getSystemService(Context.WIFI_SERVICE);
                if (wm == null) {
                    w("Wifi Manager not available");
                    return null;
                }
                assertPermission(ctx, permission.ACCESS_WIFI_STATE); // I guess
                // getMacAddress() has no java doc !!!
                return wm.getConnectionInfo().getMacAddress();
            }
        },
        BLUETOOTH_MAC {

            @Override
            String getId(Context ctx) {
                BluetoothAdapter ba = BluetoothAdapter.getDefaultAdapter();
                if (ba == null) {
                    w("Bluetooth Adapter not available");
                    return null;
                }
                assertPermission(ctx, permission.BLUETOOTH);
                return ba.getAddress();
            }
        }
        // TODO PSEUDO_ID
        // http://www.pocketmagic.net/2011/02/android-unique-device-id/
        ;

        static final String BUGGY_ANDROID_ID = "9774d56d682e549c";
        private final static String TAG = IDs.class.getSimpleName();

        abstract String getId(Context ctx) throws DeviceIDException;

        private static void w(String msg) {
            Log.w(TAG, msg);
        }

        private static void e(String msg) {
            Log.e(TAG, msg);
        }
    }

    private static void assertPermission(Context ctx, String perm) {
        final int checkPermission = ctx.getPackageManager().checkPermission(
            perm, ctx.getPackageName());
        if (checkPermission != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Permission " + perm + " is required");
        }
    }

    // =========================================================================
    // Exceptions
    // =========================================================================
    public static class DeviceIDException extends Exception {

        private static final long serialVersionUID = -8083699995384519417L;
        private static final String NO_ANDROID_ID = "Could not retrieve a "
            + "device ID";

        public DeviceIDException(Throwable throwable) {
            super(NO_ANDROID_ID, throwable);
        }

        public DeviceIDException(String detailMessage) {
            super(detailMessage);
        }

        public DeviceIDException() {
            super(NO_ANDROID_ID);
        }
    }

    public static final class DeviceIDNotUniqueException extends
            DeviceIDException {

        private static final long serialVersionUID = -8940090896069484955L;

        public DeviceIDNotUniqueException() {
            super(ANDROID_ID_BUG_MSG);
        }
    }
}

8

มีคำตอบมากกว่า 30 ข้อที่นี่และบางคำเหมือนกันและบางคำตอบไม่เหมือนกัน คำตอบนี้ขึ้นอยู่กับคำตอบเหล่านั้นสองสามข้อ หนึ่งในนั้นคือคำตอบของ @Lenn Dolling

มันรวม 3 ID และสร้างสตริงเลขฐานสิบหก 32 หลัก มันทำงานได้ดีมากสำหรับฉัน

3 ID คือ:
Pseudo-ID - มันถูกสร้างขึ้นตามข้อกำหนดทางกายภาพของอุปกรณ์
ANDROID_ID - Settings.Secure.ANDROID_ID
ที่อยู่บลูทู ธ - ที่อยู่อะแดปเตอร์ Bluetooth

มันจะส่งคืนสิ่งนี้: 551F27C060712A72730B0A0F734064B1

หมายเหตุ: คุณสามารถเพิ่ม ID ได้มากขึ้นในlongIdสตริง ตัวอย่างเช่น Serial # ที่อยู่อะแดปเตอร์ไร้สาย IMEI วิธีที่คุณทำให้อุปกรณ์นี้แตกต่างกันมากขึ้น

@SuppressWarnings("deprecation")
@SuppressLint("HardwareIds")
public static String generateDeviceIdentifier(Context context) {

        String pseudoId = "35" +
                Build.BOARD.length() % 10 +
                Build.BRAND.length() % 10 +
                Build.CPU_ABI.length() % 10 +
                Build.DEVICE.length() % 10 +
                Build.DISPLAY.length() % 10 +
                Build.HOST.length() % 10 +
                Build.ID.length() % 10 +
                Build.MANUFACTURER.length() % 10 +
                Build.MODEL.length() % 10 +
                Build.PRODUCT.length() % 10 +
                Build.TAGS.length() % 10 +
                Build.TYPE.length() % 10 +
                Build.USER.length() % 10;

        String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);

        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        String btId = "";

        if (bluetoothAdapter != null) {
            btId = bluetoothAdapter.getAddress();
        }

        String longId = pseudoId + androidId + btId;

        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(longId.getBytes(), 0, longId.length());

            // get md5 bytes
            byte md5Bytes[] = messageDigest.digest();

            // creating a hex string
            String identifier = "";

            for (byte md5Byte : md5Bytes) {
                int b = (0xFF & md5Byte);

                // if it is a single digit, make sure it have 0 in front (proper padding)
                if (b <= 0xF) {
                    identifier += "0";
                }

                // add number to string
                identifier += Integer.toHexString(b);
            }

            // hex string to uppercase
            identifier = identifier.toUpperCase();
            return identifier;
        } catch (Exception e) {
            Log.e("TAG", e.toString());
        }
        return "";
}

1
การเพิ่มUUIDให้กับlongIdและเก็บไว้ในไฟล์จะทำให้มันเป็นตัวระบุที่ไม่ซ้ำใคร:String uuid = UUID.randomUUID().toString();
Mousa Alfhaily

1
หากทุกอย่างล้มเหลวหากผู้ใช้มีต่ำกว่า API 9 (ต่ำกว่า Gingerbread) ได้รีเซ็ตโทรศัพท์หรือ 'Secure.ANDROID_ID' หากส่งคืน 'null' เพียงแค่ ID ที่ส่งคืนจะถูกยึดตามข้อมูลอุปกรณ์ Android ของพวกเขาเท่านั้น นี่คือที่ที่การชนสามารถเกิดขึ้นได้ พยายามอย่าใช้ DISPLAY, HOST หรือ ID - รายการเหล่านี้อาจมีการเปลี่ยนแปลง หากมีการชนกันจะมีข้อมูลทับซ้อนกัน แหล่งที่มา: gist.github.com/pedja1/fe69e8a80ed505500caa
Mousa Alfhaily

หากเราพยายามรับหมายเลขที่ไม่ซ้ำด้วยรหัสนี้เราสามารถพูดได้ว่าเป็นรหัสที่ไม่ซ้ำและจะไม่มีข้อขัดแย้งใด ๆ กับอุปกรณ์อื่น ๆ ?
นินจา

1
@Ninja เนื่องจากที่อยู่ Mac BLE ไม่ซ้ำกันใช่ ID ที่สร้างขึ้นจะไม่ซ้ำกันเสมอ แต่ถ้าคุณอยากจะให้แน่ใจว่าผมจะแนะนำเพื่อเพิ่ม UUID longIdไป เปลี่ยนหนึ่งบรรทัดเช่นนี้: String longId = pseudoId + androidId + btId + UUID.randomUUID().toString();สิ่งนี้รับประกันว่า ID ที่สร้างจะไม่ซ้ำกัน
ᴛʜᴇᴘᴀᴛᴇʟ

@ ᴛʜᴇᴘᴀᴛᴇʟขอบคุณมากสำหรับความช่วยเหลืออันยิ่งใหญ่นี้ ที่จริงแล้วแอพของฉันมีข้อมูลที่ละเอียดอ่อนมากดังนั้นฉันต้องแน่ใจว่านี่เป็นสาเหตุที่ฉันยืนยันสิ่งนี้
Ninja

7

อีกวิธีคือใช้/sys/class/android_usb/android0/iSerialในแอพที่ไม่มีสิทธิ์ใด ๆ

user@creep:~$ adb shell ls -l /sys/class/android_usb/android0/iSerial
-rw-r--r-- root     root         4096 2013-01-10 21:08 iSerial
user@creep:~$ adb shell cat /sys/class/android_usb/android0/iSerial
0A3CXXXXXXXXXX5

ในการทำเช่นนี้ใน Java เพียงแค่ใช้ FileInputStream เพื่อเปิดไฟล์ iSerial และอ่านตัวอักษร ตรวจสอบให้แน่ใจว่าคุณใส่ไว้ในตัวจัดการข้อยกเว้นเนื่องจากอุปกรณ์บางตัวไม่มีไฟล์นี้

อย่างน้อยอุปกรณ์ต่อไปนี้ทราบว่าไฟล์นี้สามารถอ่านได้ทั่วโลก:

  • Galaxy Nexus
  • Nexus S
  • Motorola Xoom 3G
  • โตชิบา AT300
  • HTC One V
  • มินิ MK802
  • Samsung Galaxy S II

นอกจากนี้คุณยังสามารถดูหมายเลขซีเรียลของฮาร์ดแวร์โพสต์บล็อกของAndroid ที่รั่วไหลไปยังแอพที่ไม่มีสิทธิพิเศษได้


ฉันเพิ่งอ่านโพสต์บล็อกของคุณ ฉันเชื่อว่านี่ไม่ซ้ำกัน: Build.SERIAL ยังมีสิทธิ์ใด ๆ และไม่มี (ในทางทฤษฎี) หมายเลขซีเรียลของฮาร์ดแวร์ที่ไม่ซ้ำกัน
ทอม

1
คุณถูก. เป็นอีกวิธีหนึ่งที่อุปกรณ์ของคุณสามารถติดตามได้และอย่างที่คุณบอกว่าทั้งสองวิธีนี้ไม่จำเป็นต้องมีการอนุญาตแอพ
insitusec

7

TelephonyManger.getDeviceId ()ส่งคืน ID อุปกรณ์เฉพาะเช่น IMEI สำหรับ GSM และ MEID หรือ ESN สำหรับโทรศัพท์ CDMA

final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);            
String myAndroidDeviceId = mTelephony.getDeviceId(); 

แต่ฉันแนะนำให้ใช้:

Settings.Secure.ANDROID_IDที่ส่งคืน ID Android เป็นสตริงเลขฐานสิบหก 64 บิตที่ไม่ซ้ำกัน

    String   myAndroidDeviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 

บางครั้งTelephonyManger.getDeviceId ()จะคืนค่า null ดังนั้นเพื่อให้มั่นใจว่า id ที่ไม่ซ้ำกันคุณจะใช้วิธีนี้:

public String getUniqueID(){    
    String myAndroidDeviceId = "";
    TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    if (mTelephony.getDeviceId() != null){
        myAndroidDeviceId = mTelephony.getDeviceId(); 
    }else{
         myAndroidDeviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 
    }
    return myAndroidDeviceId;
}

ฉันเพิ่งค้นพบว่าอุปกรณ์ของลูกค้าประเภท SM-G928F / Galaxy S6 edge + ให้ 15 เท่านั้นแทน 16 ฐานสิบหกสำหรับ Android ID
Holger Jakobs

7

สำหรับการรับรู้ฮาร์ดแวร์ของอุปกรณ์ Android คุณสามารถตรวจสอบที่อยู่ MAC

คุณสามารถทำได้เช่นนั้น:

ใน AndroidManifest.xml

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

ตอนนี้ในรหัสของคุณ:

List<NetworkInterface> interfacesList = Collections.list(NetworkInterface.getNetworkInterfaces());

for (NetworkInterface interface : interfacesList) {
   // This will give you the interface MAC ADDRESS
   interface.getHardwareAddress();
}

ในอุปกรณ์ Android ทุกเครื่องอย่างน้อยพวกมันก็คือ "wlan0" Interface witch เป็นชิป Wi-Fi รหัสนี้ใช้งานได้แม้ไม่ได้เปิด Wi-Fi

ป.ล. พวกเขาเป็นกลุ่มของการเชื่อมต่ออื่น ๆ ที่คุณจะได้รับจากรายการที่มี MACS แต่สิ่งนี้สามารถเปลี่ยนระหว่างโทรศัพท์


7

ฉันใช้รหัสต่อไปนี้เพื่อรับIMEIหรือใช้ Secure ANDROID_IDเป็นทางเลือกเมื่ออุปกรณ์ไม่มีความสามารถในการใช้โทรศัพท์:

String identifier = null;
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE));
if (tm != null)
      identifier = tm.getDeviceId();
if (identifier == null || identifier .length() == 0)
      identifier = Secure.getString(activity.getContentResolver(),Secure.ANDROID_ID);

7

โดยเฉพาะอย่างยิ่ง, Settings.Secure.ANDROID_ID. นี่เป็นปริมาณ 64- บิตที่สร้างและจัดเก็บเมื่ออุปกรณ์บู๊ตเป็นครั้งแรก มันจะถูกรีเซ็ตเมื่ออุปกรณ์เช็ด

ANDROID_IDดูเหมือนจะเป็นตัวเลือกที่ดีสำหรับตัวระบุอุปกรณ์ที่ไม่ซ้ำใคร มีข้อเสีย: ประการแรกมันไม่น่าเชื่อถือ 100% ในการเปิดตัว Android ก่อนหน้า 2.2 (“Froyo”).นอกจากนี้ยังมีข้อผิดพลาดที่สังเกตได้อย่างน้อยหนึ่งครั้งในโทรศัพท์มือถือยอดนิยมจากผู้ผลิตรายใหญ่ซึ่งทุกกรณีมี ANDROID_ID เดียวกัน


1
คำตอบนี้เป็น CopyPaste จากบล็อกของ Google เก่าandroid-developers.googleblog.com/2011/03/... ดังนั้นข้อผิดพลาดได้รับการแก้ไขแล้ว?
Sergii

7

เพื่อทำความเข้าใจกับรหัสที่ไม่ซ้ำที่มีอยู่ในอุปกรณ์ Android ใช้คู่มืออย่างเป็นทางการนี้

แนวทางปฏิบัติที่ดีที่สุดสำหรับตัวระบุที่ไม่ซ้ำกัน:

IMEI, ที่อยู่ Mac, รหัสอินสแตนซ์, GUID, SSAID, รหัสโฆษณา, Safety Net API เพื่อตรวจสอบอุปกรณ์

https://developer.android.com/training/articles/user-data-ids


6

รหัส Google อินสแตนซ์

เผยแพร่เมื่อ I / O 2015; บน Android ต้องใช้บริการเล่น 7.5

https://developers.google.com/instance-id/
https://developers.google.com/instance-id/guides/android-implementation

InstanceID iid = InstanceID.getInstance( context );   // Google docs are wrong - this requires context
String id = iid.getId();  // blocking call

ดูเหมือนว่า Google จะใช้ ID นี้เพื่อระบุการติดตั้งใน Android, Chrome และ iOS

มันระบุถึงการติดตั้งแทนที่จะเป็นอุปกรณ์ แต่หลังจากนั้นอีกครั้ง ANDROID_ID (ซึ่งเป็นคำตอบที่ยอมรับได้) จะไม่ระบุอุปกรณ์อีกต่อไป ด้วย ARC runtime ANDROID_ID ใหม่จะถูกสร้างขึ้นสำหรับการติดตั้งทุกครั้ง ( รายละเอียดที่นี่ ) เช่นเดียวกับ ID อินสแตนซ์ใหม่นี้ นอกจากนี้ฉันคิดว่าการระบุการติดตั้ง (ไม่ใช่อุปกรณ์) คือสิ่งที่พวกเราส่วนใหญ่มองหา

ข้อดีของ ID อินสแตนซ์

สำหรับฉันดูเหมือนว่า Google ตั้งใจจะให้มันใช้เพื่อจุดประสงค์นี้ (ระบุการติดตั้งของคุณ) มันเป็นแพลตฟอร์มข้ามและสามารถใช้เพื่อวัตถุประสงค์อื่น ๆ ได้ (ดูลิงก์ด้านบน)

หากคุณใช้ GCM ในที่สุดคุณจะต้องใช้ ID อินสแตนซ์นี้เพราะคุณจำเป็นต้องใช้เพื่อรับโทเค็น GCM (ซึ่งจะแทนที่ ID การลงทะเบียน GCM เก่า)

ข้อเสีย / ปัญหา

ในการนำไปใช้งานปัจจุบัน (GPS 7.5) จะมีการดึง ID อินสแตนซ์จากเซิร์ฟเวอร์เมื่อแอปของคุณร้องขอ ซึ่งหมายความว่าการโทรด้านบนเป็นการปิดกั้นการโทร - ในการทดสอบทางวิทยาศาสตร์ของฉันจะใช้เวลา 1-3 วินาทีหากอุปกรณ์ออนไลน์และ 0.5 - 1.0 วินาทีหากออฟไลน์ (สมมุติว่านี่เป็นระยะเวลารอก่อนที่จะเลิกและสร้าง รหัสสุ่ม) สิ่งนี้ได้รับการทดสอบในอเมริกาเหนือใน Nexus 5 พร้อม Android 5.1.1 และ GPS 7.5

หากคุณใช้ ID เพื่อจุดประสงค์ที่พวกเขาต้องการ - เช่น การตรวจสอบแอปการระบุแอป GCM - ฉันคิดว่า 1-3 วินาทีนี้อาจสร้างความรำคาญ (ขึ้นอยู่กับแอพของคุณ)


1
ข้อเสียที่สำคัญอีกอย่างหนึ่งของ instanceID คือจะมีการสร้าง instanceID ใหม่สำหรับคุณหากผู้ใช้ล้างข้อมูลของแอป
idanakav

ที่น่าสนใจ แต่ฉันไม่คิดว่ามันจะเปลี่ยนการใช้งานที่อาจเกิดขึ้นจริง ๆ : ID อินสแตนซ์เช่น android_id ไม่เหมาะที่จะระบุอุปกรณ์ ดังนั้นเซิร์ฟเวอร์ของคุณจะเห็นข้อมูลการล้างข้อมูลของผู้ใช้เหมือนกับที่ผู้ใช้ทำการถอนการติดตั้งและติดตั้งแอปของคุณใหม่ซึ่งไม่ได้ไม่มีเหตุผล
Tom
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.