• 安卓判断是否是模拟器,适配主流雷电,MUMU,夜神,逍遥


    前言

    最近游戏项目组又有新的要求,对于数据上报和数据统计接口,尽可能的具体化,比如是否是模拟器,模拟器的型号,品牌等,都要求统计,后续模拟器玩家在活动发放,安全风控等方面也易于分析和把控。

    实现

    在网上搜了搜,大概思路是:

    1:模拟器的cpu是x86,arm的,通过cpu信息判断

    2:模拟器的传感器比较少,尤其没有光传感器等

    3:模拟器没有蓝牙模块,可以通过蓝牙判断,这里没有考虑,毕竟需要动态权限

    Manifest.permission.BLUETOOTH_CONNECT

    在隐私合规的大环境下,还是尽量避免获取多的权限

    4:通过部分特征参数,比如Build.FINGERPRINT、 Build.MODEL、Build.BRAND

    5:通过模拟器特有文件检测

    下面具体贴上工具类代码:

    1. package com.xx.xx.myapplication;
    2. import android.bluetooth.BluetoothAdapter;
    3. import android.content.Context;
    4. import android.content.pm.PackageManager;
    5. import android.hardware.Sensor;
    6. import android.hardware.SensorManager;
    7. import android.os.Build;
    8. import android.text.TextUtils;
    9. import android.util.Log;
    10. import java.io.BufferedReader;
    11. import java.io.File;
    12. import java.io.IOException;
    13. import java.io.InputStreamReader;
    14. import java.util.ArrayList;
    15. import java.util.List;
    16. public class EmutorUtils {
    17. private static final String TAG = "EmutorUtils";
    18. private static final String[] PKG_NAMES = {"com.mumu.launcher", "com.ami.duosupdater.ui", "com.ami.launchmetro",
    19. "com.ami.syncduosservices", "com.bluestacks.home", "com.bluestacks.windowsfilemanager",
    20. "com.bluestacks.settings", "com.bluestacks.bluestackslocationprovider", "com.bluestacks.appsettings",
    21. "com.bluestacks.bstfolder", "com.bluestacks.BstCommandProcessor", "com.bluestacks.s2p", "com.bluestacks.setup",
    22. "com.bluestacks.appmart", "com.kaopu001.tiantianserver", "com.kpzs.helpercenter", "com.kaopu001.tiantianime",
    23. "com.android.development_settings", "com.android.development", "com.android.customlocale2", "com.genymotion.superuser",
    24. "com.genymotion.clipboardproxy", "com.uc.xxzs.keyboard", "com.uc.xxzs", "com.blue.huang17.agent", "com.blue.huang17.launcher",
    25. "com.blue.huang17.ime", "com.microvirt.guide", "com.microvirt.market", "com.microvirt.memuime", "cn.itools.vm.launcher",
    26. "cn.itools.vm.proxy", "cn.itools.vm.softkeyboard", "cn.itools.avdmarket", "com.syd.IME", "com.bignox.app.store.hd",
    27. "com.bignox.launcher", "com.bignox.app.phone", "com.bignox.app.noxservice", "com.android.noxpush", "com.haimawan.push",
    28. "me.haima.helpcenter", "com.windroy.launcher", "com.windroy.superuser", "com.windroy.launcher", "com.windroy.ime",
    29. "com.android.flysilkworm", "com.android.emu.inputservice", "com.tiantian.ime", "com.microvirt.launcher", "me.le8.androidassist",
    30. "com.vphone.helper", "com.vphone.launcher", "com.duoyi.giftcenter.giftcenter"};
    31. private static final String[] FILES = {"/data/data/com.android.flysilkworm", "/data/data/com.bluestacks.filemanager"};
    32. public static String checkFeaturesByHardware(Context context) {
    33. String result = "";
    34. String hardware = getProperty("ro.hardware");
    35. if (null == hardware)
    36. return "unknown";
    37. String tempValue = hardware.toLowerCase();
    38. Log.d(TAG,tempValue);
    39. if(tempValue.startsWith("cancro")){
    40. result = "MUMU模拟器";
    41. }else if(tempValue.contains("nox")){
    42. result = "夜神模拟器";
    43. }else if(tempValue.equals("android_x86")){
    44. result= "雷电模拟器";
    45. }else{
    46. List pathList = getInstalledSimulatorPackages(context);
    47. result = getSimulatorBrand(pathList);
    48. }
    49. return result;
    50. }
    51. private static String getProperty(String propName) {
    52. String value = null;
    53. Object roSecureObj;
    54. try {
    55. roSecureObj = Class.forName("android.os.SystemProperties")
    56. .getMethod("get", String.class)
    57. .invoke(null, propName);
    58. if (roSecureObj != null) value = (String) roSecureObj;
    59. } catch (Exception e) {
    60. value = null;
    61. } finally {
    62. return value;
    63. }
    64. }
    65. private static List getInstalledSimulatorPackages(Context context) {
    66. ArrayList localArrayList = new ArrayList();
    67. try {
    68. for (int i = 0; i < PKG_NAMES.length; i++)
    69. try {
    70. context.getPackageManager().getPackageInfo(PKG_NAMES[i], PackageManager.GET_ACTIVITIES);
    71. localArrayList.add(PKG_NAMES[i]);
    72. } catch (PackageManager.NameNotFoundException localNameNotFoundException) {
    73. }
    74. if (localArrayList.size() == 0) {
    75. for (int i = 0; i < FILES.length; i++) {
    76. if (new File(FILES[i]).exists())
    77. localArrayList.add(FILES[i]);
    78. }
    79. }
    80. } catch (Exception e) {
    81. e.printStackTrace();
    82. }
    83. return localArrayList;
    84. }
    85. private static String getSimulatorBrand(List list) {
    86. if (list.size() == 0)
    87. return "";
    88. String pkgName = list.get(0);
    89. if (pkgName.contains("mumu")) {
    90. return "mumu";
    91. } else if (pkgName.contains("ami")) {
    92. return "AMIDuOS";
    93. } else if (pkgName.contains("bluestacks")) {
    94. return "蓝叠";
    95. } else if (pkgName.contains("kaopu001") || pkgName.contains("tiantian")) {
    96. return "天天";
    97. } else if (pkgName.contains("kpzs")) {
    98. return "靠谱助手";
    99. } else if (pkgName.contains("genymotion")) {
    100. if (Build.MODEL.contains("iTools")) {
    101. return "iTools";
    102. } else if ((Build.MODEL.contains("ChangWan"))) {
    103. return "畅玩";
    104. } else {
    105. return "genymotion";
    106. }
    107. } else if (pkgName.contains("uc")) {
    108. return "uc";
    109. } else if (pkgName.contains("blue")) {
    110. return "blue";
    111. } else if (pkgName.contains("microvirt")) {
    112. return "逍遥";
    113. } else if (pkgName.contains("itools")) {
    114. return "itools";
    115. } else if (pkgName.contains("syd")) {
    116. return "手游岛";
    117. } else if (pkgName.contains("bignox")) {
    118. return "夜神";
    119. } else if (pkgName.contains("haimawan")) {
    120. return "海马玩";
    121. } else if (pkgName.contains("windroy")) {
    122. return "windroy";
    123. } else if (pkgName.contains("flysilkworm")) {
    124. return "雷电";
    125. } else if (pkgName.contains("emu")) {
    126. return "emu";
    127. } else if (pkgName.contains("le8")) {
    128. return "le8";
    129. } else if (pkgName.contains("vphone")) {
    130. return "vphone";
    131. } else if (pkgName.contains("duoyi")) {
    132. return "多益";
    133. }
    134. return "";
    135. }
    136. public static boolean isEmulator(Context context){
    137. return notHasLightSensorManager(context)
    138. ||isFeatures()
    139. ||checkIsNotRealPhone()
    140. ||checkPipes() ||isYeshenEmulator();
    141. }
    142. public static String getPhoneBrand(){
    143. return android.os.Build.BRAND;
    144. }
    145. public static String getPhoneModel(){
    146. return android.os.Build.MODEL;
    147. }
    148. /*
    149. *用途:判断蓝牙是否有效来判断是否为模拟器
    150. *返回:true 为模拟器
    151. */
    152. // private static boolean notHasBlueTooth() {
    153. // BluetoothAdapter ba = BluetoothAdapter.getDefaultAdapter();
    154. // if (ba == null) {
    155. // return true;
    156. // } else {
    157. // // 如果有蓝牙不一定是有效的。获取蓝牙名称,若为null 则默认为模拟器
    158. // String name = ba.getName();
    159. // if (TextUtils.isEmpty(name)) {
    160. // return true;
    161. // } else {
    162. // return false;
    163. // }
    164. // }
    165. // }
    166. /*
    167. *用途:依据是否存在光传感器来判断是否为模拟器
    168. *返回:true 为模拟器
    169. */
    170. private static Boolean notHasLightSensorManager(Context context) {
    171. SensorManager sensorManager = (SensorManager) context.getSystemService(context.SENSOR_SERVICE);
    172. Sensor sensor8 = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); //光
    173. if (null == sensor8) {
    174. return true;
    175. } else {
    176. return false;
    177. }
    178. }
    179. /*
    180. *用途:根据部分特征参数设备信息来判断是否为模拟器
    181. *返回:true 为模拟器
    182. */
    183. private static boolean isFeatures() {
    184. return Build.FINGERPRINT.startsWith("generic")
    185. || Build.FINGERPRINT.toLowerCase().contains("vbox")
    186. || Build.FINGERPRINT.toLowerCase().contains("test-keys")
    187. || Build.MODEL.contains("google_sdk")
    188. || Build.MODEL.contains("Emulator")
    189. || Build.MODEL.contains("Android SDK built for x86")
    190. || Build.MANUFACTURER.contains("Genymotion")
    191. || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
    192. || "google_sdk".equals(Build.PRODUCT);
    193. }
    194. /*
    195. *用途:根据CPU是否为电脑来判断是否为模拟器
    196. *返回:true 为模拟器
    197. */
    198. private static boolean checkIsNotRealPhone() {
    199. String cpuInfo = readCpuInfo();
    200. if ((cpuInfo.contains("intel") || cpuInfo.contains("amd"))) {
    201. return true;
    202. }
    203. return false;
    204. }
    205. /*
    206. *用途:根据CPU是否为电脑来判断是否为模拟器(子方法)
    207. *返回:String
    208. */
    209. private static String readCpuInfo() {
    210. String result = "";
    211. try {
    212. String[] args = {"/system/bin/cat", "/proc/cpuinfo"};
    213. ProcessBuilder cmd = new ProcessBuilder(args);
    214. Process process = cmd.start();
    215. StringBuffer sb = new StringBuffer();
    216. String readLine = "";
    217. BufferedReader responseReader = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"));
    218. while ((readLine = responseReader.readLine()) != null) {
    219. sb.append(readLine);
    220. }
    221. responseReader.close();
    222. result = sb.toString().toLowerCase();
    223. } catch (IOException ex) {
    224. }
    225. return result;
    226. }
    227. /*
    228. *用途:检测模拟器的特有文件
    229. *返回:true 为模拟器
    230. */
    231. private static String[] known_pipes = {"/dev/socket/qemud", "/dev/qemu_pipe"};
    232. private static boolean checkPipes() {
    233. for (int i = 0; i < known_pipes.length; i++) {
    234. String pipes = known_pipes[i];
    235. File qemu_socket = new File(pipes);
    236. if (qemu_socket.exists()) {
    237. Log.v("Result:", "Find pipes!");
    238. return true;
    239. }
    240. }
    241. Log.i("Result:", "Not Find pipes!");
    242. return false;
    243. }
    244. //****************适配夜神模拟器*******************
    245. //获取 cpu 信息
    246. private static String getCpuInfo() {
    247. String[] abis = new String[]{};
    248. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    249. abis = Build.SUPPORTED_ABIS;
    250. } else {
    251. abis = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
    252. }
    253. StringBuilder abiStr = new StringBuilder();
    254. for (String abi : abis) {
    255. abiStr.append(abi);
    256. abiStr.append(',');
    257. }
    258. return abiStr.toString();
    259. }
    260. // 通过cpu判断是否模拟器 ,适配夜神
    261. private static boolean isYeshenEmulator() {
    262. String abiStr = getCpuInfo();
    263. if (abiStr != null && abiStr.length() > 0) {
    264. boolean isSupportX86 = false;
    265. boolean isSupportArm = false;
    266. if (abiStr.contains("x86_64") || abiStr.contains("x86")) {
    267. isSupportX86 = true;
    268. }
    269. if (abiStr.contains("armeabi") || abiStr.contains("armeabi-v7a") || abiStr.contains("arm64-v8a")) {
    270. isSupportArm = true;
    271. }
    272. if (isSupportX86 && isSupportArm) {
    273. //同时拥有X86和arm的判断为模拟器。
    274. return true;
    275. }
    276. }
    277. return false;
    278. }
    279. }

    使用

    1. boolean flag = EmutorUtils.isEmulator(MainActivity.this);
    2. Toast.makeText(MainActivity.this,"是否是模拟器:"+flag,Toast.LENGTH_SHORT).show();
    3. button.setOnClickListener(new View.OnClickListener() {
    4. @Override
    5. public void onClick(View v) {
    6. String name = EmutorUtils.checkFeaturesByHardware(MainActivity.this);
    7. String brand = EmutorUtils.getPhoneBrand();
    8. String model = EmutorUtils.getPhoneModel();
    9. String result = "name:"+name+"brand:"+brand+"model:"+model;
    10. Toast.makeText(MainActivity.this,result,Toast.LENGTH_SHORT).show();
    11. }
    12. });

    效果图

    mumu

    雷电

    逍遥

    夜神

    官方AVD(用这个玩游戏基本没有)

    参考 

    http://dxtdbj.com/article.php?id=268
    https://cloud.tencent.com/developer/article/2019963 

  • 相关阅读:
    docker笔记
    下一个风口在哪里?云计算:未来十年最有潜力行业!
    在 Django Model ViewSet 中实现多对多字段的搜索
    数字化转型将为企业带来什么样的变革?
    【AI应用】NVIDIA GeForce RTX 1080Ti的详情参数
    DSL查询文档
    oracle-buffer cache
    【JavaScript复习十一】数组内置对象方法一
    Java基础---第五篇
    可视化模块
  • 原文地址:https://blog.csdn.net/qq_34123324/article/details/132827002