您的位置:首页 > 移动开发 > Android开发

Android检测设备是否是模拟器

2017-10-30 13:49 246 查看
前言:为什么要写这文章呢,这么普通甚至大众化的一个检测是否模拟器的方法应该早就烂大街了吧,最开始小白也是直接收索现有的,可是拿来用后检测都不准确,一次次删删改改确定了最终方法。

吐槽:(最近有一个新的需求,检测设备是否为模拟器,如果是模拟器就禁用某些功能)当时客服给我说不能用模拟器登陆我们的软件,一大堆什么什么的说了半天,最后来句你是搞技术的,这些应该很简单吧,把我们软件也加上。我:**

吐槽归吐槽,代码还是要敲得。然后百度,Wiki了大概半个小时吧,看了好几篇关于这方面的文章,大概方向就是判断下模拟器和真机的一些硬件信息的区别,下面跟着来看看改进步骤吧,不愿意看过程的可以直接到文章结尾看最终结果:

1、下面的方法是最常见的,基本我看的文章都提到过,也最是鸡肋(只对eclipse和Android studio自带的模拟器有用)

public static boolean isEmulator(Context context){
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String imei = tm.getDeviceId();
if (imei == null || imei.equals(“000000000000000”)){
return true;
}
return false;
}


2.检查默认ID=”000000000000000”,默认IMSI=”310260000000000”这类方法都过时了,现在很多模拟器这些都可以设置了,经过筛选各篇文章,发现下面几个通用的解决方案。

方案一:

public boolean isEmulator() {
return ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
.getNetworkOperatorName().toLowerCase().equals("android");
}


方案二:

public boolean isEmulator() {
return Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.toLowerCase().contains("vbox")
|| Build.FINGERPRINT.toLowerCase().contains("test-keys")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.SERIAL.equalsIgnoreCase("unknown")
|| Build.SERIAL.equalsIgnoreCase("android")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|| "google_sdk".equals(Build.PRODUCT);
}


于是把上面两种方案结合起来,就是:

public boolean isEmulator() {
return Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.toLowerCase().contains("vbox")
|| Build.FINGERPRINT.toLowerCase().contains("test-keys")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.SERIAL.equalsIgnoreCase("unknown")
|| Build.SERIAL.equalsIgnoreCase("android")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|| "google_sdk".equals(Build.PRODUCT)
|| ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
.getNetworkOperatorName().toLowerCase().equals("android");
}


然后我就拿着这个方法开始测试了,发现大多数都是可以检测出来的,只有各别模拟器不可以检测出来,其中包括“夜神安卓模拟器”。又再次查看夜神安卓模拟器个和其他模拟器以及手机(手头的)不同的地方,有个博客主提到夜神的“Build.SERIAL”是一个16位的字符串,而其他模拟器都是“unknow”或者”android”,于是修改了检测方法。

public boolean isEmulator() {
return Build.FINGE
4000
RPRINT.startsWith("generic")
|| Build.FINGERPRINT.toLowerCase().contains("vbox")
|| Build.FINGERPRINT.toLowerCase().contains("test-keys")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.SERIAL.equalsIgnoreCase("unknown")
|| Build.SERIAL.equalsIgnoreCase("android")
|| Build.SERIAL.length() = 16
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|| "google_sdk".equals(Build.PRODUCT)
|| ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
.getNetworkOperatorName().toLowerCase().equals("android");
}


再次检测夜神模拟器,成功识别!!

原以为这样就结束了,可是又出现了问题:测试到华为畅享5s的时候,竟然也被识别为模拟器。发现问题出现在上面加的一个判断中 Build.SERIAL.length() =16 ,这个手机的 Build.SERIAL 也是 16 位,其他手机大部分都是8位。好吧,GG了。。。

然后想能不能通过获取手机品牌来区分,看了篇文章http://www.cnblogs.com/goodhacker/p/3404398.html ,然后文章里的方法在上面我们得到的方法中出现过,于是现在还是区别不出来。。。

最后强哥说了句“模拟器应该不能打电话吧”,呃(⊙﹏⊙),试了一下在模拟器中调用拨打电话拨打居然 Crash 了,于是在其他几个模拟器中也尝试,结果是大部分都是简单粗暴的直接 Crash 。虽然不能保证100% 的识别,但大多数还是可以识别的。

接下来再修改方法:

public boolean isEmulator() {
String url = "tel:" + "12345678910";
Intent intent = new Intent();
intent.setData(Uri.parse(url));
intent.setAction(Intent.ACTION_DIAL);
// 是否可以处理跳转到拨号的 Intent
boolean canResolveIntent = intent.resolveActivity(mContext.getPackageManager()) != null;

return Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.toLowerCase().contains("vbox")
|| Build.FINGERPRINT.toLowerCase().contains("test-keys")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.SERIAL.equalsIgnoreCase("unknown")
|| Build.SERIAL.equalsIgnoreCase("android")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|| "google_sdk".equals(Build.PRODUCT)
|| ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
.getNetworkOperatorName().toLowerCase().equals("android")
|| !canResolverIntent;
}


这就是最终方法了,但是模拟器检测与模拟器反检测都在不断的更新迭代中,无法确保哪一种方法会永垂不朽。

ps:根据业务需要于是我将上面的方法放在了登陆接口里面,如果登陆成功,返回一个Boolean值判断是否调用该方法判断模拟器,造成误判之后,拿到登陆用户修改登陆返回的那个值使其不调用该方法判断做一定的应急处理,然后在去看看方法应该怎么改了。当然如果各位有什么好的方法,希望可以告诉我,十分感谢!

为了向别人、向世界证明自己而努力拼搏,而一旦你真的取得了成绩,才会明白:人无须向别人证明什么,只要你能超越自己。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息