现存问题:
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 系统更新,还需要不断的升级。