在开发中我们经常会用到一些手机系统信息,如IMEI、IMSI、MAC、SERIALNO等等,下面给出这些信息的一些获取方法
1. android.os.SystemProperties (the access to System Property store)
这是android系统属性的控制类,控制系统的各属性值,用于记录和管理系统的配置和状态。每个属性都以pair(key/value)的形式存储,可以在adb shell下使用getprop查看系统属性(列出部分):
[dhcp.wlan0.dns1]: [172.26.210.1]
[dhcp.wlan0.dns2]: [218.2.135.1]
[dhcp.wlan0.gateway]: [172.26.210.1]
[dhcp.wlan0.ipaddress]: [172.26.210.24]
[dhcp.wlan0.mask]: [255.255.0.0]
[ro.build.display.id]: [G750-T01-CM11-NiuNai]
[ro.product.model]: [G750-T01]
[ro.product.locale.language]: [zh]
[ro.product.locale.region]: [CN]
[ro.serialno]: [DQBALFPNNBKRIVSO]
[gsm.serial]: [BY2PJU1496058698]
SystemProperties提供了get、set函数来访问和设置属性值,而具体的处理由C++实现(下面给出部分源码,一些重载的get、set函数省略。如果想具体了解函数实现的细节,可以参考http://www.cnblogs.com/bastard/archive/2012/10/11/2720314.html):
public class SystemProperties {
//JNI调用函数
private static native String native_get(String key);
private static native void native_set(String key, String def);
public static String get(String key) {
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key);
}
public static void set(String key, String val) {
if (val != null && val.length() > PROP_VALUE_MAX) {
throw newValueTooLargeException(key, val);
}
if (TRACK_KEY_ACCESS) onKeyAccess(key);
native_set(key, val);
}
}
这个类是隐藏的,上层程序开发无法直接使用。如果要用,需要用到java的反射机制,并且需要知道所需属性的具体标识key(建议使用下面的封装类),如:
Class clazz = Class.forName("android.os.SystemProperties");
Method MethodGet = clazz.getDeclaredMethod("get", String.class);
String serialno = (String) MethodGet.invoke(null,"ro.serialno");
Log.d("yi"," serialno: " + serialno);
日志:D/yi: serialno: DQBALFPNNBKRIVSO
2. android.os.Build (Information about the current build, extracted from system properties)
从备注可以看出,此类是基于系统信息的,提供了一些系统属性值作为类变量,以供使用。它大致可以算是基于SystemProperties的一个封装类,大部分特征都是通过SystemProperties的get函数获取:
public class Build {
//系统版本号
public static final String RELEASE = getString("ro.build.version.release");
//型号
public static final String MODEL = getString("ro.product.model");
//硬件识别码
public static final String FINGERPRINT = deriveFingerprint();
private static String deriveFingerprint() {
String finger = SystemProperties.get("ro.build.fingerprint");
if (TextUtils.isEmpty(finger)) {
finger = getString("ro.product.brand") + '/' +
getString("ro.product.name") + '/' +
getString("ro.product.device") + ':' +
getString("ro.build.version.release") + '/' +
getString("ro.build.id") + '/' +
getString("ro.build.version.incremental") + ':' +
getString("ro.build.type") + '/' +
getString("ro.build.tags");
}
return finger;
}
//生产商
public static final String BRAND = getString("ro.product.brand");
private static String getString(String property) {
return SystemProperties.get(property, UNKNOWN);
}
}
我们在使用的时候可以直接通过Build类调用,用此方法可获取常用的系统版本号、手机型号、硬件识别码、序列号等等:
StringBuilder out = new StringBuilder();
out.append("MODEL: " + android.os.Build.MODEL);
out.append("; BRAND: " + android.os.Build.BRAND);
//新版SDK已不推荐通过此方法获取SERIAL,而是用getSerial():封装了IDeviceIdentifiersPolicyService的getSerial()方法
out.append("; SERIAL: " + android.os.Build.SERIAL);
3. android.telephony.TelephonyManager(access to information about the telephony services)
电话管理器,用于管理手机通话状态、获取电话信息、侦听电话状态以及可以调用电话拨号器拨打电话。这里有我们所需要的IMEI、IMSI等:
public class TelephonyManager {
//唯一设备号(GSM为IMEI,CDMA为MEID或ESN)
public String getDeviceId() {
ITelephony telephony = getITelephony();
if (telephony == null)
return null;
return telephony.getDeviceId(mContext.getOpPackageName());
}
//IMEI
public String getImei() {
return getImei(getSlotIndex());
}
public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
return telephony.getImeiForSlot(slotIndex, getOpPackageName());
}
//IMSI
public String getSimSerialNumber() {
return getSimSerialNumber(getSubId());
}
public String getSimSerialNumber(int subId) {
IPhoneSubInfo info = getSubscriberInfo();
if (info == null)
return null;
return info.getIccSerialNumberForSubscriber(subId, mContext.getOpPackageName());
}
}
构造对象直接调用函数即可,在AndroidManifest.xml中添加访问手机状态的权限:<uses-permission android:name="android.permission.READ_PHONE_STATE" />
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
Log.d("yi"," DeviceId: " + tm.getDeviceId());
Log.d("yi"," Imei: " + tm.getImei()); //SDK26才提供
Log.d("yi"," Imsi: " + tm.getSimSerialNumber ());
4.android.provider.settings(The Settings provider contains global system-level device preferences)
其实,这个就是与我们的设置页面对应的数据库,以键值对存储数据,比如 ADB_ENABLED、BLUETOOTH_ON、WIFI_COUNTRY_CODE等等,通过函数 getXxx(ContentResolver resolver, String name)可以获取键值name所对应的数值,而如果你要设置新的属性值,通过putXxx(ContentResolver resolver, String name, Xxx value),可以设置应用所私有的,也可以设置全局的。当然,有些重要的需要设置权限:Manifest.permission.WRITE_SECURE_SETTINGS
当然,如果需要修改系统属性值,通常不建议应用通过这个类的属性来设置,而是通过UI界面来修改(像内部类Secure中的属性值,应用只能读取而无法改写),利用ACTION_SETTINGS等属性构造INTENT可以打开对应的设置页面进行手动修改。
这里我们所要获取的手机系统属性值就是在内部类Secure中,毫无疑问,这些值是无法被应用修改的,别说是应用了,我们都改不了好不好 -_-
public final class Settings {
public static final class Secure extends NameValueTable {
private static final HashSet<String> MOVED_TO_LOCK_SETTINGS;
private static final HashSet<String> MOVED_TO_GLOBAL;
static {
MOVED_TO_LOCK_SETTINGS = new HashSet<>(3);
MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_ENABLED);
MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_VISIBLE);
MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
MOVED_TO_GLOBAL = new HashSet<>();
MOVED_TO_GLOBAL.add(Settings.Global.ADB_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.ASSISTED_GPS_ENABLED);
。。。。。。
}
public static String getString(ContentResolver resolver, String name) {
//这个源码就不多看了,涉及到库查找、可修改与不可修改、多线程同步等
return getStringForUser(resolver, name, UserHandle.myUserId());
}
}
public static final class System extends NameValueTable {
private static final HashSet<String> MOVED_TO_SECURE;
static {
MOVED_TO_SECURE = new HashSet<>(30);
//android_id
MOVED_TO_SECURE.add(Secure.ANDROID_ID);
MOVED_TO_SECURE.add(Secure.HTTP_PROXY);
MOVED_TO_SECURE.add(Secure.LOCATION_PROVIDERS_ALLOWED);
MOVED_TO_SECURE.add(Secure.LOCK_BIOMETRIC_WEAK_FLAGS);
MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_ENABLED);
MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_VISIBLE);
}
}
}
然后简单看下使用:
Context cont = this.getApplicationContext();
String msg;
msg = Settings.Secure.getString(cont.getContentResolver(),"android_id");
对应Smali:
invoke-virtual {p0}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;
move-result-object v1
const-string/jumbo v2, "android_id"
invoke-static {v1, v2}, Landroid/provider/Settings$Secure;->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;
5. 获取MAC地址
MAC地址获取方式与以上的一些特征有点区别,可以用Android的API直接获取,也可以使用Linux命令获取
在使用WIFI上网时可直接使用android.net.wifi.WifiManager系统调用,使用时需要在AndroidManifest.xml中添加访问手机WIFI状态的权限:<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
WifiManager pwifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiInfo pinfo = pwifi.getConnectionInfo();
Log.d("yi","MAC: " + pinfo.getMacAddress());
Log.d("yi","IP: " + pinfo.getIpAddress()); //ip整数形式
如果是在使用移动网络的情况下,可直接使用java.net.NetworkInterface系统调用,使用时需要设置手机上网权限:<uses-permission android:name="android.permission.INTERNET"></uses-permission>
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();// 返回所有网络接口的一个枚举实例
while (e.hasMoreElements()) {
NetworkInterface network = e.nextElement();// 获得一个网络接口
if (network != null) {
if (network.getHardwareAddress() != null) { // 获得MAC地址
byte[] addres = network.getHardwareAddress();
StringBuffer mac = new StringBuffer();
for (int i = 0; i < addres.length; i++) {
int intValue = addres[i];
if (intValue < 0)
intValue = 256 + intValue;
mac.append(Integer.toHexString(intValue));
if(i < addres.length - 1)
mac.append(":");
}
Log.d("yi","Mac: " + mac);
}
Enumeration<InetAddress> ips = network.getInetAddresses(); // 获取ip
for(;ips.hasMoreElements();)
{
InetAddress ip = ips.nextElement();
if(ip instanceof InetAddress)
Log.d("yi","IP: " + ip);;
}
}
}
也可使用可获取MAC的Linux命令,cat /sys/class/net/wlan0/address 或 ifconfig等都可以,然后使用读写函数将MAC地址部分数据搞出来就行了(WifiInfo给MAC的默认初始值为“02:00:00:00:00:00”,有些设备并未在WifiInfo中设置MAC地址值,这样调用WifiManager获得的就只是这个初始值),下面借助busybox调用Linux命令获取MAC:
String readLine = "";
Process process = Runtime.getRuntime().exec("busybox ifconfig");
BufferedReader bufferedReader = new BufferedReader (new InputStreamReader(process.getInputStream()));
while ((readLine = bufferedReader.readLine ()) != null) {//只取结果中含有"HWaddr"的这一行: Link encap:Ethernet HWaddr 00:08:22:76:CF:FB
if(readLine.contains("HWaddr")){
Log.d("yi","Mac: " + readLine.substring(readLine.indexOf("HWaddr")+6, readLine.length()-1));
}