APP在不使用第三方的情况下,为了统计往往需要手机的唯一标识,在Android10之前通过权限是可以获取手机的IMEI号的,
需要权限android.permission.READ_PHONE_STATE,Android10及其以上即使打开权限无法获取手机的IMEI;
所以AndroidId成为了备选项,而且不需要什么权限。
/**
* 获得设备的AndroidId
* @param context 上下文
* @return 设备的AndroidId
*/
public static String getAndroidId(Context context) {
try {
return Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ANDROID_ID);
} catch (Exception ex) {
ex.printStackTrace();
}
return getDeviceId(context);
}
但是这个是不靠谱的,因为有时候它是null的,文档中明确说明,如果你恢复了出厂设置或者root了手机,那它就会改变的。
还有就是部分设备由于制造商错误实现,导致多台设备会返回相同的 Android_ID.
解决方案
新增文件方式存储到外部目录,免得App卸载也会删除对应文件夹
1.新建一个文件夹在/storage/documents/0/下面,并在文件夹里生成一个deviceInfo.txt,存储内容为(ANDROID_ID+时间戳)
2.对这个文件夹/storage/documents/0/lastfun/deviceInfo.txt 进行是否存在文件的判断
有文件就读取使用,取出来使用即可,
没有那就新建一个即可,下面是详细步骤
首先检测外部目录可用
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
在Android 10及更高版本中,由于引入了存储权限变更,访问外部存储的方式发生了改变,建议使用ContentResolver和Uri来访问文件,以适应存储权限的变更。所以为了适配做了分别处理
public static String getNewDeviceId(Context context) {
String data = getAndroidId(context);
Log.v("=======oldDeviceId", data);
if (isExternalStorageWritable()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// // 在 Android 10 及以上版本执行的代码
try {
File folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), "lastfun");
if (!folder.exists()) {
folder.mkdirs();
}
File timestampFile = new File(folder.getAbsolutePath(), "deviceInfo.txt");
if (timestampFile.exists()) {
// If the file exists, read the timestamp from it
data = readTimestampFromFile(context,timestampFile);
Log.v("=======ReadDeviceId", data);
} else {
// If the file doesn't exist, create it and write the current timestamp
data = createTimestampFile(context, timestampFile);
Log.v("=======saveDeviceId", data);
}
} catch (Exception e) {
Log.v("=======error", e.getMessage());
}
} else {
// 在 Android 10 以下版本执行的代码
String path = Environment.getExternalStorageDirectory() + "/lastfun/";
File appDir = new File(path);
if (!appDir.exists()) {
appDir.mkdirs();
}
File file = new File(appDir, "deviceInfo.txt");
try {
if (file.exists()) {
data = readDeviceIdFromFile(file.getAbsolutePath());
Log.v("=======ReadDeviceId", data);
} else {
// 文件不存在,创建并保存
data = createAndSaveDeviceId(context, file.getAbsolutePath());
Log.v("=======saveDeviceId", data);
}
} catch (IOException e) {
Log.v("=======error", e.getMessage());
}
}
}
return data.isEmpty() ? getAndroidId(context) : data;
}
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
private static String readDeviceIdFromFile(String filePath) throws IOException {
// 创建 FileReader 对象
FileReader reader = new FileReader(filePath);
// 创建 BufferedReader 对象
BufferedReader bufferedReader = new BufferedReader(reader);
// 读取文件中的字符串数据
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
// 关闭 BufferedReader
bufferedReader.close();
return stringBuilder.toString();
}
private static String createAndSaveDeviceId(Context context, String filePath) throws IOException {
File file = new File(filePath);
file.createNewFile();
// 文件创建成功
FileWriter writer = new FileWriter(file);
// 将字符串写入文件
String data = getAndroidId(context) + System.currentTimeMillis();
writer.write(data);
// 关闭 FileWriter
writer.close();
// 字符串已成功写入文件
return data;
}
//app卸载后,此方法无法读取之前创建的文件
@RequiresApi(api = Build.VERSION_CODES.Q)
private static String readTimestampFromFile(Context context, File file) {
try (InputStream inputStream = context.getContentResolver().openInputStream(Uri.fromFile(file))) {
if (inputStream != null) {
int size = inputStream.available();
byte[] bytes = new byte[size];
inputStream.read(bytes);
return new String(bytes);
}
} catch (IOException e) {
e.printStackTrace();
Log.v("=======error", e.getMessage());
}
return "";
}
private static String createTimestampFile(Context context, File file) {
String data = getAndroidId(context) + System.currentTimeMillis();
try (OutputStream outputStream = context.getContentResolver().openOutputStream(Uri.fromFile(file))) {
if (outputStream != null) {
outputStream.write(data.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
public static String getAndroidId(Context context) {
try {
return Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ANDROID_ID);
} catch (Exception ex) {
ex.printStackTrace();
}
return getDeviceId(context);
}
//使用数据库创建缓存,直接app没卸载都是可以读到的
public static String getDeviceId(Context context) {
String deviceId = "";
if (DeviceUtils.getInstance().getList().size() > 0) {
} else {
Device device = new Device();
device.setDeviceId(String.valueOf(System.currentTimeMillis()));
DeviceUtils.getInstance().insert(device);
}
deviceId = DeviceUtils.getInstance().getList().get(0).deviceId;
return deviceId;
}
这里遇到的问题是:Android 10及更高版本,APP卸载后,无法读取之前创建的文件,可能是.text文件绑定了APP吧
所以单独处理Android 以上的读取操作,使用图片的存储是可以的,
具体操作,在本地保存一张很小的图片,名字按照之前的生成方式一样,如果存在就读取名字,如果不存在就创建,完美解决,
具体代码
public static String getBackDeviceId(Context context) {
String data = getAndroidId(context);
Log.v("=======oldDeviceId", data);
if (isExternalStorageWritable()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try {
String directoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/netimaga";
File appDir = new File(directoryPath);
if (!appDir.exists()) {
appDir.mkdirs();
}
List<String> imageList = readImagesFromFolder(directoryPath);
if (!imageList.isEmpty()) {
data = imageList.get(0).replace(".jpg", "");
Log.v("=======ReadDeviceId", data);
} else {
data = saveImagesToFolder(context, appDir).replace(".jpg", "");
Log.v("=======SaveDeviceId", data);
}
} catch (Exception e) {
Log.v("=======error", e.getMessage());
}
} else {
// 在 Android 10 以下版本执行的代码
String path = Environment.getExternalStorageDirectory() + "/netimaga/";
File appDir = new File(path);
if (!appDir.exists()) {
appDir.mkdirs();
}
File file = new File(appDir, "deviceInfo.txt");
try {
if (file.exists()) {
data = readDeviceIdFromFile(file.getAbsolutePath());
Log.v("=======ReadDeviceId", data);
} else {
// 文件不存在,创建并保存
data = createAndSaveDeviceId(context, file.getAbsolutePath());
Log.v("=======saveDeviceId", data);
}
} catch (IOException e) {
Log.v("=======error", e.getMessage());
}
}
}
return data.isEmpty() ? getAndroidId(context) : data;
}
public static ArrayList<String> readImagesFromFolder(String directoryPath) {
ArrayList<String> imageList = new ArrayList<>();
// 构建指定文件夹的File对象
File folder = new File(directoryPath);
// 检查文件夹是否存在并且是一个目录
if (folder.exists() && folder.isDirectory()) {
// 获取文件夹中的所有文件
File[] files = folder.listFiles();
if (files != null) {
// 遍历文件夹中的所有文件,并将图片文件的路径添加到列表中
for (File file : files) {
Log.v("=======image==", file.getName());
imageList.add(file.getName());
}
}
}
return imageList;
}
public static String saveImagesToFolder(Context context, File appDir) {
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.logo);
String data = getAndroidId(context) + System.currentTimeMillis() + ".jpg";
File file = new File(appDir, data);
try {
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
Log.v("=======SaveSuccess", data);
} catch (Exception e) {
Log.v("=======SaveError", e.getMessage());
}
return data;
}
private static String readDeviceIdFromFile(String filePath) throws IOException {
// 创建 FileReader 对象
FileReader reader = new FileReader(filePath);
// 创建 BufferedReader 对象
BufferedReader bufferedReader = new BufferedReader(reader);
// 读取文件中的字符串数据
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
// 关闭 BufferedReader
bufferedReader.close();
return stringBuilder.toString();
}
private static String createAndSaveDeviceId(Context context, String filePath) throws IOException {
File file = new File(filePath);
file.createNewFile();
// 文件创建成功
FileWriter writer = new FileWriter(file);
// 将字符串写入文件
String data = getAndroidId(context) + System.currentTimeMillis();
writer.write(data);
// 关闭 FileWriter
writer.close();
// 字符串已成功写入文件
return data;
}
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
public static String getAndroidId(Context context) {
try {
return Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ANDROID_ID);
} catch (Exception ex) {
ex.printStackTrace();
}
return getDeviceId(context);
}
然后再次优化下, Android 10一下也这样处理,在性能最差的手机上,创建到读出,也没超过100毫秒
public static String getNewDeviceId(Context context) {
String data = getAndroidId(context);
Log.v("=======oldDeviceId", data);
if (isExternalStorageWritable()) {
String path = Environment.getExternalStorageDirectory() + "/netimaga/";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/netimaga";
}
try {
File appDir = new File(path);
if (!appDir.exists()) {
appDir.mkdirs();
}
List<String> imageList = readImagesFromFolder(path);
if (!imageList.isEmpty()) {
data = imageList.get(0).replace(".jpg", "");
Log.v("=======ReadDeviceId", data);
} else {
data = saveImagesToFolder(context, appDir).replace(".jpg", "");
Log.v("=======SaveDeviceId", data);
}
} catch (Exception e) {
Log.v("=======error", e.getMessage());
}
}
return data.isEmpty() ? getAndroidId(context) : data;
}
注意
ndroid 13及以上需要单独读写权限,因为Google新增了READ_MEDIA_IMAGES、READ_MEDIA_VIDEO和READ_MEDIA_AUDIO这3个运行时权限,分别用于管理手机的照片、视频和音频文件。所以在读取图片的时候申请READ_MEDIA_IMAGES;