출처
: http://stunstun.tistory.com/184
참고자료
: http://huewu.blog.me/110107222113
안드로이드 개발 시에 디바이스 정보를 획득 하는 일들이 있는데, 그 중 에서도 디바이스의 고유한 번호를 획득 하는 일은 빈번하게 발생 한다. 예를 들면 고유한 디바이스의 정보를 저장 해 어플리케이션의 설치 상태를 확인 한다 던가
다양한 인증 서비스를 제공 할 때 보다 쉽게 서비스를 이용 할 수 있도록 게스트 로그인 같은 기능을 제공 할 때 디바이스에 대한 식별이 필요 할 때가 있다. 하지만 Android SDK 는 API Level 이 업데이트 되면서 이에 대한 정보를 획득 하는 데에 대한 이슈가 있는데 간단하게 나마 정리해보고자 한다.
TelephonyManager
지난 시간 동안 일반적인 경우에는 Hardware 단위로 제공 하는 identifier 를 통해 디바이스의 고유 번호를 획득 하고는 했다. TelephonyManager 클래스를 통해 Phone의 IMEI, MEID 또는 ESN 값을 획득 한다.
예를 들면 아래와 같다.
TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); manager.getDeviceId(); |
하지만 TelephonyManager 를 통한 방법은 아래와 같은 이슈가 있다.
| Non - Phone : 핸드폰 번호를 갖고 있지 않는 태블릿 이나 Wifi 만을 제공 하는 디바이스는 TelephonyManager 를 통한
디바이스 번호를 획득하지 못 할 수도 있다.
| Persistence : 한번 생성된 번호에 대한 지속 여부를 보장 할 수 가 없다. 디바이스가 공장 초기화 될 경우를 예로 들 수 있다.
뿐만 아니라, 현재 까지 몇몇 버그들이 report 되어 왔다. 안드로이드 디바이스는 제조사 마다
다양하게 커스터마이징 하기 때문에 000000000000000 같이 의미 없는 값이나 null 이 반환 되기도 한다.
| Privilege : 디바이스에 접근 하기 위해 READ_PHONE_STATE 권한 설정이 필요 하다.
Google API에서는 위 메소드(getDeviceId())를 이렇게 설명하고 있지만 실제로 USIM이 존재 하더라도 디바이스 ID 값이 넘어오지 않는
경우가 있다. (갤럭시 S 초창기 모델 및 몇몇 기억도 안나는 LG 디바이스들..) 그리고 반대의 경우도 있다. (유심을 뺐는데도 디바이스 ID 값이 넘어오는 현상)
Mac address
Wifi 나 blue-tooth 를 사용 하는 디바이스에서 획득 가능 하다. 하지만 디바이스의 고유 번호를 획득 하기 위한 용도로는 추천 되지 않는다. Wifi 가 항상 켜져 있다고 보장 할 수 없을 뿐만 아니라 모든 기기가 고유번호를 반환 하는 것이 아니기 때문이다.
Serial Number
안드로이드의 API Level 9 (진저브레드 2.3) 이후 부터 제공 하는 고유 번호로서 TelephonyManager 클래스를 통한 획득 보다는 안전 하다고 할 수 있지만 2.3 미만의 Version 에서는 문제가 발생 할 수 가 있다.
API Level 9 부터 제공 하기 때문에 @SuppressLint("NewApi") 를 추가 해야 되기 때문에 아래와 같이 Java reflection을 통해 획득 하는 것이 좋다.
private static String getDeviceSerialNumber() { try { return (String) Build.class.getField("SERIAL").get(null); } catch (Exception ignored) { return null; } } |
ANDROID_ID
가장 명확한 방법이며, 디바이스가 최초 Boot 될때 생성 되는 64-bit 값이다. ANDROID_ID는 디바이스의 고유한 번호를 획득 하기 위해 가장 좋은 선택이 될 수 있다.
Settings.Secure.ANDROID_ID |
하지만 ANDROID_ID 또한 단점이 있는데, Proyo 2.2 이전 Version 에는 100% 디바이스 고유 번호를 획득 한다고는 보장 할 수 없으며 몇몇 Vendor 에서 출하된 디바이스에 동일한 고유 번호가 획득 된다는 버그가 report 됐다는 것이다.
결론
지금 까지 안드로이드 플랫폼에서 디바이스를 구별하기 위한 고유한 번호를 획득 하는 방법을 알아 보았는데, 물리적으로 100% 보장 할 수 없다는 것이고, 이로 인해 결코 쉽지 않은 일이라는 것을 알 수 있었다. 가장 좋은 방법은 ANDROID_ID를 통해 획득 하는 방법이며, 다른 해결책을 혼용 해 사용하는 것도 좋을 방법 일 것이다. 여기에 위에 나열된 예상 되는 이슈 들과 같은 만일의 사태에 대한 대비책을 만들어 놓는 것도 좋을 것 이다.
참조 : http://android-developers.blogspot.kr/2011/03/identifying-app-installations.html#uds-search-results
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();
}
}
※ 유니크한 디바이스 아이디값 만들기
1) UUID 사용
: 프로그램 재 설치시 값이 달라진다. (http://stackoverflow.com/questions/5088474/how-can-i-get-the-uuid-of-my-android-phone-in-an-application)
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(); } } |
import android.content.Context; import android.content.SharedPreferences;
public class DeviceUuidFactory { protected static final String PREFS_DEVICE_ID = "device_id"; public static String getDeviceUuid(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(); } } } }
} |
// 카카오 SDK 에서 사용했던(?) 메서드 (바로 위의 코드와 동일한 내용이다.) /*
*/ public static String getDeviceUUID(final Context context) { final SharedPreferencesCache cache = Session.getAppCache(); final String id = cache.getString(PROPERTY_DEVICE_ID);
UUID uuid = null; if (id != null) { uuid = UUID.fromString(id); } else { final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); 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); }
Bundle bundle = new Bundle(); bundle.putString(PROPERTY_DEVICE_ID, uuid.toString()); cache.save(bundle); }
return uuid.toString(); } |
2) 혼용해서 사용하기
예1)
// Null 체크 생량 - 예외처리 구현할 것! 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(); |
예2) Pseudo-Unique ID (http://www.pocketmagic.net/?p=1662)
// 이것은 확률적으로 같은 하드웨어와 같은 ROM IMAGE를 쓰지 않으면 동일 아이디가 발생 하지 않을 꺼라 하는데 쫌 찜찜하긴 하네요 ㅎ // 결국 종합적으로 따져본 결과 DEVICEID + PHONE NUMBER + MACADDRESS을 OR로 사용하기로 함. return "35" + // we make this look like a valid IMEI |
예3)
String m_szLongID = m_szImei + m_szDevIDShort + m_szAndroidID+ m_szWLANMAC + m_szBTMAC; // compute md5 MessageDigest m = null; try { m = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } m.update(m_szLongID.getBytes(),0,m_szLongID.length()); // get md5 bytes byte p_md5Data[] = m.digest(); // create a hex string String m_szUniqueID = new String(); for (int i=0;i<p_md5Data.length;i++) { int b = (0xFF & p_md5Data[i]); // if it is a single digit, make sure it have 0 in front (proper padding) if (b <= 0xF) { m_szUniqueID += "0"; } // add number to string m_szUniqueID+=Integer.toHexString(b); } // hex string to uppercase m_szUniqueID = m_szUniqueID.toUpperCase(); |
3) 버전 분기처리해서 사용하기
예) 2.2 이전 버전으로는 TelephonyManager.getDeviceId(); 를 통해서 가져오고 2.2 이후 버전에서는 Settings.Secure.ANDROID_ID 로 가져오는
방법을 사용
4) OpenUDID 라이브러리 사용
- iOS / MacOS code: https://github.com/ylechelle/OpenUDID - Android code: https://github.com/vieux/OpenUDID
(참고자료 1) 안드로이드 개별 디바이스를 번역하는 방법 (안드로이드 공식 블로그의 번역)
http://huewu.blog.me/110107222113
(참고자료 2) IMEI, MEID, ESN, IMSI 등에 대해 자세히 나옴
http://www.webs.co.kr/index.php?mid=adnroid&sort_index=readed_count&order_type=asc&page=2&document_srl=38470
(참고자료 3) Stack Overflow 의 방대한 문답들
http://stackoverflow.com/questions/2785485/is-there-a-unique-android-device-id
(참고자료 4) 카카오톡 안드로이드 SDK
https://developers.kakao.com/docs/android
'IT_Programming > Android_Java' 카테고리의 다른 글
[펌] 구글 Advertising ID로 새롭게 시작. 그 변화에 맞춰 알아야 할 모든 것! (0) | 2015.10.23 |
---|---|
안드로이드 6.0 마시멜로 런타임 권한 적용하기 (0) | 2015.10.21 |
[펌] Bitmap.recycle()은 가급적 호출해 주는 편이 좋다. (Android 2.x 버전) (0) | 2015.09.15 |
[펌] 안드로이드 텍스트 뷰에서 지원하는 HTML 태그들 (0) | 2015.09.10 |
[펌] 안드로이드 오픈지엘(OpenGL ES2.0)의 기본 (0) | 2015.09.08 |