現存問題:
Android 獲取設備唯一值,由於 國內rom 不同,一直沒有一個 一個穩定的唯一標示。
權限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
常用的信息
-
imei Sim Serial Number
-
Android_id Android ID
-
mac 地址 mac address
-
CPUSerial cpu sn
-
SerialNumber
-
Installtion ID
IMEI
獲取權限<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
獲取方法
public static String getIMEI(Context context) {
TelephonyManager TelephonyMgr = (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
return "";
}
String szImei = TelephonyMgr.getDeviceId();
return szImei;
}
如果 6.0 以上需要動態權限,發現在 定製設備上,這個爲空。手機上一般存在。這個值可能有手機卡的 Android 設備纔會有;如果沒有手機卡,支持電話可能會沒有;目前看唯一;
ANDROID_ID
在設備首次啓動時,系統會隨機生成一個64位的數字,並把這個數字以16進制字符串的形式保存下來,這個16進制的字符串就是ANDROID_ID,當設備被wipe後該值會被重置。可以通過下面的方法獲取:
String m_szAndroidID = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ANDROID_ID);
Android_id是不需要權限,但它跟手機rom和手機廠商有關(Android_id是設備首次運行隨機生成的64位數字)有點手機是獲取不到,恢復出廠設置時也會改變,可靠性也較差
在Android 8.0 中針對Android ID有一個比較大的變化,8.0開始不同簽名的App 會產生不同的Android ID,這對於使用AndroidID來追蹤設備的用戶來說影響比較大
MAC ADDRESS
可以使用手機Wifi或藍牙的MAC地址作爲設備標識,但是並不推薦這麼做,原因有以下兩點:
-
硬件限制:並不是所有的設備都有Wifi和藍牙硬件,硬件不存在自然也就得不到這一信息。
-
獲取的限制:如果Wifi沒有打開過,是無法獲取其Mac地址的;而藍牙是隻有在打開的時候才能獲取到其Mac地址。
獲取Wifi Mac地址:
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
public class MacUtils {
/**
* 獲取失敗默認返回值
*/
public static final String ERROR_MAC_STR = "02:00:00:00:00:00";
// Wifi 管理器
private static WifiManager mWifiManager;
/**
* 實例化WifiManager對象
*
* @param context 當前上下文對象
* @return
*/
private static WifiManager getInstant(Context context) {
if (mWifiManager == null) {
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
return mWifiManager;
}
/**
* 開啓wifi
*/
public static void getStartWifiEnabled() {
// 判斷當前wifi狀態是否爲開啓狀態
if (!mWifiManager.isWifiEnabled()) {
// 打開wifi 有些設備需要授權
mWifiManager.setWifiEnabled(true);
}
}
/**
* 獲取手機設備MAC地址
* MAC地址:物理地址、硬件地址,用來定義網絡設備的位置
* modify by heliquan at 2018年1月17日
*
* @param context
* @return
*/
public static String getMobileMAC(Context context) {
mWifiManager = getInstant(context);
// 如果當前設備系統大於等於6.0 使用下面的方法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return getAndroidHighVersionMac();
} else { // 當前設備在6.0以下
return getAndroidLowVersionMac(mWifiManager);
}
}
/**
* Android 6.0 設備兼容獲取mac
* 兼容原因:從Android 6.0之後,Android 移除了通過WiFi和藍牙API來在應用程序中可編程的訪問本地硬件標示符。
* 現在WifiInfo.getMacAddress()和BluetoothAdapter.getAddress()方法都將返回:02:00:00:00:00:00
*
* @return
*/
public static String getAndroidHighVersionMac() {
String str = "";
String macSerial = "";
try {
// 由於Android底層基於Linux系統 可以根據shell獲取
Process pp = Runtime.getRuntime().exec(
"cat /sys/class/net/wlan0/address ");
InputStreamReader ir = new InputStreamReader(pp.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
for (; null != str; ) {
str = input.readLine();
if (str != null) {
macSerial = str.trim();// 去空格
break;
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
if (macSerial == null || "".equals(macSerial)) {
try {
return loadFileAsString("/sys/class/net/eth0/address")
.toUpperCase().substring(0, 17);
} catch (Exception e) {
e.printStackTrace();
macSerial = getAndroidVersion7MAC();
}
}
return macSerial;
}
/**
* Android 6.0 以下設備獲取mac地址 獲取失敗默認返回:02:00:00:00:00:00
*
* @param wifiManager
* @return
*/
@NonNull
private static String getAndroidLowVersionMac(WifiManager wifiManager) {
try {
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String mac = wifiInfo.getMacAddress();
if (TextUtils.isEmpty(mac)) {
return ERROR_MAC_STR;
} else {
return mac;
}
} catch (Exception e) {
e.printStackTrace();
Log.e("mac", "get android low version mac error:" + e.getMessage());
return ERROR_MAC_STR;
}
}
/**
* 兼容7.0獲取不到的問題
*
* @return
*/
public static String getAndroidVersion7MAC() {
try {
List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
if (!nif.getName().equalsIgnoreCase("wlan0")) continue;
byte[] macBytes = nif.getHardwareAddress();
if (macBytes == null) {
return "";
}
StringBuilder res1 = new StringBuilder();
for (byte b : macBytes) {
res1.append(String.format("%02X:", b));
}
if (res1.length() > 0) {
res1.deleteCharAt(res1.length() - 1);
}
return res1.toString();
}
} catch (Exception e) {
Log.e("mac", "get android version 7.0 mac error:" + e.getMessage());
}
return ERROR_MAC_STR;
}
public static String loadFileAsString(String fileName) throws Exception {
FileReader reader = new FileReader(fileName);
String text = loadReaderAsString(reader);
reader.close();
return text;
}
public static String loadReaderAsString(Reader reader) throws Exception {
StringBuilder builder = new StringBuilder();
char[] buffer = new char[4096];
int readLength = reader.read(buffer);
while (readLength >= 0) {
builder.append(buffer, 0, readLength);
readLength = reader.read(buffer);
}
return builder.toString();
}
}
CPUSerial cpu sn
這個好像只有在root 過的設備纔可以取到 ,沒有root的設備,可能會空
public static String getCPUSerial() {
String str = "", strCPU = "", cpuAddress = "";
try {
// 讀取CPU信息
Process pp = Runtime. getRuntime().exec("cat /proc/cpuinfo");
InputStreamReader ir = new InputStreamReader(pp.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
// 查找CPU序列號
for ( int i = 1; i < 100; i++) {
str = input.readLine();
if (str != null) {
// 查找到序列號所在行
if (str.indexOf( "Serial") > -1) {
// 提取序列號
strCPU = str.substring(str.indexOf(":" ) + 1, str.length());
// 去空格
cpuAddress = strCPU.trim();
break;
}
} else {
// 文件結尾
break;
}
}
} catch (IOException ex) {
// 賦予默認值
ex.printStackTrace();
}
return cpuAddress;
}
SerialNumber 設備SN
具體測試,這個好像也只有root過的設備可以獲取到,手機不能獲取
String SerialNumber = android.os.Build.SERIAL;
Installtion ID
如果並不是確實需要對硬件本身進行綁定,使用自己生成的UUID也是一個不錯的選擇,因爲該方法無需訪問設備的資源,也跟設備類型無關。
這種方式的原理是在程序安裝後第一次運行時生成一個ID,該方式和設備唯一標識不一樣,不同的應用程序會產生不同的ID,同一個程序重新安裝也會不同。所 以這不是設備的唯一ID,但是可以保證每個用戶的ID是不同的。可以說是用來標識每一份應用程序的唯一ID(即Installtion ID),可以用來跟蹤應用的安裝數量等。
Google Developer Blog提供了這樣的一個框架:
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();
}
}
例:
生成文件保存在多媒體目錄下
private static final String saveFileName = "ADMObileDeviceIdFile";
/**
* 在媒體文件中 生成fileName文件
* 向Mediastore添加內容
*/
private void creatUUIDFile() {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, saveFileName);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
ContentResolver contentResolver = this.getContentResolver();
Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
try {
FileOutputStream outputStream = null;
//訪問 對於單個媒體文件,請使用 openFileDescriptor()。
if (deviceVersion >= 10) {
ParcelFileDescriptor fielDescriptor = contentResolver.openFileDescriptor(uri, "w", null);
outputStream = new FileOutputStream(fielDescriptor.getFileDescriptor());
} else {
File file = new File(filePath);
outputStream = new FileOutputStream(file);
}
try {
//講UUID寫入到文件中
String uuidStr = UUID.randomUUID().toString();
outputStream.write(uuidStr.getBytes());
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
contentResolver.update(uri, values, null, null);
values.clear();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 將內容選擇編碼格式輸出
*
* @param inputStream
* @return
*/
private static String inputStreamToString(InputStream inputStream) {
InputStreamReader inputStreamReader = null;
try {
inputStreamReader = new InputStreamReader(inputStream, "utf-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
BufferedReader reader = new BufferedReader(inputStreamReader);
StringBuffer sb = new StringBuffer("");
String line;
try {
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
/**
* 檢查文件是否存在
*
* @return
*/
private String checkUUIDFileByUri() {
String[] projection = {
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media._ID
};
//查詢
ContentResolver contentResolver = this.getContentResolver();
// 添加篩選條件,根據SQLite表裏的 display_name是否與saveFileName一直
String selection = MediaStore.Images.Media.DISPLAY_NAME + "=" + "'" + saveFileName + "'";
//EXTERNAL_CONTENT_URI 爲查詢外置內存卡
Cursor mCursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, null, null);
String getSaveContent = "";
if (mCursor != null) {
while (mCursor.moveToNext()) {
int fileIdIndex = mCursor.getColumnIndex(MediaStore.Images.Media._ID);
String thumbPath = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()
.appendPath(String.valueOf(mCursor.getInt(fileIdIndex))).build().toString();
Uri fileUri = Uri.parse(thumbPath);
FileInputStream inputStream = null;
try {
if (deviceVersion >= 10) {
ParcelFileDescriptor fielDescriptor = contentResolver.openFileDescriptor(fileUri, "r", null);
inputStream = new FileInputStream(fielDescriptor.getFileDescriptor());
} else {
File file = new File(filePath);
inputStream = new FileInputStream(file);
}
getSaveContent = inputStreamToString(inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//只有在得到的唯一標識符不爲空的情況下才結束循環
if (!TextUtils.isEmpty(getSaveContent)) {
break;
}
}
mCursor.close();
}
return getSaveContent;
}
總結:
一般的情況會前面所有的,加上最install id(或者類似的id)結合使用,並且隨着Android 系統更新,還需要不斷的升級。