admin 管理员组文章数量: 1184232
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(), "device");
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() + "/device/";
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;
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
版权声明:本文标题:Android 10及以上 设置唯一标识,APP卸载后还能读取 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1766108174a3437998.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论