前言
前段時(shí)間工作有個(gè)需求别厘,要求檢測App是否在模擬器環(huán)境下運(yùn)行,就像在有些手機(jī)游戲上可以看到這個(gè)功能
這多虧了國內(nèi)pc端模擬器的發(fā)展,現(xiàn)在市面上的模擬器越來越多渴肉,也越來越“逼真”了冗懦,模擬器和真機(jī)的區(qū)別在逐步縮小,這就使得模擬器的檢測存在偏差仇祭,不管有多小披蕉,偏差總是會存在的,如何降低這種偏差值乌奇,就是這篇文章像討論的內(nèi)容没讲。
先來看一下我是怎么頭大的
1.撥號檢測法
首先一開始想到的就是能否撥號,真機(jī)肯定可以的礁苗,不然手機(jī)的根基就沒了爬凑,模擬器肯定不能撥號,所以我很快寫下代碼:
public boolean isSimulator1() {
String url = "tel:" + "123456";
Intent intent = new Intent();
intent.setData(Uri.parse(url));
intent.setAction(Intent.ACTION_DIAL);
// 是否可以處理跳轉(zhuǎn)到撥號的 Intent
boolean canResolveIntent = intent.resolveActivity(mContext.getPackageManager()) != null;
return !canResolveIntent;
}
完事收工试伙!... ... 等會,夜神模擬器怎么可以返回個(gè)false嘁信?也就是夜神模擬器是可以跳轉(zhuǎn)撥號的??疏叨。
2.設(shè)備標(biāo)識符檢測法
不行我就換一種方式蚤蔓,我記得Android有個(gè)設(shè)備標(biāo)識符Build.MANUFACTURER,它是用來標(biāo)注手機(jī)廠商的既绕,例如小米手機(jī)的MANUFACTURER的值為:Xiaomi凄贩,三星手機(jī)則為:Samsung……而模擬器的值一般是跟他們的品牌有關(guān)疲扎,例如Genymotion的Build.MANUFACTURER為Genymotion,Mumu模擬器的值為netease等壹甥,可以根據(jù)比較此值來較為方便的區(qū)分模擬器和真實(shí)設(shè)備句柠。
但是溯职!又是夜神模擬器帽哑,他有個(gè)很騷的地方妻枕,就是這個(gè)值你可以通過系統(tǒng)設(shè)置修改,比如我把他改成小米的:
結(jié)果輸出的Build.MANUFACTURER的值正是Xiaomi,所以這種方式也不可行
查了下網(wǎng)上很多也用到的類似這種比較設(shè)備標(biāo)識符的方法康嘉,但是效果也不是很好亭珍,幾乎都會卡在夜神模擬器這關(guān)肄梨,例如將篩選條件變得更加多樣:(方法實(shí)現(xiàn)可以查看這篇博客)
public boolean isSimulator2() {
String url = "tel:" + "123456";
Intent intent = new Intent();
intent.setData(Uri.parse(url));
intent.setAction(Intent.ACTION_DIAL);
// 是否可以處理跳轉(zhuǎn)到撥號的 Intent
boolean canResolveIntent = intent.resolveActivity(MainActivity.this.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) MainActivity.this.getSystemService(Context.TELEPHONY_SERVICE))
.getNetworkOperatorName().toLowerCase().equals("android")
|| !canResolveIntent;
}
依舊返回的還是false侨赡,這種方法的博主也主注意到了這一點(diǎn)粱侣,他發(fā)現(xiàn)夜神的SERIAL為16位齐婴,比真機(jī)的多了8位,所以Build.SERIAL這里加了個(gè)判斷Build.SERIAL.length() > 8
情妖,問題似乎可以得到解決了。
但是电爹,Android10.0以后丐箩,Build.SERIAL的獲取變得麻煩起來雏蛮,甚至有些手機(jī)阱州,比如我的小米9,得到了一個(gè)"unknown"苔货,也就是說我的手機(jī)會被識別為模擬器夜惭!所以我們又回到了原點(diǎn) ??
3.包名檢測法
找啊找诈茧,終于敢会,我看到一種有特點(diǎn)的檢測方式了这嚣,包名檢測法:
原理是通過獲取設(shè)備和模擬器中的包名來區(qū)分是否模擬器,每個(gè)品牌的模擬器都有應(yīng)用商店和一些系統(tǒng)應(yīng)用吏垮,這些都是不可卸載的膳汪,這些應(yīng)用對應(yīng)著唯一的包名遗嗽,那么包名就反過來可以鑒定模擬器的品牌。
舉個(gè)例子??網(wǎng)易Mumu模擬器:”com.mumu.launcher“這個(gè)包名就是網(wǎng)易Mumu啟動(dòng)時(shí)的系統(tǒng)應(yīng)用媳谁,我們就可以用他這一點(diǎn)來作為鑒定的依據(jù)之一晴音。
private static final String[] PKG_NAMES = {"com.mumu.launcher", "com.ami.duosupdater.ui", "com.ami.launchmetro", "com.ami.syncduosservices", "com.bluestacks.home",
"com.bluestacks.windowsfilemanager", "com.bluestacks.settings", "com.bluestacks.bluestackslocationprovider", "com.bluestacks.appsettings", "com.bluestacks.bstfolder",
"com.bluestacks.BstCommandProcessor", "com.bluestacks.s2p", "com.bluestacks.setup", "com.bluestacks.appmart", "com.kaopu001.tiantianserver", "com.kpzs.helpercenter",
"com.kaopu001.tiantianime", "com.android.development_settings", "com.android.development", "com.android.customlocale2", "com.genymotion.superuser",
"com.genymotion.clipboardproxy", "com.uc.xxzs.keyboard", "com.uc.xxzs", "com.blue.huang17.agent", "com.blue.huang17.launcher", "com.blue.huang17.ime",
"com.microvirt.guide", "com.microvirt.market", "com.microvirt.memuime", "cn.itools.vm.launcher", "cn.itools.vm.proxy", "cn.itools.vm.softkeyboard",
"cn.itools.avdmarket", "com.syd.IME", "com.bignox.app.store.hd", "com.bignox.launcher", "com.bignox.app.phone", "com.bignox.app.noxservice", "com.android.noxpush",
"com.haimawan.push", "me.haima.helpcenter", "com.windroy.launcher", "com.windroy.superuser", "com.windroy.launcher", "com.windroy.ime", "com.android.flysilkworm",
"com.android.emu.inputservice", "com.tiantian.ime", "com.microvirt.launcher", "me.le8.androidassist", "com.vphone.helper", "com.vphone.launcher", "com.duoyi.giftcenter.giftcenter"};
private static final String[] PATHS = {"/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", "/system/lib/libc_malloc_debug_qemu.so", "/sys/qemu_trace", "/system/bin/qemu-props",
"/dev/socket/qemud", "/dev/qemu_pipe", "/dev/socket/baseband_genyd", "/dev/socket/genyd"};
private static final String[] FILES = {"/data/data/com.android.flysilkworm", "/data/data/com.bluestacks.filemanager"};
// 包名檢測
public static boolean isSimulator3(Context paramContext) {
try {
List pathList = new ArrayList();
pathList = getInstalledSimulatorPackages(paramContext);
if (pathList.size() == 0) {
for (int i = 0; i < PATHS.length; i++)
if (i == 0) { 檢測的特定路徑
if (new File(PATHS[i]).exists()) continue;
pathList.add(Integer.valueOf(i));
} else {
if (!new File(PATHS[i]).exists()) continue;
pathList.add(Integer.valueOf(i));
}
}
if (pathList.size() == 0) {
pathList = loadApps(paramContext);
}
return (pathList.size() == 0 ? null : pathList.toString()) != null;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
@SuppressLint("WrongConstant")
private static List getInstalledSimulatorPackages(Context context) {
ArrayList localArrayList = new ArrayList();
try {
for (int i = 0; i < PKG_NAMES.length; i++)
try {
context.getPackageManager().getPackageInfo(PKG_NAMES[i], PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
localArrayList.add(PKG_NAMES[i]);
} catch (PackageManager.NameNotFoundException localNameNotFoundException) {
}
if (localArrayList.size() == 0) {
for (int i = 0; i < FILES.length; i++) {
if (new File(FILES[i]).exists()) // 檢測的特定文件
localArrayList.add(FILES[i]);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return localArrayList;
}
public static List loadApps(Context context) {
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
List<String> list = new ArrayList<>();
List<ResolveInfo> apps = context.getPackageManager().queryIntentActivities(intent, 0);
//for循環(huán)遍歷ResolveInfo對象獲取包名和類名
for (int i = 0; i < apps.size(); i++) {
ResolveInfo info = apps.get(i);
String packageName = info.activityInfo.packageName;
CharSequence cls = info.activityInfo.name;
CharSequence name = info.activityInfo.loadLabel(context.getPackageManager());
if (!TextUtils.isEmpty(packageName)) {
if (packageName.contains("bluestacks")) {
list.add("藍(lán)疊");
return list;
}
}
}
return list;
}
其中還用到了檢測的特定文件來加強(qiáng)檢測精度,這種方法算是比較靠譜的了郭计。具體實(shí)現(xiàn)昭伸,可以查看這篇博客澎迎,寫的很好。
這種方法的成功率高狠多了灵份,當(dāng)然失敗的概率是很小的填渠,除非遇到以下情況:
- A模擬器安裝了B模擬器的應(yīng)用鸟辅,導(dǎo)致識別的模擬器類型出錯(cuò)
- A手機(jī)安裝了B模擬器的應(yīng)用,一般情況下屉更,模擬器的系統(tǒng)應(yīng)用是不可被下載安裝的瑰谜;如果你足夠皮??树绩,你可以隨便弄個(gè)包,把包名改成"com.mumu.launcher"渤早,那么你的手機(jī)也就會被識別為Mumu模擬器了鹊杖。
4.特征值檢測
這種可以說是集大成者了,這種方式的檢測成功率極高积瞒,甚至之前的手動(dòng)改包名的騷操作也可以被揪出來登下,實(shí)現(xiàn)方式可以看這兒:一行代碼幫你檢測Android模擬器
這種方法的實(shí)現(xiàn)思路是通過定義一個(gè)嫌疑值被芳,當(dāng)嫌疑值達(dá)到閥值的時(shí)候畔濒,bang!就把你識別成模擬器了李破。
隨便貼一下代碼截圖大家體會一下:
很厲害了!
當(dāng)然如果你想嘗試一下诽俯,可以用我的demo,以上四種方式都有暴区,你可以隨便測仙粱,隨便玩~??
代碼地址:MonitorDemo