Tôi cần sử dụng ID duy nhất cho ứng dụng Android và tôi nghĩ số sê-ri cho thiết bị sẽ là một ứng cử viên sáng giá. Làm cách nào để truy xuất số sê-ri của thiết bị Android trong ứng dụng của tôi?
Tôi cần sử dụng ID duy nhất cho ứng dụng Android và tôi nghĩ số sê-ri cho thiết bị sẽ là một ứng cử viên sáng giá. Làm cách nào để truy xuất số sê-ri của thiết bị Android trong ứng dụng của tôi?
Câu trả lời:
TelephonyManager tManager = (TelephonyManager)myActivity.getSystemService(Context.TELEPHONY_SERVICE);
String uid = tManager.getDeviceId();
getSystemService là một phương thức từ lớp Activity. getDeviceID () sẽ trả về MDN hoặc MEID của thiết bị tùy thuộc vào loại điện thoại mà điện thoại sử dụng (GSM hoặc CDMA).
Mỗi thiết bị PHẢI trả về một giá trị duy nhất tại đây (giả sử đó là điện thoại). Điều này sẽ hoạt động cho mọi thiết bị Android có khe cắm sim hoặc radio CDMA. Bạn đang ở một mình với lò vi sóng chạy Android đó ;-)
Như Dave Webb đã đề cập, Blog Nhà phát triển Android có một bài viết đề cập đến vấn đề này.
Tôi đã nói chuyện với ai đó tại Google để có thêm một số giải thích rõ ràng về một vài mặt hàng. Đây là những gì tôi phát hiện ra rằng KHÔNG được đề cập trong bài viết trên blog đã nói ở trên:
Dựa trên các đề xuất của Google, tôi đã triển khai một lớp sẽ tạo ra một UUID duy nhất cho mỗi thiết bị, sử dụng ANDROID_ID làm hạt giống khi thích hợp, quay lại TelephonyManager.getDeviceId () nếu cần, và nếu không, hãy sử dụng UUID duy nhất được tạo ngẫu nhiên được duy trì trên toàn bộ ứng dụng khởi động lại (nhưng không cài đặt lại ứng dụng).
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 static volatile 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;
}
}
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) {
}
Mã này trả về số sê-ri thiết bị bằng API Android ẩn.
String deviceId = Settings.System.getString(getContentResolver(),
Settings.System.ANDROID_ID);
Mặc dù, không đảm bảo rằng Android ID sẽ là một định danh duy nhất.
getContentResolver
là trở về null
. Tuy nhiên, nó có thể có giá trị trong khi mở một câu hỏi và đăng mã của bạn.
Có một bài viết tuyệt vời trên Blog của Nhà phát triển Android thảo luận về điều này .
Nó khuyên bạn không nên sử dụng TelephonyManager.getDeviceId()
vì nó không hoạt động trên các thiết bị Android không phải là điện thoại như máy tính bảng, nó cần có sự READ_PHONE_STATE
cho phép và nó không hoạt động đáng tin cậy trên tất cả các điện thoại.
Thay vào đó, bạn có thể sử dụng một trong những điều sau đây:
Bài viết thảo luận về ưu và nhược điểm của từng loại và nó đáng để đọc để bạn có thể tìm ra cái nào là tốt nhất cho việc sử dụng của bạn.
Đối với một số đơn giản là duy nhất cho thiết bị và không đổi trong suốt vòng đời của thiết bị (chặn cài đặt gốc hoặc hack), hãy sử dụng Settings.Secure.ANDROID_ID .
String id = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
Để sử dụng số sê-ri của thiết bị (số được hiển thị trong "Cài đặt hệ thống / Giới thiệu / Trạng thái") nếu có và quay lại ID Android:
String serialNumber = Build.SERIAL != Build.UNKNOWN ? Build.SERIAL : Secure.getString(getContentResolver(), Secure.ANDROID_ID);
IMEI tốt nhưng chỉ hoạt động trên thiết bị Android có điện thoại. Bạn cũng nên xem xét hỗ trợ cho Máy tính bảng hoặc các thiết bị Android khác, không có điện thoại.
Bạn có một số lựa chọn thay thế như: Xây dựng thành viên lớp, BT MAC, WLAN MAC hoặc thậm chí tốt hơn - kết hợp tất cả những thứ này.
Tôi đã giải thích những chi tiết này trong một bài viết trên blog của mình, xem: http://www.pocketmagic.net/?p=1662
Vì không có câu trả lời nào ở đây đề cập đến một ID hoàn hảo, không chứng minh được cả PERSISTENT thông qua các bản cập nhật hệ thống và tồn tại trong TẤT CẢ các thiết bị (chủ yếu là do không có giải pháp riêng lẻ nào từ Google), tôi đã quyết định đăng một phương pháp điều tốt nhất tiếp theo bằng cách kết hợp hai trong số các định danh có sẵn và kiểm tra để chọn giữa chúng trong thời gian chạy.
Trước mã, 3 sự thật:
TelephonyManager.getDeviceId()
(akaIMEI) sẽ không hoạt động tốt hoặc hoàn toàn cho các thiết bị không phải GSM, 3G, LTE, v.v., nhưng sẽ luôn trả về một ID duy nhất khi có phần cứng liên quan , ngay cả khi không có SIM nào được lắp hoặc ngay cả khi không có khe cắm SIM ( một số OEM đã làm điều này).
Vì Gingerbread (Android 2.3) android.os.Build.SERIAL
phải tồn tại trên bất kỳ thiết bị nào không cung cấp IMEI , tức là, không có phần cứng nói trên, theo chính sách của Android.
Do thực tế (2.), ít nhất một trong hai số nhận dạng duy nhất này LUÔN LUÔN có mặt và SERIAL có thể có mặt cùng lúc với IMEI.
Lưu ý: Fact (1.) và (2.) dựa trên các tuyên bố của Google
GIẢI PHÁP
Với các sự kiện ở trên, người ta luôn có thể có một mã định danh duy nhất bằng cách kiểm tra xem có phần cứng ràng buộc IMEI hay không và quay lại SERIAL khi không, vì người ta không thể kiểm tra xem SERIAL hiện tại có hợp lệ không. Lớp tĩnh sau đây trình bày 2 phương thức để kiểm tra sự hiện diện đó và sử dụng IMEI hoặc SERIAL:
import java.lang.reflect.Method;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.Log;
public class IDManagement {
public static String getCleartextID_SIMCHECK (Context mContext){
String ret = "";
TelephonyManager telMgr = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if(isSIMAvailable(mContext,telMgr)){
Log.i("DEVICE UNIQUE IDENTIFIER",telMgr.getDeviceId());
return telMgr.getDeviceId();
}
else{
Log.i("DEVICE UNIQUE IDENTIFIER", Settings.Secure.ANDROID_ID);
// return Settings.Secure.ANDROID_ID;
return android.os.Build.SERIAL;
}
}
public static String getCleartextID_HARDCHECK (Context mContext){
String ret = "";
TelephonyManager telMgr = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if(telMgr != null && hasTelephony(mContext)){
Log.i("DEVICE UNIQUE IDENTIFIER",telMgr.getDeviceId() + "");
return telMgr.getDeviceId();
}
else{
Log.i("DEVICE UNIQUE IDENTIFIER", Settings.Secure.ANDROID_ID);
// return Settings.Secure.ANDROID_ID;
return android.os.Build.SERIAL;
}
}
public static boolean isSIMAvailable(Context mContext,
TelephonyManager telMgr){
int simState = telMgr.getSimState();
switch (simState) {
case TelephonyManager.SIM_STATE_ABSENT:
return false;
case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
return false;
case TelephonyManager.SIM_STATE_PIN_REQUIRED:
return false;
case TelephonyManager.SIM_STATE_PUK_REQUIRED:
return false;
case TelephonyManager.SIM_STATE_READY:
return true;
case TelephonyManager.SIM_STATE_UNKNOWN:
return false;
default:
return false;
}
}
static public boolean hasTelephony(Context mContext)
{
TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null)
return false;
//devices below are phones only
if (Build.VERSION.SDK_INT < 5)
return true;
PackageManager pm = mContext.getPackageManager();
if (pm == null)
return false;
boolean retval = false;
try
{
Class<?> [] parameters = new Class[1];
parameters[0] = String.class;
Method method = pm.getClass().getMethod("hasSystemFeature", parameters);
Object [] parm = new Object[1];
parm[0] = "android.hardware.telephony";
Object retValue = method.invoke(pm, parm);
if (retValue instanceof Boolean)
retval = ((Boolean) retValue).booleanValue();
else
retval = false;
}
catch (Exception e)
{
retval = false;
}
return retval;
}
}
Tôi sẽ tư vấn về việc sử dụng getCleartextID_HARDCHECK
. Nếu sự phản chiếu không dính vào môi trường của bạn, getCleartextID_SIMCHECK
thay vào đó hãy sử dụng phương pháp, nhưng hãy xem xét nó phải phù hợp với nhu cầu hiện diện SIM cụ thể của bạn.
PS : Xin lưu ý rằng các OEM đã tìm cách khắc phục SERIAL chống lại chính sách của Google (nhiều thiết bị có cùng SERIAL) và Google như đã nêu có ít nhất một trường hợp được biết đến trong một OEM lớn (không được tiết lộ và tôi không biết thương hiệu nào đó là một trong hai, tôi đoán Samsung).
Tuyên bố miễn trừ trách nhiệm : Điều này trả lời câu hỏi ban đầu về việc nhận ID thiết bị duy nhất, nhưng OP đã đưa ra sự mơ hồ bằng cách nói rằng anh ta cần một ID duy nhất cho một ứng dụng. Ngay cả khi với các kịch bản như vậy, Android_ID sẽ tốt hơn, nó sẽ KHÔNG LÀM VIỆC sau khi giả sử Titanium Backup của một ứng dụng thông qua 2 lần cài đặt ROM khác nhau (thậm chí có thể là cùng một ROM). Giải pháp của tôi duy trì sự bền bỉ không phụ thuộc vào flash hoặc khôi phục cài đặt gốc và sẽ chỉ thất bại khi xảy ra giả mạo IMEI hoặc SERIAL thông qua hack / mod phần cứng.
Có vấn đề với tất cả các phương pháp trên. Tại Google, i / o Reto Meier đã đưa ra một câu trả lời mạnh mẽ về cách tiếp cận vấn đề này sẽ đáp ứng hầu hết các nhu cầu của nhà phát triển để theo dõi người dùng qua các cài đặt.
Cách tiếp cận này sẽ cung cấp cho bạn ID người dùng an toàn, ẩn danh, sẽ tồn tại lâu dài cho người dùng trên các thiết bị khác nhau (bao gồm cả máy tính bảng, dựa trên tài khoản Google chính) và trên các cài đặt trên cùng một thiết bị. Cách tiếp cận cơ bản là tạo ID người dùng ngẫu nhiên và lưu trữ tệp này trong các tùy chọn chia sẻ ứng dụng. Sau đó, bạn sử dụng tác nhân sao lưu của Google để lưu trữ các tùy chọn được chia sẻ được liên kết với tài khoản Google trên đám mây.
Hãy đi qua cách tiếp cận đầy đủ. Trước tiên, chúng tôi cần tạo bản sao lưu cho SharedPreferences bằng Dịch vụ sao lưu Android. Bắt đầu bằng cách đăng ký ứng dụng của bạn thông qua liên kết này: http://developer.android.com/google/backup/signup.html
Google sẽ cung cấp cho bạn khóa dịch vụ sao lưu mà bạn cần thêm vào bảng kê khai. Bạn cũng cần yêu cầu ứng dụng sử dụng BackupAgent như sau:
<application android:label="MyApplication"
android:backupAgent="MyBackupAgent">
...
<meta-data android:name="com.google.android.backup.api_key"
android:value="your_backup_service_key" />
</application>
Sau đó, bạn cần tạo tác nhân sao lưu và bảo nó sử dụng tác nhân trợ giúp cho các cuộc họp chung:
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);
}
}
Để hoàn thành bản sao lưu, bạn cần tạo một phiên bản của BackupManager trong Hoạt động chính của mình:
BackupManager backupManager = new BackupManager(context);
Cuối cùng tạo ID người dùng, nếu nó chưa tồn tại và lưu nó trong 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 này bây giờ sẽ liên tục trong các cài đặt, ngay cả khi người dùng chuyển đổi thiết bị.
Để biết thêm thông tin về cách tiếp cận này, hãy xem Reto nói chuyện tại đây http://www.google.com/events/io/2011/simes/android-protips-advified-topics-for-expert-android-app-developers.html
Và để biết chi tiết đầy đủ về cách triển khai tác nhân sao lưu, hãy xem trang web của nhà phát triển tại đây: http://developer.android.com/guide/topics/data/backup.html Tôi đặc biệt khuyên bạn nên kiểm tra phần dưới cùng để kiểm tra vì sao lưu không xảy ra tức thời và vì vậy để kiểm tra bạn phải buộc sao lưu.
Một cách khác là sử dụng / sys / class / android_usb / android0 / iSerial trong một Ứng dụng không có quyền gì.
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
Để làm điều này trong java, người ta chỉ cần sử dụng FileInputStream để mở tệp iSerial và đọc các ký tự. Chỉ cần chắc chắn rằng bạn bọc nó trong một trình xử lý ngoại lệ vì không phải tất cả các thiết bị đều có tệp này.
Ít nhất các thiết bị sau đây được biết là có tệp này có thể đọc được trên thế giới:
Bạn cũng có thể xem bài đăng trên blog của tôi ở đây: http://insitusec.blogspot.com/2013/01/leaking-android-hardware-serial-number.html nơi tôi thảo luận về những tệp khác có sẵn để biết thông tin.
ID thiết bị duy nhất của Thiết bị HĐH Android dưới dạng Chuỗi.
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);
}
nhưng tôi thực sự khuyên dùng phương pháp này được đề xuất bởi Google ::
Build.SERIAL
là cách đơn giản nhất để đi, mặc dù không hoàn toàn đáng tin cậy vì nó có thể trống hoặc đôi khi trả về một giá trị khác ( bằng chứng 1 , bằng chứng 2 ) so với những gì bạn có thể thấy trong cài đặt của thiết bị.
Có một số cách để có được số đó tùy thuộc vào nhà sản xuất thiết bị và phiên bản Android, vì vậy tôi quyết định biên dịch mọi giải pháp có thể tôi có thể tìm thấy trong một ý chính . Đây là phiên bản đơn giản hóa của nó:
public static String getSerialNumber() {
String serialNumber;
try {
Class<?> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class);
serialNumber = (String) get.invoke(c, "gsm.sn1");
if (serialNumber.equals(""))
serialNumber = (String) get.invoke(c, "ril.serialnumber");
if (serialNumber.equals(""))
serialNumber = (String) get.invoke(c, "ro.serialno");
if (serialNumber.equals(""))
serialNumber = (String) get.invoke(c, "sys.serialnumber");
if (serialNumber.equals(""))
serialNumber = Build.SERIAL;
// If none of the methods above worked
if (serialNumber.equals(""))
serialNumber = null;
} catch (Exception e) {
e.printStackTrace();
serialNumber = null;
}
return serialNumber;
}
Tôi biết câu hỏi này đã cũ nhưng nó có thể được thực hiện trong một dòng mã
String deviceID = Build.SERIAL;
Tôi thấy lớp ví dụ được đăng bởi @emmby ở trên là một điểm khởi đầu tuyệt vời. Nhưng nó có một vài sai sót, như được đề cập bởi các áp phích khác. Cái chính là nó duy trì UUID thành một tệp XML một cách không cần thiết và sau đó luôn lấy nó từ tệp này. Điều này đặt lớp mở cho một hack dễ dàng: bất kỳ ai có điện thoại đã root đều có thể chỉnh sửa tệp XML để tạo cho mình một UUID mới.
Tôi đã cập nhật mã để nó chỉ tồn tại thành XML nếu thực sự cần thiết (nghĩa là khi sử dụng UUID được tạo ngẫu nhiên) và xác nhận lại logic theo câu trả lời của @Brill Pappin:
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 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) || (androidId == null) ) {
final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId();
if (deviceId != null)
{
uuid = UUID.nameUUIDFromBytes(deviceId.getBytes("utf8"));
}
else
{
uuid = UUID.randomUUID();
// Write the value out to the prefs file so it persists
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString() ).commit();
}
}
else
{
uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
/**
* 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;
}
Đúng. Nó là một số sê-ri phần cứng thiết bị và nó là duy nhất. Vì vậy, ở cấp độ api 2.3 trở lên, bạn có thể sử dụng android.os.Build.ANDROID_ID để lấy nó. Đối với cấp độ API dưới 2,3, hãy sử dụng TelephonyManager.getDeviceID () .
bạn có thể đọc http://android-developers.blogspot.in/2011/03/identifying-app-installations.html này