前言
接到了一个需求,要求判断是当前是模拟器还是真机,还给了一张图,吃鸡都能做到,所以我们也一定能做到。(似曾相似的一句话:微信都能做,为什么我们不能做?)
记得多年以前研究过这个,模拟器的各种参数都可以改的,只能另辟蹊跷。比如蓝牙、各种传感器(其中好像就光传感器比较实用),然后就开始各种查资料了。
然后发现网上资料虽然很多,经实践,但是大多数都用不了,作者自己也没有去验证,一句话,用了会被老板艹的。
目前市面上好像也没有一套比较靠谱的方案,于是本篇博客就诞生了。
解决思路
1.通过光传感器来判断
大多数手机都是有光传感器的,模拟器都没有光传感器。
public static boolean hasLightSensor(Context context)
{
SensorManager sensorManager = (SensorManager) context.getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); //光
if (sensor == null)
return true;
else
return false;
}
2. 通过是否能拨打电话
根据以前的经验,模拟器拨打电话会崩溃,然后就写个demo测试下咯。测试后发现还真可以通过这个来判断。
String url = "tel:" + "123456";
Intent intent = new Intent();
intent.setData(Uri.parse(url));
intent.setAction(Intent.ACTION_DIAL);
boolean canCallPhone = intent.resolveActivity(context.getPackageManager()) != null; // 是否可以跳转到拨号页面
最终方案
经过实践并不是所有的真机都含有光传感器,所以这个方案抛弃。
经过测试主流模拟器(下方列出来的模拟器),通过是否能拨打电话确实可以识别出真机与模拟器,但是只是这么一个方法感觉有点不够严谨,于是我觉得再加点什么。
于是又加了一些参数参数判断。
最终完整代码如下
public class SimulatorUtil
{
private SimulatorUtil()
{
}
public static boolean isSimulator(Context context)
{
String url = "tel:" + "123456";
Intent intent = new Intent();
intent.setData(Uri.parse(url));
intent.setAction(Intent.ACTION_DIAL);
// 是否可以处理跳转到拨号的 Intent
boolean canCallPhone = intent.resolveActivity(context.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.MODEL
.contains("MuMu") || Build.MODEL.contains("virtual") || Build.SERIAL.equalsIgnoreCase("android") || Build.MANUFACTURER
.contains("Genymotion") || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) || "google_sdk"
.equals(Build.PRODUCT) || ((TelephonyManager)context
.getSystemService(Context.TELEPHONY_SERVICE)).getNetworkOperatorName()
.toLowerCase()
.equals("android") || !canCallPhone;
}
}
最终方案:通过是否可以拨打电话以及一些模拟器特有的参数来区别出是真机还是模拟器
网上看到有一些作者,加入了以下判断
Build.SERIAL.contains("unkonwn");
经实践,小米8 SE、华为AL00,返回的手机序列号都是unkonwn,所以抛弃该方法。
小米8 SE
华为AL00
以下是各模拟器的测试数据
网易MuMu模拟器
很良心,设备型号直接返回MuMu(Mac、Windows版都是一样的)
腾讯手游助手
很良心
逍遥模拟器
这个就有点过分了,samsung???说好的模拟器呢。
雷电模拟器
这个也很过分,vivo???说好的模拟器呢。
夜神模拟器
这个也是有点过分的
蓝叠模拟器
这个模拟器竟然可以打电话,各种无懈可击的样
海马玩模拟器
最后
由于没有平板,没有条件测试,如有热心网友测试,欢迎上图留言,大家一起搞定这个模拟器、真机判断方法。
测试代码
boolean simulator = SimulatorUtil.isSimulator(this);
String isSimulator;
if (simulator)
isSimulator = "当前是模拟器环境";
else
isSimulator = "当前是真机环境";
String url = "tel:" + "123456";
Intent intent = new Intent();
intent.setData(Uri.parse(url));
intent.setAction(Intent.ACTION_DIAL);
boolean canCallPhone = intent.resolveActivity(this.getPackageManager()) != null;
Toast.makeText(this, isSimulator+"\n|||canResolveIntent:"+canCallPhone+"\n|||"
+"\n|||Build.FINGERPRINT:"+Build.FINGERPRINT
+"\n|||Build.MODEL:"+Build.MODEL+"\n|||Build.MODEL:"+Build.MODEL+"\n|||Build.SERIAL:"+Build.SERIAL+"\n|||Build.SERIAL:"+Build.SERIAL
+"\n|||Build.MANUFACTURER:"+Build.MANUFACTURER+"\n|||Build.BRAND:"+Build.BRAND+"\n|||Build.DEVICE:"+Build.DEVICE
+"\n|||Build.PRODUCT:"+Build.PRODUCT+"\n|||getSystemService:"+((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE)).getNetworkOperatorName(),Toast.LENGTH_LONG).show();
后续
该判定方法经过一段时间的使用,发现部分华为手机(华为折叠屏、华为mate30保时捷版)会出现真机被判定为模拟器的情况。
这些机型都有点贵,没办法真机测试分析到底是哪的问题,所以另辟蹊径,针对华为机型特殊优化。
在之前方法的基础上,如果得出是模拟器,并且可以拨打电话,并且手机品牌是华为,并且有光传感器,将结果改为真机
if (!isSimulator && canCallPhone && Build.BRAND.equalsIgnoreCase("HUAWEI") && hasLightSensor())
isSimulator = true;