亚洲国产精品乱码一区二区,美景房屋2免费观看,哎呀哎呀在线观看视频高清国语,从镜子里看我是怎么C哭你

Article / 文章中心

檢測App是否在Android模擬器中運(yùn)行

發(fā)布時(shí)間:2023-05-15 點(diǎn)擊數(shù):5888

引言

在Android開發(fā)中,經(jīng)常會(huì)使用到Android模擬器,普通用戶也可能由于游戲等其他需求而使用Android模擬器。

但是,由于模擬器往往與實(shí)際真機(jī)有差異,會(huì)存在使用模擬器刷單、頻繁大量請(qǐng)求等惡意行為,于是就產(chǎn)生了區(qū)分模擬器與真機(jī)的需求。

在此,提供了區(qū)分模擬器與真機(jī)的方法,并提供了Java工具類 EmulatorHelper,方便各位開發(fā)同事在業(yè)務(wù)需求中調(diào)用以區(qū)分當(dāng)前程序是否運(yùn)行在模擬器當(dāng)中。

以下是該工具類實(shí)現(xiàn)思路的說明,如有不妥當(dāng)之處,歡迎指出并與我交流。
實(shí)現(xiàn)思路

區(qū)分模擬器的基本思路就是根據(jù)Android的Build類中一些與硬件相關(guān)聯(lián)的常量及系統(tǒng)參數(shù),在真機(jī)與模擬器中值的不同,從而區(qū)分模擬器。

但是實(shí)際使用中,現(xiàn)在的模擬器往往都會(huì)支持修改這當(dāng)中的一些值,導(dǎo)致只是用靜態(tài)變量及系統(tǒng)參數(shù)的方法準(zhǔn)確率不高,因此還需要輔助其他方法(例如:用戶的行為、檢查傳感器、基帶信息、進(jìn)程信息等),再綜合判斷:
硬件名稱檢查

硬件名稱(ro.hardware),是安卓系統(tǒng)變量文件build.prop中的一個(gè)參數(shù),用于描述該硬件的名稱,而部分模擬器的某些版本(早期版本),該值的內(nèi)容是有特定值的。

因此該因素可以作為一個(gè)判斷模擬器的因素,具體值在下方代碼中已經(jīng)列出,但是由于這個(gè)值是可以在模擬器中進(jìn)行修改的,因此該因素判斷的準(zhǔn)確率一般。
/**
 * 特征參數(shù)-硬件名稱
 */
private EmulatorCheckResult checkFeaturesByHardware() {
    String hardware = getProperty("ro.hardware");
 if (null == hardware) {
        return new EmulatorCheckResult(Result.RESULT_MAYBE_EMULATOR, null);
 }
    Result result;
 String tempValue = hardware.toLowerCase();
 switch (tempValue) {
        case "ttvm":
            //天天模擬器
 case "nox":
            //夜神模擬器
 case "cancro":
            //網(wǎng)易MUMU模擬器
 case "intel":
            //逍遙模擬器
 case "vbox":
        case "vbox86":
            //騰訊手游助手
 case "android_x86":
            //雷電模擬器
 result = Result.RESULT_CONFIRM_EMULATOR;
 break;
 default:
            result = Result.RESULT_UNKNOWN;
 break;
 }
    return new EmulatorCheckResult(result, hardware);
}
發(fā)布渠道檢查

設(shè)備發(fā)布渠道信息(ro.build.flavor),是安卓系統(tǒng)變量文件build.prop中的一個(gè)參數(shù),用于描述該設(shè)備ISO發(fā)布時(shí)的渠道,而在部分模擬器中,該值的內(nèi)容是有特定值的。

因此該因素可以作為一個(gè)判斷模擬器的因素,具體值在下方代碼中已經(jīng)列出,但是由于這個(gè)值在某些模擬器中是不固定或可以根據(jù)系統(tǒng)鏡像進(jìn)行修改的,因此該因素判斷的準(zhǔn)確率一般。
/**
 * 特征參數(shù)-渠道
 */
private EmulatorCheckResult checkFeaturesByFlavor() {
    String flavor = getProperty("ro.build.flavor");
 if (null == flavor) {
        return new EmulatorCheckResult(Result.RESULT_MAYBE_EMULATOR, null);
 }
    Result result;
 String tempValue = flavor.toLowerCase();
 if (tempValue.contains("vbox")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else if (tempValue.contains("sdk_gphone")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else {
        result = Result.RESULT_UNKNOWN;
 }
    return new EmulatorCheckResult(result, flavor);
}
設(shè)備型號(hào)檢查

設(shè)備型號(hào)(ro.product.model),是安卓系統(tǒng)變量文件build.prop中的一個(gè)參數(shù),用于描述該設(shè)備型號(hào),部分模擬器中該值是存在特定值的。

因此該因素可以作為一個(gè)判斷模擬器的因素,具體值在下方代碼中已經(jīng)列出,但是由于這個(gè)值在某些模擬器中是不固定或可以根據(jù)系統(tǒng)鏡像進(jìn)行修改的,因此該因素判斷的準(zhǔn)確率一般。
/**
 * 特征參數(shù)-設(shè)備型號(hào)
 */
private EmulatorCheckResult checkFeaturesByModel() {
    String model = getProperty("ro.product.model");
 if (null == model) {
        return new EmulatorCheckResult(Result.RESULT_MAYBE_EMULATOR, null);
 }
    Result result;
 String tempValue = model.toLowerCase();
 if (tempValue.contains("google_sdk")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else if (tempValue.contains("emulator")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else if (tempValue.contains("android sdk built for x86")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else {
        result = Result.RESULT_UNKNOWN;
 }
    return new EmulatorCheckResult(result, model);
}
硬件制造商檢查

硬件制造商(ro.product.manufacturer),是安卓系統(tǒng)變量文件build.prop中的一個(gè)參數(shù),用于描述該設(shè)備的制造商,部分模擬器中該值是存在特定值的。

因此該因素可以作為一個(gè)判斷模擬器的因素,具體值在下方代碼中已經(jīng)列出,但是由于這個(gè)值在某些模擬器中是不固定或可以根據(jù)系統(tǒng)鏡像進(jìn)行修改的,因此該因素判斷的準(zhǔn)確率一般。
/**
 * 特征參數(shù)-硬件制造商
 */
private EmulatorCheckResult checkFeaturesByManufacturer() {
    String manufacturer = getProperty("ro.product.manufacturer");
 if (null == manufacturer) {
        return new EmulatorCheckResult(Result.RESULT_MAYBE_EMULATOR, null);
 }
    Result result;
 String tempValue = manufacturer.toLowerCase();
 if (tempValue.contains("genymotion")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else if (tempValue.contains("netease")) {
        //網(wǎng)易MUMU模擬器
 result = Result.RESULT_CONFIRM_EMULATOR;
 } else {
        result = Result.RESULT_UNKNOWN;
 }
    return new EmulatorCheckResult(result, manufacturer);
}
主板名稱檢查

主板名稱(ro.product.board),是安卓系統(tǒng)變量文件build.prop中的一個(gè)參數(shù),用于描述該設(shè)備的主板名稱信息,部分模擬器中該值是存在特定值的。

因此該因素可以作為一個(gè)判斷模擬器的因素,具體值在下方代碼中已經(jīng)列出,但是由于這個(gè)值在某些模擬器中是不固定或可以根據(jù)系統(tǒng)鏡像進(jìn)行修改的,因此該因素判斷的準(zhǔn)確率一般。
/**
 * 特征參數(shù)-主板名稱
 */
private EmulatorCheckResult checkFeaturesByBoard() {
    String board = getProperty("ro.product.board");
 if (null == board) {
        return new EmulatorCheckResult(Result.RESULT_MAYBE_EMULATOR, null);
 }
    Result result;
 String tempValue = board.toLowerCase();
 if (tempValue.contains("android")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else if (tempValue.contains("goldfish")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else {
        result = Result.RESULT_UNKNOWN;
 }
    return new EmulatorCheckResult(result, board);
}
主板平臺(tái)檢查

主板平臺(tái)(ro.product.platform),是安卓系統(tǒng)變量文件build.prop中的一個(gè)參數(shù),用于描述該設(shè)備的主板平臺(tái)信息,部分模擬器中該值是存在特定值的。

因此該因素可以作為一個(gè)判斷模擬器的因素,具體值在下方代碼中已經(jīng)列出,但是由于這個(gè)值在某些模擬器中是不固定或可以根據(jù)系統(tǒng)鏡像進(jìn)行修改的,因此該因素判斷的準(zhǔn)確率一般。
/**
 * 特征參數(shù)-主板平臺(tái)
 */
private EmulatorCheckResult checkFeaturesByPlatform() {
    String platform = getProperty("ro.board.platform");
 if (null == platform) {
        return new EmulatorCheckResult(Result.RESULT_MAYBE_EMULATOR, null);
 }
    Result result;
 String tempValue = platform.toLowerCase();
 if (tempValue.contains("android")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else {
        result = Result.RESULT_UNKNOWN;
 }
    return new EmulatorCheckResult(result, platform);
}
基帶信息檢查

基帶信息(gsm.version.baseband),是安卓系統(tǒng)變量文件build.prop中的一個(gè)參數(shù),用于描述該設(shè)備的基帶信息,部分模擬器中該值是存在特定值的(AS自帶模擬器),由于該值是在基帶芯片中寫入的,因而大部門市面主流的模擬器,該值都是無法獲取的。

因此該因素可以作為一個(gè)判斷模擬器的因素,且該值無法獲取時(shí),大概率是模擬器,具體值在下方代碼中已經(jīng)列出,該值獲取失敗時(shí),是模擬器的可能性非常大。
/**
 * 特征參數(shù)-基帶信息
 */
private EmulatorCheckResult checkFeaturesByBaseBand() {
    String baseBandVersion = getProperty("gsm.version.baseband");
 if (null == baseBandVersion) {
        return new EmulatorCheckResult(Result.RESULT_MAYBE_EMULATOR, null);
 }
    Result result;
 if (baseBandVersion.contains("1.0.0.0")) {
        result = Result.RESULT_CONFIRM_EMULATOR;
 } else {
        result = Result.RESULT_UNKNOWN;
 }
    return new EmulatorCheckResult(result, baseBandVersion);
}
傳感器數(shù)量檢查

當(dāng)前(2020年初),大部分市面上銷售的手機(jī)都具有很多傳感器(例如陀螺儀、溫度傳感器、光線傳感器等),我個(gè)人的小米9 Pro 5G手機(jī),傳感器檢測出的數(shù)量多達(dá)42個(gè),而模擬器中,該數(shù)量僅為個(gè)位數(shù)。因此該因素可以作為一個(gè)判斷模擬器的因素,且該值數(shù)量較少(<7)時(shí),大概率是模擬器,判斷邏輯在下方已經(jīng)列出。
/**
 * 獲取傳感器數(shù)量
 */
private int getSensorNumber(Context context) {
    SensorManager sm = (SensorManager) context.getSystemService(SENSOR_SERVICE);
 return sm.getSensorList(Sensor.TYPE_ALL).size();
}
第三方應(yīng)用數(shù)量檢查

根據(jù)我們?nèi)粘5氖謾C(jī)使用經(jīng)驗(yàn)可以得知,正常的用戶一般都會(huì)下載安裝多個(gè)應(yīng)用軟件(微信、支付寶、微博、抖音、視頻軟件、游戲軟件等),而模擬器中的軟件數(shù)量一般較少。因此該因素可以作為一個(gè)判斷模擬器的因素,且該值數(shù)量較少(<5)時(shí),大概率是模擬器,但該因素不絕對(duì)正確,可能一個(gè)用戶手機(jī)上一個(gè)軟件都沒有,也可能一個(gè)模擬器上應(yīng)用軟件非常多,因此該因素?zé)o法絕對(duì)確定,判斷邏輯在下方已經(jīng)列出。
/**
 * 獲取已安裝第三方應(yīng)用數(shù)量
 */
private int getUserAppNumber() {
    String userApps = exec("pm list package -3");
 return getUserAppNum(userApps);
}
相機(jī)支持檢查

部分模擬器的某些版本,是不支持相機(jī)的,因此該因素也可以作為一個(gè)判斷模擬器的因素,但是由于一些非常低端的、非常早期的手機(jī)也是不支持相機(jī)的,因此該因素也無法絕對(duì)確定,獲取是否支持相機(jī)的代碼邏輯在下方已經(jīng)列出。
/**
 * 是否支持相機(jī)
 */
private boolean supportCamera(Context context) {
    return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
}
閃光燈支持檢查

與相機(jī)不同,我調(diào)研的目前市面上的主流模擬器(包含支持相機(jī)的模擬器)基本都不支持閃光燈,因此該因素也可以作為一個(gè)判斷模擬器的因素,但是由于一些非常低端的、非常早期的手機(jī)也是不支持閃光燈的,因此該因素也無法絕對(duì)確定,獲取是否支持閃光燈的代碼邏輯在下方已經(jīng)列出。
/**
 * 是否支持閃光燈
 */
private boolean supportCameraFlash(Context context) {
    return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
}
藍(lán)牙支持檢查

與閃光燈類似,我調(diào)研的目前市面上的主流模擬器(包含支持相機(jī)的模擬器)基本都不支持藍(lán)牙,因此該因素也可以作為一個(gè)判斷模擬器的因素,但是由于一些非常低端的、非常早期的手機(jī)也是不支持藍(lán)牙的,因此該因素也無法絕對(duì)確定,獲取是否支持藍(lán)牙的代碼邏輯在下方已經(jīng)列出。
/**
 * 是否支持藍(lán)牙
 */
private boolean supportBluetooth(Context context) {
    return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
}
光傳感器支持檢查

與閃光燈類似,我調(diào)研的目前市面上的主流模擬器(包含支持相機(jī)的模擬器)基本都不支持光傳感器,因此該因素也可以作為一個(gè)判斷模擬器的因素,但是由于一些非常低端的、非常早期的手機(jī)也是不支持光傳感器的,因此該因素也無法絕對(duì)確定,獲取是否支持光傳感器的代碼邏輯在下方已經(jīng)列出。
/**
 * 判斷是否存在光傳感器來判斷是否為模擬器
 * 部分真機(jī)也不存在溫度和壓力傳感器。其余傳感器模擬器也存在。
 */
private boolean hasLightSensor(Context context) {
    SensorManager sensorManager = (SensorManager) context.getSystemService(SENSOR_SERVICE);
 Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
 if (null == sensor) {
        return false;
 } else {
        return true;
 }
}
進(jìn)程組信息檢查

在真機(jī)中,進(jìn)程組信息都會(huì)存儲(chǔ)在/proc/self/cgroup目錄中,使用cat命令可以查看到該信息,但某些模擬器是不會(huì)寫入該信息的,因此該檢查也可以作為一個(gè)模擬器檢查的因素,但是因?yàn)椴唤^對(duì),因此無法100%確定是模擬器。
/**
 * 特征參數(shù)-進(jìn)程組信息
 */
private EmulatorCheckResult checkFeaturesByCgroup() {
    String filter = exec("cat /proc/self/cgroup");
 if (null == filter) {
        return new EmulatorCheckResult(Result.RESULT_MAYBE_EMULATOR, null);
 }
    return new EmulatorCheckResult(Result.RESULT_UNKNOWN, filter);
}
核心判斷邏輯

到這里,可以發(fā)現(xiàn),上述的各個(gè)因素,都無法確定程序的運(yùn)行環(huán)境一定是模擬器,而是只存在一定可能性。

因此,該工具類綜合使用了上述所有的因素來綜合確定當(dāng)前是否是模擬器

(例如,當(dāng)前有一個(gè)設(shè)備同時(shí)滿足了好幾個(gè)條件:用戶安裝的第三方軟件不到5個(gè)、檢查不到基帶信息、沒有相機(jī)、也沒有光線傳感器,我們就可以認(rèn)為這個(gè)設(shè)備是模擬器了)。

代碼中引入了一個(gè)變量

currentDoubt
懷疑值,來代表對(duì)當(dāng)前設(shè)備是模擬器的懷疑程度,滿足一個(gè)條件,該值就+1,這樣就可以根據(jù)這個(gè)值來確定當(dāng)前是不是模擬器。

設(shè)置這個(gè)值的閾值,就可以調(diào)整這個(gè)工具類對(duì)于模擬器探測的敏感程度(比如:設(shè)置為1,表示非常敏感,只要有一個(gè)因素滿足了就認(rèn)為是模擬器;

設(shè)置為10,表示比較不敏感,必須有10個(gè)因素都不滿足,才認(rèn)為是模擬器)。

當(dāng)前代碼中,這個(gè)值設(shè)置為3,即三個(gè)因素同時(shí)達(dá)到了,就認(rèn)為當(dāng)前設(shè)備是模擬器。
public boolean check(Context context) {
    if (context == null) {
        Log.e(TAG, "check(), context is null!");
 return false;
 }
    currentDoubt = 0;

 //檢測硬件名稱
 EmulatorCheckResult hardwareResult = checkFeaturesByHardware();
 if (handleCheckResult(hardwareResult, CheckType.HARDWARE_NAME)) {
        return true;
 }

    //檢測渠道
 EmulatorCheckResult flavorResult = checkFeaturesByFlavor();
 if (handleCheckResult(flavorResult, CheckType.FLAVOR)) {
        return true;
 }

    //檢測設(shè)備型號(hào)
 EmulatorCheckResult modelResult = checkFeaturesByModel();
 if (handleCheckResult(modelResult, CheckType.DEVICE_MODULE)) {
        return true;
 }

    //檢測硬件制造商
 EmulatorCheckResult manufacturerResult = checkFeaturesByManufacturer();
 if (handleCheckResult(manufacturerResult, CheckType.MANUFACTURER)) {
        return true;
 }

    //檢測主板名稱
 EmulatorCheckResult boardResult = checkFeaturesByBoard();
 if (handleCheckResult(boardResult, CheckType.BOARD)) {
        return true;
 }

    //檢測主板平臺(tái)
 EmulatorCheckResult platformResult = checkFeaturesByPlatform();
 if (handleCheckResult(platformResult, CheckType.PLATFORM)) {
        return true;
 }

    //檢測基帶信息
 EmulatorCheckResult baseBandResult = checkFeaturesByBaseBand();
 if (handleCheckResult(baseBandResult, CheckType.BASE_BAND)) {
        return true;
 }

    //檢測傳感器數(shù)量
 int sensorNumber = getSensorNumber(context);
 if (sensorNumber <= 7) {
        ++currentDoubt;
 }

    //檢測已安裝第三方應(yīng)用數(shù)量
 int userAppNumber = getUserAppNumber();
 if (userAppNumber <= 5) {
        ++currentDoubt;
 }

    //檢測是否支持閃光燈
 boolean supportCameraFlash = supportCameraFlash(context);
 if (!supportCameraFlash) {
        ++currentDoubt;
 }
    //檢測是否支持相機(jī)
 boolean supportCamera = supportCamera(context);
 if (!supportCamera) {
        ++currentDoubt;
 }
    //檢測是否支持藍(lán)牙
 boolean supportBluetooth = supportBluetooth(context);
 if (!supportBluetooth) {
        ++currentDoubt;
 }

    //檢測光線傳感器
 boolean hasLightSensor = hasLightSensor(context);
 if (!hasLightSensor) {
        ++currentDoubt;
 }

    //檢測進(jìn)程組信息
 EmulatorCheckResult cGroupResult = checkFeaturesByCgroup();
 if (cGroupResult.result == Result.RESULT_CONFIRM_EMULATOR) {
        ++currentDoubt;
 }

    //可疑值>3,就認(rèn)為是模擬器,可調(diào)整這個(gè)值,調(diào)節(jié)認(rèn)為是模擬器的靈敏度
 return currentDoubt > 3;
}
測試結(jié)果

經(jīng)過本人、本人所在團(tuán)隊(duì)同事的測試,該工具類在如下環(huán)境中驗(yàn)證通過:

Android Studio 自帶模擬器、網(wǎng)易MUMU模擬器、夜神模擬器、Xiaomi 9 Pro 5G、Xiaomi 6X、Google Pixel 2、Huawei Mate20 Pro、Huawei 暢享9、Huawei Mate30 Pro等機(jī)型上驗(yàn)證通過。

由于條件有限,無法驗(yàn)證全部機(jī)型與模擬器,如果在使用中遇到了檢測錯(cuò)誤的情況,請(qǐng)?jiān)诖颂幜粞裕覀兛梢砸黄饋砀倪M(jìn)該工具類。

真正在生產(chǎn)環(huán)境使用該工具類時(shí),還需要在灰度升級(jí)過程中,根據(jù)用戶規(guī)模、日志回傳情況,來調(diào)節(jié)懷疑值的閾值,以達(dá)到一個(gè)檢測效果與設(shè)備規(guī)模的平衡。