APP在不使用第三方的情況下,為了統(tǒng)計(jì)往往需要手機(jī)的唯一標(biāo)識,在Android10之前通過權(quán)限是可以獲取手機(jī)的IMEI號的,
需要權(quán)限android.permission.READ_PHONE_STATE,Android10及其以上即使打開權(quán)限無法獲取手機(jī)的IMEI;
所以AndroidId成為了備選項(xiàng),而且不需要什么權(quán)限。
/**
* 獲得設(shè)備的AndroidId
* @param context 上下文
* @return 設(shè)備的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);
}
但是這個(gè)是不靠譜的,因?yàn)橛袝r(shí)候它是null的,文檔中明確說明,如果你恢復(fù)了出廠設(shè)置或者root了手機(jī),那它就會改變的。
還有就是部分設(shè)備由于制造商錯(cuò)誤實(shí)現(xiàn),導(dǎo)致多臺設(shè)備會返回相同的 Android_ID.
解決方案
新增文件方式存儲到外部目錄,免得App卸載也會刪除對應(yīng)文件夾
1.新建一個(gè)文件夾在/storage/documents/0/下面,并在文件夾里生成一個(gè)deviceInfo.txt,存儲內(nèi)容為(ANDROID_ID+時(shí)間戳)
2.對這個(gè)文件夾/storage/documents/0/lastfun/deviceInfo.txt 進(jìn)行是否存在文件的判斷
有文件就讀取使用,取出來使用即可,
沒有那就新建一個(gè)即可,下面是詳細(xì)步驟
首先檢測外部目錄可用
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
在Android 10及更高版本中,由于引入了存儲權(quán)限變更,訪問外部存儲的方式發(fā)生了改變,建議使用ContentResolver和Uri來訪問文件,以適應(yīng)存儲權(quán)限的變更。所以為了適配做了分別處理
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 及以上版本執(zhí)行的代碼
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 以下版本執(zhí)行的代碼
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 {
// 文件不存在,創(chuàng)建并保存
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 {
// 創(chuàng)建 FileReader 對象
FileReader reader = new FileReader(filePath);
// 創(chuàng)建 BufferedReader 對象
BufferedReader bufferedReader = new BufferedReader(reader);
// 讀取文件中的字符串?dāng)?shù)據(jù)
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
// 關(guān)閉 BufferedReader
bufferedReader.close();
return stringBuilder.toString();
}
private static String createAndSaveDeviceId(Context context, String filePath) throws IOException {
File file = new File(filePath);
file.createNewFile();
// 文件創(chuàng)建成功
FileWriter writer = new FileWriter(file);
// 將字符串寫入文件
String data = getAndroidId(context) + System.currentTimeMillis();
writer.write(data);
// 關(guān)閉 FileWriter
writer.close();
// 字符串已成功寫入文件
return data;
}
//app卸載后,此方法無法讀取之前創(chuàng)建的文件
@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);
}
//使用數(shù)據(jù)庫創(chuàng)建緩存,直接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卸載后,無法讀取之前創(chuàng)建的文件,可能是.text文件綁定了APP吧
所以單獨(dú)處理Android 以上的讀取操作,使用圖片的存儲是可以的,
具體操作,在本地保存一張很小的圖片,名字按照之前的生成方式一樣,如果存在就讀取名字,如果不存在就創(chuàng)建,完美解決,
具體代碼
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 以下版本執(zhí)行的代碼
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 {
// 文件不存在,創(chuàng)建并保存
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<>();
// 構(gòu)建指定文件夾的File對象
File folder = new File(directoryPath);
// 檢查文件夾是否存在并且是一個(gè)目錄
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 {
// 創(chuàng)建 FileReader 對象
FileReader reader = new FileReader(filePath);
// 創(chuàng)建 BufferedReader 對象
BufferedReader bufferedReader = new BufferedReader(reader);
// 讀取文件中的字符串?dāng)?shù)據(jù)
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
// 關(guān)閉 BufferedReader
bufferedReader.close();
return stringBuilder.toString();
}
private static String createAndSaveDeviceId(Context context, String filePath) throws IOException {
File file = new File(filePath);
file.createNewFile();
// 文件創(chuàng)建成功
FileWriter writer = new FileWriter(file);
// 將字符串寫入文件
String data = getAndroidId(context) + System.currentTimeMillis();
writer.write(data);
// 關(guān)閉 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);
}
然后再次優(yōu)化下, Android 10一下也這樣處理,在性能最差的手機(jī)上,創(chuàng)建到讀出,也沒超過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及以上需要單獨(dú)讀寫權(quán)限,因?yàn)镚oogle新增了READ_MEDIA_IMAGES、READ_MEDIA_VIDEO和READ_MEDIA_AUDIO這3個(gè)運(yùn)行時(shí)權(quán)限,分別用于管理手機(jī)的照片、視頻和音頻文件。所以在讀取圖片的時(shí)候申請READ_MEDIA_IMAGES;