取得电信网络和手机的相关信息之TelephonyManager与android.provider.settings.System

本文介绍了如何使用Android系统API获取设备的蓝牙、Wi-Fi及飞行模式等状态,并通过ContentResolver与Settings.System交互来读取这些系统设置信息。

      除了SIM卡的相关信息之外,我们可以使用TelephonyManager来获取电信网络的相关信息。例如电信网络国别、电信网络国别、代码、名称、网络类型等。

初次之外,我们要想获得蓝牙、无限网络等手机内置值,我们可以通过android.provider.settings.System来获取。

至于TelephonyManager这个类,我们前边已经介绍过了。现在,我们来看看android.provider.settings.System.

我们可以看出,如果我们想取得System中的值,那么我们必须首先获取一个ContentResolver,然后传递进去,这是为什么呢?其实system本质上是一张表的映射,这么说,你就明白了吧。我们甚至可以通过其提供的各种put方法,将我们的一些值存储到该表中。

源码中有这么一段代码:

 /**
         * The content:// style URL for this table
         */
        public static final Uri CONTENT_URI =
            Uri.parse("content://" + AUTHORITY + "/system");

现在,明白了吧。其中authority是settings

---------------------------------------------------------------

其他可访问的设置信息:

 

<think>我们正在讨论一个Android系统属性:`vendor.gsm.phoneserial`,它的值是一串0(00000000000000000)。这个属性通常由设备制造商设置,用于存储设备的序列号或IMEI(国际移动设备识别码)等标识信息。然而,当这个属性值是一串0时,可能有以下几种情况: 1. **设备未初始化**:设备首次启动或恢复出厂设置后,该属性尚未被正确设置。 2. **模拟器环境**:在Android模拟器中,某些硬件属性可能被设置为默认值(如全0)。 3. **设备制造商未正确设置**:设备制造商可能没有正确地将设备的唯一标识写入该属性。 4. **隐私保护**:在某些设备上,出于隐私考虑,可能会返回一个虚假的值(如全0)来替代真实的序列号。 ### 如何获取正确的设备序列号? 在Android中,获取设备序列号的方法有多种,但需要注意权限版本限制。 #### 方法1:使用`Build`类(需要权限) 从Android 8.0(API 26)开始,直接访问`Build.SERIAL`可能会返回"unknown"。在Android 10(API 29)及以上,需要`READ_PRIVILEGED_PHONE_STATE`权限(只有系统应用才能获取)或者使用`Build.getSerial()`方法(需要`READ_PHONE_STATE`权限)。 示例代码: ```java import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.support.annotation.RequiresApi; import android.support.v4.app.ActivityCompat; public class DeviceInfoUtils { @RequiresApi(api = Build.VERSION_CODES.O) public static String getDeviceSerial(Context context) { // 检查权限 if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { try { return Build.getSerial(); } catch (SecurityException e) { e.printStackTrace(); } } // 权限未授予或发生异常,尝试其他方法 return "unknown"; } } ``` #### 方法2:反射获取系统属性 如果上述方法不可用,我们可以尝试通过反射读取系统属性,但这种方法可能在不同设备上表现不一致。 ```java import java.lang.reflect.Method; public class SystemPropertiesReflection { public static String getSystemProperty(String key) { try { Class<?> systemProperties = Class.forName("android.os.SystemProperties"); Method get = systemProperties.getMethod("get", String.class); return (String) get.invoke(null, key); } catch (Exception e) { e.printStackTrace(); return null; } } public static void main(String[] args) { String serial = getSystemProperty("vendor.gsm.phoneserial"); // 如果返回全0,可以尝试其他属性 if (serial == null || serial.equals("00000000000000000")) { serial = getSystemProperty("ro.serialno"); } System.out.println("Serial: " + serial); } } ``` #### 方法3:使用`Settings.Secure.ANDROID_ID` 作为备选方案,可以使用`ANDROID_ID`,它是一个64位的十六进制字符串,在设备首次设置时生成。但注意,它可能在某些设备上重置。 ```java import android.content.Context; import android.provider.Settings; public class DeviceIdUtils { public static String getAndroidId(Context context) { return Settings.Secure.getString( context.getContentResolver(), Settings.Secure.ANDROID_ID ); } } ``` ### 为什么会出现全零? - **模拟器**:在模拟器中,硬件信息通常被设置为默认值(如全0)。 - **真实设备**:可能是设备制造商没有正确写入该属性,或者该属性被故意设置为全0以保护隐私(尤其是在用户未授权相关权限时)。 ### 如何应对全零情况? 1. **尝试多种属性**:除了`vendor.gsm.phoneserial`,还可以尝试其他属性,如`ro.serialno`、`ril.serialnumber`等。 2. **使用官方API**:优先使用`Build.getSerial()`(需要权限)或`ANDROID_ID`。 3. **组合标识符**:如果无法获取有效的硬件序列号,可以结合`ANDROID_ID`、设备型号、制造商等信息生成一个唯一标识符(注意用户隐私)。 ### 完整示例:获取设备唯一标识符 ```java import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.os.Build; import android.provider.Settings; import androidx.core.app.ActivityCompat; import java.util.UUID; public class DeviceIdentifier { @SuppressLint("HardwareIds") public static String getUniqueId(Context context) { String serial = null; // 1. 尝试获取硬件序列号(需要权限) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { try { serial = Build.getSerial(); } catch (Exception e) { e.printStackTrace(); } } } // 2. 如果硬件序列号不可用,尝试系统属性(反射) if (serial == null || serial.isEmpty() || serial.equals("unknown")) { serial = getSystemPropertyReflection("ro.serialno"); } // 3. 如果仍然不可用,尝试ANDROID_ID String androidId = null; if (serial == null || serial.isEmpty() || serial.equals("unknown")) { androidId = Settings.Secure.getString( context.getContentResolver(), Settings.Secure.ANDROID_ID ); } // 4. 组合标识符 if (serial != null && !serial.isEmpty() && !serial.equals("unknown")) { return "SERIAL-" + serial; } else if (androidId != null && !androidId.isEmpty() && !"9774d56d682e549c".equals(androidId)) { return "ANDROID_ID-" + androidId; } else { // 5. 作为最后手段,生成一个UUID(持久化存储) return "UUID-" + generateDeviceUUID(context); } } private static String getSystemPropertyReflection(String key) { try { Class<?> systemProperties = Class.forName("android.os.SystemProperties"); Method get = systemProperties.getMethod("get", String.class); return (String) get.invoke(null, key); } catch (Exception e) { return null; } } private static String generateDeviceUUID(Context context) { // 这里应该从持久化存储中读取之前生成的UUID,如果没有则生成并存储 // 简化为示例,直接返回一个随机UUID(实际应用中需要存储) return UUID.randomUUID().toString(); } } ``` ### 注意事项 1. **权限**:从Android 6.0开始,需要动态申请`READ_PHONE_STATE`权限。 2. **隐私政策**:收集设备标识符需要遵守GDPR等隐私法规,并明确告知用户。 3. **设备唯一性**:由于用户重置设备、刷机等原因,设备标识符可能会改变。 ### 总结 当`getprop vendor.gsm.phoneserial`返回全零时,表明该属性没有被正确设置或者被故意屏蔽。在开发中,我们应该优先使用官方API,并处理权限问题。同时,准备好备选方案(如使用`ANDROID_ID`或生成UUID)来确保能够获取到设备唯一标识符。 §§相关问题§§: 1.Android 10及以上版本中,如何正确获取设备序列号? 2. 使用反射获取系统属性有什么潜在风险? 3. 如何为Android应用实现设备唯一标识符(考虑隐私重置问题)? 4.Android模拟器中测试设备信息获取时需要注意什么?</think>### 回答:Android 系统属性 `vendor.gsm.phoneserial` 解析 `getprop vendor.gsm.phoneserial` 命令用于获取 Android 设备的特定系统属性值,而 `00000000000000000` 这个返回值通常表示以下情况之一: #### 1. **设备序列号占位符** - 在开发阶段或模拟器环境中,设备制造商可能使用全零作为临时占位符 - 表示真实的序列号尚未写入或不可用 - 常见于预发布固件或测试设备 #### 2. **隐私保护机制** - 某些设备在用户未授权时会返回虚假值(如全零) - 符合 Android 10+ 的隐私增强要求 - 防止未授权应用获取真实设备标识 #### 3. **设备特定状态** - 基带处理器未初始化或未就绪 - 设备处于工厂测试模式 - SIM 卡未插入或未激活 --- ### 完整实现:安全获取设备标识符 以下是正确处理设备序列号获取的完整代码实现,包含多种备选方案错误处理: ```java import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import java.lang.reflect.Method; import java.util.UUID; public class DeviceIdentifier { private static final String TAG = "DeviceIdentifier"; /** * 获取安全设备标识符(优先使用官方API) */ @SuppressLint({"HardwareIds", "MissingPermission"}) public static String getSafeDeviceId(Context context) { // 1. 尝试官方API获取序列号 String serial = getOfficialSerial(context); // 2. 检查是否有效(非空且非全零) if (isValidSerial(serial)) { return serial; } // 3. 尝试系统属性(反射方式) serial = getSystemProperty("vendor.gsm.phoneserial"); if (isValidSerial(serial)) { return serial; } // 4. 尝试其他可能的属性键 serial = getAlternativeSerials(); if (isValidSerial(serial)) { return serial; } // 5. 使用Android ID作为后备 serial = Settings.Secure.getString( context.getContentResolver(), Settings.Secure.ANDROID_ID ); // 6. 如果所有方法都失败,生成UUID if (TextUtils.isEmpty(serial) || "9774d56d682e549c".equals(serial)) { return generatePersistentUUID(context); } return serial; } /** * 使用官方API获取序列号 */ private static String getOfficialSerial(Context context) { try { // Android 8.0+ 官方方法 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 检查权限 if (context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { return Build.getSerial(); } } // 旧版Android return Build.SERIAL; } catch (Exception e) { Log.e(TAG, "Official API failed: " + e.getMessage()); return null; } } /** * 通过反射获取系统属性 */ private static String getSystemProperty(String key) { try { Class<?> systemProperties = Class.forName("android.os.SystemProperties"); Method get = systemProperties.getMethod("get", String.class); return (String) get.invoke(null, key); } catch (Exception e) { Log.w(TAG, "Reflection failed for key: " + key); return null; } } /** * 尝试其他可能的序列号属性 */ private static String getAlternativeSerials() { String[] possibleKeys = { "ro.serialno", "ril.serialnumber", "sys.serialnumber", "ro.boot.serialno", "gsm.serial" }; for (String key : possibleKeys) { String value = getSystemProperty(key); if (isValidSerial(value)) { return value; } } return null; } /** * 验证序列号是否有效 */ private static boolean isValidSerial(String serial) { return !TextUtils.isEmpty(serial) && !serial.equals("00000000000000000") && !serial.equalsIgnoreCase("unknown"); } /** * 生成持久化UUID作为后备标识符 */ private static String generatePersistentUUID(Context context) { // 从SharedPreferences获取已保存的UUID String uuid = context.getSharedPreferences("device_id", Context.MODE_PRIVATE) .getString("uuid", null); // 如果不存在则生成新UUID if (uuid == null) { uuid = UUID.randomUUID().toString(); context.getSharedPreferences("device_id", Context.MODE_PRIVATE) .edit() .putString("uuid", uuid) .apply(); } return uuid; } /** * 获取设备IMEI(需要READ_PHONE_STATE权限) */ @SuppressLint("MissingPermission") public static String getDeviceImei(Context context) { try { if (context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { return "PERMISSION_REQUIRED"; } TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); // Android 10+ 需要特殊处理 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return telephonyManager.getImei(0); // 主卡槽 } else { return telephonyManager.getDeviceId(); } } catch (Exception e) { Log.e(TAG, "IMEI retrieval failed: " + e.getMessage()); return "ERROR"; } } } ``` ### 代码解析 1. **分层获取策略**: - 优先使用官方API `Build.getSerial()` - 然后尝试反射获取系统属性 - 最后使用Android ID或生成UUID 2. **有效性验证**: - 排除全零值 (`00000000000000000`) - 排除 `unknown` 字符串 - 排除空值 3. **权限处理**: - 检查 `READ_PHONE_STATE` 权限 - 处理Android不同版本的API差异 - 优雅降级策略 4. **持久化UUID**: - 当所有方法失败时生成UUID - 使用SharedPreferences存储 - 保证应用重装后标识符不变 ### 使用示例 ```java // 在Activity或Service中获取设备ID String deviceId = DeviceIdentifier.getSafeDeviceId(context); Log.d("DeviceID", "Safe Device ID: " + deviceId); // 获取IMEI(需要先请求权限) if (checkPermission(Manifest.permission.READ_PHONE_STATE)) { String imei = DeviceIdentifier.getDeviceImei(context); Log.d("DeviceID", "IMEI: " + imei); } ``` ### 最佳实践 1. **权限请求**: ```java // 在Activity中请求权限 private static final int PERMISSION_REQUEST_CODE = 1001; private void requestPhoneStatePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { requestPermissions( new String[]{Manifest.permission.READ_PHONE_STATE}, PERMISSION_REQUEST_CODE ); } } } ``` 2. **Android 10+ 适配**: ```xml <!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 针对Android 10+ --> <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" tools:ignore="ProtectedPermissions" /> ``` 3. **隐私合规**: - 在隐私政策中说明设备标识符收集 - 提供用户禁用跟踪的选项 - 对敏感信息进行加密存储 ### 为什么需要处理全零值? 1. **避免错误识别**: - 多个设备返回相同全零值 - 无法区分不同设备 2. **功能异常**: - 授权系统可能拒绝全零标识符 - 分析系统无法正确追踪设备 3. **用户体验**: - 自动填充功能可能失效 - 设备恢复功能无法工作 4. **安全风险**: - 攻击者可能利用默认值绕过安全检查 - 虚假设备可能模仿合法设备 通过上述实现,您可以安全可靠地获取设备标识符,即使在遇到 `00000000000000000` 这样的无效值时也能提供有效的后备方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值