前言
最近游戏项目组又有新的要求,对于数据上报和数据统计接口,尽可能的具体化,比如是否是模拟器,模拟器的型号,品牌等,都要求统计,后续模拟器玩家在活动发放,安全风控等方面也易于分析和把控。
实现
在网上搜了搜,大概思路是:
1:模拟器的cpu是x86,arm的,通过cpu信息判断
2:模拟器的传感器比较少,尤其没有光传感器等
3:模拟器没有蓝牙模块,可以通过蓝牙判断,这里没有考虑,毕竟需要动态权限
Manifest.permission.BLUETOOTH_CONNECT
在隐私合规的大环境下,还是尽量避免获取多的权限
4:通过部分特征参数,比如Build.FINGERPRINT、 Build.MODEL、Build.BRAND
5:通过模拟器特有文件检测
下面具体贴上工具类代码:
package com.xx.xx.myapplication;import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;public class EmutorUtils {private static final String TAG = "EmutorUtils";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[] FILES = {"/data/data/com.android.flysilkworm", "/data/data/com.bluestacks.filemanager"};public static String checkFeaturesByHardware(Context context) {String result = "";String hardware = getProperty("ro.hardware");if (null == hardware)return "unknown";String tempValue = hardware.toLowerCase();Log.d(TAG,tempValue);if(tempValue.startsWith("cancro")){result = "MUMU模拟器";}else if(tempValue.contains("nox")){result = "夜神模拟器";}else if(tempValue.equals("android_x86")){result= "雷电模拟器";}else{List pathList = getInstalledSimulatorPackages(context);result = getSimulatorBrand(pathList);}return result;}private static String getProperty(String propName) {String value = null;Object roSecureObj;try {roSecureObj = Class.forName("android.os.SystemProperties").getMethod("get", String.class).invoke(null, propName);if (roSecureObj != null) value = (String) roSecureObj;} catch (Exception e) {value = null;} finally {return value;}}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.GET_ACTIVITIES);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;}private static String getSimulatorBrand(List<String> list) {if (list.size() == 0)return "";String pkgName = list.get(0);if (pkgName.contains("mumu")) {return "mumu";} else if (pkgName.contains("ami")) {return "AMIDuOS";} else if (pkgName.contains("bluestacks")) {return "蓝叠";} else if (pkgName.contains("kaopu001") || pkgName.contains("tiantian")) {return "天天";} else if (pkgName.contains("kpzs")) {return "靠谱助手";} else if (pkgName.contains("genymotion")) {if (Build.MODEL.contains("iTools")) {return "iTools";} else if ((Build.MODEL.contains("ChangWan"))) {return "畅玩";} else {return "genymotion";}} else if (pkgName.contains("uc")) {return "uc";} else if (pkgName.contains("blue")) {return "blue";} else if (pkgName.contains("microvirt")) {return "逍遥";} else if (pkgName.contains("itools")) {return "itools";} else if (pkgName.contains("syd")) {return "手游岛";} else if (pkgName.contains("bignox")) {return "夜神";} else if (pkgName.contains("haimawan")) {return "海马玩";} else if (pkgName.contains("windroy")) {return "windroy";} else if (pkgName.contains("flysilkworm")) {return "雷电";} else if (pkgName.contains("emu")) {return "emu";} else if (pkgName.contains("le8")) {return "le8";} else if (pkgName.contains("vphone")) {return "vphone";} else if (pkgName.contains("duoyi")) {return "多益";}return "";}public static boolean isEmulator(Context context){return notHasLightSensorManager(context)||isFeatures()||checkIsNotRealPhone()||checkPipes() ||isYeshenEmulator();}public static String getPhoneBrand(){return android.os.Build.BRAND;}public static String getPhoneModel(){return android.os.Build.MODEL;}/**用途:判断蓝牙是否有效来判断是否为模拟器*返回:true 为模拟器*/
// private static boolean notHasBlueTooth() {
// BluetoothAdapter ba = BluetoothAdapter.getDefaultAdapter();
// if (ba == null) {
// return true;
// } else {
// // 如果有蓝牙不一定是有效的。获取蓝牙名称,若为null 则默认为模拟器
// String name = ba.getName();
// if (TextUtils.isEmpty(name)) {
// return true;
// } else {
// return false;
// }
// }
// }/**用途:依据是否存在光传感器来判断是否为模拟器*返回:true 为模拟器*/private static Boolean notHasLightSensorManager(Context context) {SensorManager sensorManager = (SensorManager) context.getSystemService(context.SENSOR_SERVICE);Sensor sensor8 = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); //光if (null == sensor8) {return true;} else {return false;}}/**用途:根据部分特征参数设备信息来判断是否为模拟器*返回:true 为模拟器*/private static boolean isFeatures() {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("Android SDK built for x86")|| Build.MANUFACTURER.contains("Genymotion")|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))|| "google_sdk".equals(Build.PRODUCT);}/**用途:根据CPU是否为电脑来判断是否为模拟器*返回:true 为模拟器*/private static boolean checkIsNotRealPhone() {String cpuInfo = readCpuInfo();if ((cpuInfo.contains("intel") || cpuInfo.contains("amd"))) {return true;}return false;}/**用途:根据CPU是否为电脑来判断是否为模拟器(子方法)*返回:String*/private static String readCpuInfo() {String result = "";try {String[] args = {"/system/bin/cat", "/proc/cpuinfo"};ProcessBuilder cmd = new ProcessBuilder(args);Process process = cmd.start();StringBuffer sb = new StringBuffer();String readLine = "";BufferedReader responseReader = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"));while ((readLine = responseReader.readLine()) != null) {sb.append(readLine);}responseReader.close();result = sb.toString().toLowerCase();} catch (IOException ex) {}return result;}/**用途:检测模拟器的特有文件*返回:true 为模拟器*/private static String[] known_pipes = {"/dev/socket/qemud", "/dev/qemu_pipe"};private static boolean checkPipes() {for (int i = 0; i < known_pipes.length; i++) {String pipes = known_pipes[i];File qemu_socket = new File(pipes);if (qemu_socket.exists()) {Log.v("Result:", "Find pipes!");return true;}}Log.i("Result:", "Not Find pipes!");return false;}//****************适配夜神模拟器*******************//获取 cpu 信息private static String getCpuInfo() {String[] abis = new String[]{};if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {abis = Build.SUPPORTED_ABIS;} else {abis = new String[]{Build.CPU_ABI, Build.CPU_ABI2};}StringBuilder abiStr = new StringBuilder();for (String abi : abis) {abiStr.append(abi);abiStr.append(',');}return abiStr.toString();}// 通过cpu判断是否模拟器 ,适配夜神private static boolean isYeshenEmulator() {String abiStr = getCpuInfo();if (abiStr != null && abiStr.length() > 0) {boolean isSupportX86 = false;boolean isSupportArm = false;if (abiStr.contains("x86_64") || abiStr.contains("x86")) {isSupportX86 = true;}if (abiStr.contains("armeabi") || abiStr.contains("armeabi-v7a") || abiStr.contains("arm64-v8a")) {isSupportArm = true;}if (isSupportX86 && isSupportArm) {//同时拥有X86和arm的判断为模拟器。return true;}}return false;}}
使用
boolean flag = EmutorUtils.isEmulator(MainActivity.this);Toast.makeText(MainActivity.this,"是否是模拟器:"+flag,Toast.LENGTH_SHORT).show();button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {String name = EmutorUtils.checkFeaturesByHardware(MainActivity.this);String brand = EmutorUtils.getPhoneBrand();String model = EmutorUtils.getPhoneModel();String result = "name:"+name+"brand:"+brand+"model:"+model;Toast.makeText(MainActivity.this,result,Toast.LENGTH_SHORT).show();}});
效果图
mumu
雷电
逍遥
夜神
官方AVD(用这个玩游戏基本没有)
参考
http://dxtdbj.com/article.php?id=268
https://cloud.tencent.com/developer/article/2019963