Android全面檢測設(shè)備是否模擬器

前言

前段時(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)然失敗的概率是很小的填渠,除非遇到以下情況:

  1. A模擬器安裝了B模擬器的應(yīng)用鸟辅,導(dǎo)致識別的模擬器類型出錯(cuò)
  2. 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


題外話

yysy確實(shí)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伐割,一起剝皮案震驚了整個(gè)濱河市隔心,隨后出現(xiàn)的幾起案子尚胞,更是在濱河造成了極大的恐慌笼裳,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抽减,死亡現(xiàn)場離奇詭異撩轰,居然都是意外死亡堪嫂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門淹办,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怜森,“玉大人副硅,你說我怎么就攤上這事翅萤√酌矗” “怎么了胚泌?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵零蓉,是天一觀的道長。 經(jīng)常有香客問我壁公,道長紊册,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮妥色,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嘹害。我一直安慰自己笔呀,他們只是感情好髓需,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布微渠。 她就那樣靜靜地躺著逞盆,像睡著了一般纳击。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刨啸,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天识脆,我揣著相機(jī)與錄音,去河邊找鬼离例。 笑死宫蛆,一個(gè)胖子當(dāng)著我的面吹牛的猛,可吹牛的內(nèi)容都是我干的耀盗。 我是一名探鬼主播想虎,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叛拷!你這毒婦竟也來了舌厨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤忿薇,失蹤者是張志新(化名)和其女友劉穎裙椭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體署浩,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揉燃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑰抵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婿崭。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡授瘦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出徒欣,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站酷宵,受9級特大地震影響荣挨,放射性物質(zhì)發(fā)生泄漏此虑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一恩伺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧褒脯,春花似錦、人聲如沸养盗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春敌完,著一層夾襖步出監(jiān)牢的瞬間长赞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工乐设, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人歼跟。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓她倘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親荧止。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容