android如何区分模拟器和真机

在游戏上线之后可能会遇到这样的问题,有人用模拟器恶意刷金币或者注册啊等问题,这个时候我们就需要把那些用模拟器登录玩家的某些功能屏蔽掉或者直接不让其登录,这个因人而异了。下面就说一下如何鉴别真机和模拟器,网上的方法也有很多,什么压力传感器温度传感器啊光传感器等,然而我这边经过研究之后,只有一个光传感器还算靠谱,鉴别成功率最好了。

下面直接上代码吧,不比比了。

java代码:

/**
	 * 判断是否存在光传感器来判断是否为模拟器
	 * 部分真机也不存在温度和压力传感器。其余传感器模拟器也存在。
	 * @return true 为模拟器
	 */	
public static int getmmethod(){
		boolean isMmethod  = true;
		try {
			 
			Class mclass = Class.forName("android.os.SystemProperties");
 
			Object invoker = mclass.newInstance();
        Method mmethod = mclass.getMethod("get", new Class[] { String.class,String.class });
 
			Object result = mmethod.invoke(invoker, new Object[] {"gsm.version.baseband", "0000" });
 
			Log.i("基带版本:", (String) result);
			
			if ((String) result == "0000"){
				isMmethod = false;
			}
 
		} catch (Exception e) {
        }
		
		if (!isMmethod){
			//判断是否存在光传感器来判断是否为模拟器true 为模拟器
			SensorManager sensorManager = (SensorManager) instance.getSystemService(SENSOR_SERVICE);
		    Sensor sensor8 = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); //光
if (null == sensor8) {//sensor8为空的话则是模拟器
		    	isMmethod = false;
		    	if(isRootSystem())
		    	{
		    		isMmethod = false;
		    	}
		    	else
		    	{
		    		isMmethod = true;
		    	}
		    }
		    else
		    {
		    	isMmethod = true;
		    }
		}
        if(isMmethod)
		{
			return 0;
		}
		
		return 1;
	}
public static boolean isRootSystem() {
        if(isRootSystem1()||isRootSystem2()){
            //TODO 可加其他判断 如是否装了权限管理的apk,大多数root 权限 申请需要app配合,也有不需要的,这个需要改su源码。因为管理su权限的app太多,无法列举所有的app,特别是国外的,暂时不做判断是否有root权限管理app
            //多数只要su可执行就是root成功了,但是成功后用户如果删掉了权限管理的app,就会造成第三方app无法申请root权限,此时是用户删root权限管理app造成的。
            //市场上常用的的权限管理app的包名   com.qihoo.permmgr  com.noshufou.android.su  eu.chainfire.supersu   com.kingroot.kinguser  com.kingouser.com  com.koushikdutta.superuser
            //com.dianxinos.superuser  com.lbe.security.shuame com.geohot.towelroot 。。。。。。
            return true;
        }else{
            return false;
        }
    }
private static boolean isRootSystem1() {
        File f = null;
        final String kSuSearchPaths[] = { "/system/bin/", "/system/xbin/",
                "/system/sbin/", "/sbin/", "/vendor/bin/" };
        try {
            for (int i = 0; i < kSuSearchPaths.length; i++) {
                f = new File(kSuSearchPaths[i] + "su");
                if (f != null && f.exists()&&f.canExecute()) {
                    return true;
                }
            }
        } catch (Exception e) {
        }
        return false;
    }
private static boolean isRootSystem2() {
        List pros = getPath();
        File f = null;
        try {
            for (int i = 0; i < pros.size(); i++) {
                f = new File(pros.get(i),"su");
                System.out.println("f.getAbsolutePath():"+f.getAbsolutePath());
                if (f != null && f.exists()&&f.canExecute()) {
                    return true;
                }
            }
        } catch (Exception e) {
        }
        return false;
    }
private static List getPath() {
        return Arrays.asList(System.getenv("PATH").split(":"));
    }
public static AppActivity instance = null;//类静态实例,为了方便后面静态函数的调用
 //返回实例
    public static AppActivity getInstance() {
        Log.v("AppActivity","getInstance");
    	return instance;
    }

android如何区分模拟器和真机_第1张图片

哦,记得给instance赋值,在onCreate函数最后赋值就行了:

instance = this;

android如何区分模拟器和真机_第2张图片

剩下的事情就是调用了,我实在lua中调用的:

if checkAndroid() then
    print("是模拟器");
else
    print("不是模拟器");
end


-- 检查Android是否是模拟器 
function checkAndroid()
    if device.platform == "ios" then
    elseif device.platform == "android" then
        local javaClassName = "org.cocos2dx.lua.AppActivity"
        local javaMethodName = "getmmethod"
        local javaParams = {}
        local javaMethodSig = "()I"
        local ok,ret  = require("cocos.cocos2d.luaj").callStaticMethod(javaClassName, javaMethodName, javaParams, javaMethodSig)
        
        if ok == false then
            luaPrint("luaoc调用出错 getmmethod")
        else
            return ret == 1
        end
    else
        luaPrint("getmmethod!")
    end

    return false
end

运行的结果,我是在手机上运行的:

android如何区分模拟器和真机_第3张图片

感兴趣的可以用模拟器试一下。

我刚好装了一个木木模拟器就试了一下:

 

 

时间过了半年之后,发现了一个问题,就是模拟器把root权限关掉,这个时候上面的检测居然失败了,经过查看之后发现是

isRootSystem1 和isRootSystem2 这二个函数都检测出错了。然后我就在网上查找其他的解决方法,查找了很久,终于让我找到了一点眉目,我总结了一下:

1.方案一:直接判断是否有光传感器,这个方法很直接,模拟器都没有光传感器。但是经过大量真机测试发现有一些手机是没有光传感器的,比如三星的 sm_g5510 这个破手机就没有光传感器,还有一些低端的三星手机也没有,除此之外国产机中也有一些没有光感的,但是没有三星的多,到了这里我就感觉不买三星是对的。说的有点多了,代码贴出来:

//返回:true 为模拟器

public static boolean hasLightSensor(Context context)
    {
        SensorManager sensorManager = (SensorManager) context.getSystemService(SENSOR_SERVICE);
        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); //光
        if (sensor == null)
            return true;
        else
            return false;
    }
 

这个方案和我之前的方案有点类似,但是并没有我其他的2个判断,所以不会放过一个模拟器,但是会误杀一些低端的机型,这个怎么取舍你自己看着吧。

2.方案二:通过一系列的研究,得出一个嫌疑指数,综合判断是否运行在模拟器中(基带信息、vBox、处理器、进程组信息等)

先把代码贴出来在说明:

public static boolean checkIsRunningInEmulator() {
        int suspectCount = 0;
        // 读基带信息
        String baseBandVersion = getProperty("gsm.version.baseband");
        if (baseBandVersion == null | "".equals(baseBandVersion)) {
            ++suspectCount;
        }
 
        // 读渠道信息,针对一些基于vBox的模拟器
        String buildFlavor = getProperty("ro.build.flavor");
        if (buildFlavor == null | "".equals(buildFlavor) | (buildFlavor != null && buildFlavor.contains("vbox"))) {
            ++suspectCount;
        }
 
        // 读处理器信息,这里经常会被处理
        String productBoard = getProperty("ro.product.board");
        if (productBoard == null | "".equals(productBoard)) {
            ++suspectCount;
        }
 
        // 读处理器平台,这里不常会处理
        String boardPlatform = getProperty("ro.board.platform");
        if (boardPlatform == null | "".equals(boardPlatform)) {
            ++suspectCount;
        }
 
        // 高通的cpu两者信息一般是一致的(发现不准,弃之)
       // if (productBoard != null && boardPlatform != null && !productBoard.equals(boardPlatform)) {
           // ++suspectCount;
        //}
 
        // 一些模拟器读取不到进程租信息
        String filter = exec("cat /proc/self/cgroup");
        if (filter == null || filter.length() == 0) {
            ++suspectCount;
        }
        return suspectCount >=1;
    }
 
    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 String exec(String command) {
        BufferedOutputStream bufferedOutputStream = null;
        BufferedInputStream bufferedInputStream = null;
        Process process = null;
        try {
            process = Runtime.getRuntime().exec("sh");
            bufferedOutputStream = new BufferedOutputStream(process.getOutputStream());
 
            bufferedInputStream = new BufferedInputStream(process.getInputStream());
            bufferedOutputStream.write(command.getBytes());
            bufferedOutputStream.write('\n');
            bufferedOutputStream.flush();
            bufferedOutputStream.close();
 
            process.waitFor();
 
            String outputStr = getStrFromBufferInputSteam(bufferedInputStream);
            return outputStr;
        } catch (Exception e) {
            return null;
        } finally {
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedInputStream != null) {
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (process != null) {
                process.destroy();
            }
        }
    }
 
    private static String getStrFromBufferInputSteam(BufferedInputStream bufferedInputStream) {
        if (null == bufferedInputStream) {
            return "";
        }
        int BUFFER_SIZE = 512;
        byte[] buffer = new byte[BUFFER_SIZE];
        StringBuilder result = new StringBuilder();
        try {
            while (true) {
                int read = bufferedInputStream.read(buffer);
                if (read > 0) {
                    result.append(new String(buffer, 0, read));
                }
                if (read < BUFFER_SIZE) {
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result.toString();
    }

 

判断一系列属性以此来判断是否是模拟器,在测试的过程中,我就发现那个 乐视手机 判断cpu和处理器平台信息是否一致的时候出现了误判,所以这个比较久就不用了。这个方法我是和第一个方法结合使用的,比较难搞的夜神和蓝叠模拟器也没有过去。

3.方案三:是否能拨号和其他属性

public static boolean isEmulator(Context context) {
        if (context == null) {
            return false;
        }
        String url = "tel:" + "123456";
        Intent intent = new Intent();
        intent.setData(Uri.parse(url));
        intent.setAction(Intent.ACTION_DIAL);
        // 是否可以处理跳转到拨号的 Intent
        boolean canResolverIntent = intent.resolveActivity(context.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) context.getSystemService(Context.TELEPHONY_SERVICE)).getNetworkOperatorName().toLowerCase().equals("android")
                || !canResolverIntent;
    }
 

这个方法刚开始我也以为是可以的,后来我发现蓝叠模拟器上失效了,夜神也失效了,但是木木模拟器,天天模拟器都是可以鉴别出来的。

最后我是把这三个方法结合起来使用的,先检测光传感器,这一关就可以把所有的模拟器全部过滤掉了,然后再用方案二和方案三检测再放出来一些,同时不满足方案二的所有条件,然后方案三又返回false ,这样三个判断应该会把模拟器全部过滤掉,又不会误杀过多的真机,我自己手上只有一加手机,华为手机,乐视手机 没有发现误杀的情况,如果你有误杀的情况,欢迎提出。

 

你可能感兴趣的:(android如何区分模拟器和真机)