我希望在模拟器上运行的代码与在设备上运行的代码略有不同。(例如,使用10.0.2.2代替公共URL自动针对开发服务器运行。)检测Android应用程序何时在模拟器中运行的最佳方法是什么?
我希望在模拟器上运行的代码与在设备上运行的代码略有不同。(例如,使用10.0.2.2代替公共URL自动针对开发服务器运行。)检测Android应用程序何时在模拟器中运行的最佳方法是什么?
Answers:
该解决方案如何:
fun isProbablyAnEmulator() = Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.startsWith("unknown")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.BOARD == "QC_Reference_Phone" //bluestacks
|| Build.MANUFACTURER.contains("Genymotion")
|| Build.HOST.startsWith("Build") //MSI App Player
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|| "google_sdk" == Build.PRODUCT
请注意,某些仿真器会伪造真实设备的确切规格,因此可能无法检测到。
您可以在APK中制作一个小片段来显示有关它的各种内容,因此可以添加自己的规则:
textView.text = "FINGERPRINT:${Build.FINGERPRINT}\n" +
"MODEL:${Build.MODEL}\n" +
"MANUFACTURER:${Build.MANUFACTURER}\n" +
"BRAND:${Build.BRAND}\n" +
"DEVICE:${Build.DEVICE}\n" +
"BOARD:${Build.BOARD}\n" +
"HOST:${Build.HOST}\n" +
"PRODUCT:${Build.PRODUCT}\n"
一种常见的一种说法是 Build.FINGERPRINT.contains("generic")
FINGERPRINT
值。
Android id不适用于我,我目前正在使用:
"google_sdk".equals( Build.PRODUCT );
根据其他答案的提示,这可能是最可靠的方法:
isEmulator = "goldfish".equals(Build.HARDWARE)
isEmulator = Build.HARDWARE.contains("golfdish")
Google使用Flutter 的device-info插件中的以下代码来确定设备是否为模拟器:
private boolean isEmulator() {
return (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|| Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.startsWith("unknown")
|| Build.HARDWARE.contains("goldfish")
|| Build.HARDWARE.contains("ranchu")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| Build.PRODUCT.contains("sdk_google")
|| Build.PRODUCT.contains("google_sdk")
|| Build.PRODUCT.contains("sdk")
|| Build.PRODUCT.contains("sdk_x86")
|| Build.PRODUCT.contains("vbox86p")
|| Build.PRODUCT.contains("emulator")
|| Build.PRODUCT.contains("simulator");
}
类似于下面的代码(告诉您的应用是否已使用调试键签名)怎么样?它没有检测到仿真器,但可能对您有用吗?
public void onCreate Bundle b ) {
super.onCreate(savedInstanceState);
if ( signedWithDebugKey(this,this.getClass()) ) {
blah blah blah
}
blah
blah
blah
}
static final String DEBUGKEY =
"get the debug key from logcat after calling the function below once from the emulator";
public static boolean signedWithDebugKey(Context context, Class<?> cls)
{
boolean result = false;
try {
ComponentName comp = new ComponentName(context, cls);
PackageInfo pinfo = context.getPackageManager().getPackageInfo(comp.getPackageName(),PackageManager.GET_SIGNATURES);
Signature sigs[] = pinfo.signatures;
for ( int i = 0; i < sigs.length;i++)
Log.d(TAG,sigs[i].toCharsString());
if (DEBUGKEY.equals(sigs[0].toCharsString())) {
result = true;
Log.d(TAG,"package has been signed with the debug key");
} else {
Log.d(TAG,"package signed with a key other than the debug key");
}
} catch (android.content.pm.PackageManager.NameNotFoundException e) {
return false;
}
return result;
}
BuildConfig.DEBUG
。
该代码对我有用
TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String networkOperator = tm.getNetworkOperatorName();
if("Android".equals(networkOperator)) {
// Emulator
}
else {
// Device
}
如果该设备没有SIM卡,它将重新调整空字符串:“”
由于Android模拟器总是将“ Android”重新调谐为网络运营商,因此我使用上面的代码。
我尝试了几种技术,但选择了对Build.PRODUCT进行稍微修改的版本,如下所示。各个仿真器之间的差异似乎相差很大,这就是为什么我要拥有当前拥有的3张支票的原因。我想我可以检查一下product.contains(“ sdk”),但认为以下检查更安全。
public static boolean isAndroidEmulator() {
String model = Build.MODEL;
Log.d(TAG, "model=" + model);
String product = Build.PRODUCT;
Log.d(TAG, "product=" + product);
boolean isEmulator = false;
if (product != null) {
isEmulator = product.equals("sdk") || product.contains("_sdk") || product.contains("sdk_");
}
Log.d(TAG, "isEmulator=" + isEmulator);
return isEmulator;
}
仅供参考-我发现我的Kindle Fire拥有Build.BRAND =“ generic”,并且某些仿真器没有网络运营商的“ Android”。
我只是寻找_sdk
,_sdk_
或sdk_
,甚至只是sdk
参与其中Build.PRODUCT
:
if(Build.PRODUCT.matches(".*_?sdk_?.*")){
//-- emulator --
}else{
//-- other device --
}
contains("sdk")
呢?唯一的区别(不是更快)是matches(".*_?sdk_?.*")
要求在sdk之前或之后有一个字符,它必须是下划线'_',这并不是很重要的检查。
使用此功能:
public static final boolean isEmulator() {
int rating = 0;
if ((Build.PRODUCT.equals("sdk")) || (Build.PRODUCT.equals("google_sdk"))
|| (Build.PRODUCT.equals("sdk_x86")) || (Build.PRODUCT.equals("vbox86p"))) {
rating++;
}
if ((Build.MANUFACTURER.equals("unknown")) || (Build.MANUFACTURER.equals("Genymotion"))) {
rating++;
}
if ((Build.BRAND.equals("generic")) || (Build.BRAND.equals("generic_x86"))) {
rating++;
}
if ((Build.DEVICE.equals("generic")) || (Build.DEVICE.equals("generic_x86")) || (Build.DEVICE.equals("vbox86p"))) {
rating++;
}
if ((Build.MODEL.equals("sdk")) || (Build.MODEL.equals("google_sdk"))
|| (Build.MODEL.equals("Android SDK built for x86"))) {
rating++;
}
if ((Build.HARDWARE.equals("goldfish")) || (Build.HARDWARE.equals("vbox86"))) {
rating++;
}
if ((Build.FINGERPRINT.contains("generic/sdk/generic"))
|| (Build.FINGERPRINT.contains("generic_x86/sdk_x86/generic_x86"))
|| (Build.FINGERPRINT.contains("generic/google_sdk/generic"))
|| (Build.FINGERPRINT.contains("generic/vbox86p/vbox86p"))) {
rating++;
}
return rating > 4;
}
这是我的解决方案(仅当您在调试计算机上运行Web服务器时,该方法才有效):我创建了一个后台任务,该任务在应用程序启动时启动。它查找http://10.0.2.2,如果存在,它将一个全局参数(IsDebug)更改为true。这是找出您在哪里跑步的一种无声方式。
public class CheckDebugModeTask extends AsyncTask<String, Void, String> {
public static boolean IsDebug = false;
public CheckDebugModeTask()
{
}
@Override
protected String doInBackground(String... params) {
try {
HttpParams httpParameters = new BasicHttpParams();
int timeoutConnection = 1000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
int timeoutSocket = 2000;
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
String url2 = "http://10.0.2.2";
HttpGet httpGet = new HttpGet(url2);
DefaultHttpClient client = new DefaultHttpClient(httpParameters);
HttpResponse response2 = client.execute(httpGet);
if (response2 == null || response2.getEntity() == null || response2.getEntity().getContent() == null)
return "";
return "Debug";
} catch (Exception e) {
return "";
}
}
@Override
protected void onPostExecute (String result)
{
if (result == "Debug")
{
CheckDebugModeTask.IsDebug = true;
}
}
从onCreate的主要活动中:
CheckDebugModeTask checkDebugMode = new CheckDebugModeTask();
checkDebugMode.execute("");
另一个选择是查看ro.hardware属性,并查看其是否设置为goldfish。不幸的是,似乎没有一种简单的方法可以从Java做到这一点,但是使用property_get()从C做到这一点却微不足道。
以上建议的解决方案可以ANDROID_ID
为我检查是否有效,直到我今天更新为随Android 2.2一起发布的最新SDK工具。
因此,我目前切换到以下解决方案,该解决方案目前为止仍具有缺点,但是您需要设置PHONE_STATE读取权限(<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
)
private void checkForDebugMode() {
ISDEBUGMODE = false; //(Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID) == null);
TelephonyManager man = (TelephonyManager) getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE);
if(man != null){
String devId = man.getDeviceSoftwareVersion();
ISDEBUGMODE = (devId == null);
}
}
所有答案合而为一
static boolean checkEmulator()
{
try
{
String buildDetails = (Build.FINGERPRINT + Build.DEVICE + Build.MODEL + Build.BRAND + Build.PRODUCT + Build.MANUFACTURER + Build.HARDWARE).toLowerCase();
if (buildDetails.contains("generic")
|| buildDetails.contains("unknown")
|| buildDetails.contains("emulator")
|| buildDetails.contains("sdk")
|| buildDetails.contains("genymotion")
|| buildDetails.contains("x86") // this includes vbox86
|| buildDetails.contains("goldfish")
|| buildDetails.contains("test-keys"))
return true;
}
catch (Throwable t) {Logger.catchedError(t);}
try
{
TelephonyManager tm = (TelephonyManager) App.context.getSystemService(Context.TELEPHONY_SERVICE);
String non = tm.getNetworkOperatorName().toLowerCase();
if (non.equals("android"))
return true;
}
catch (Throwable t) {Logger.catchedError(t);}
try
{
if (new File ("/init.goldfish.rc").exists())
return true;
}
catch (Throwable t) {Logger.catchedError(t);}
return false;
}
init.goldfish.rc
仅存在于模拟器中;除了构建细节外,它还是一个不错的检查。
我找到了新的模拟器 Build.HARDWARE = "ranchu"
。
参考:https : //groups.google.com/forum/#!topic/android-emulator- dev/ dltBnUW_HzU
我也找到了Android官方方法来检查是否模拟器,我认为这对我们来说是很好的参考。
自Android API Level 23 [Android 6.0]起
package com.android.internal.util;
/**
* @hide
*/
public class ScreenShapeHelper {
private static final boolean IS_EMULATOR = Build.HARDWARE.contains("goldfish");
}
我们必须ScreenShapeHelper.IS_EMULATOR
检查模拟器。
自Android API Level 24 [Android 7.0]起
package android.os;
/**
* Information about the current build, extracted from system properties.
*/
public class Build {
/**
* Whether this build was for an emulator device.
* @hide
*/
public static final boolean IS_EMULATOR = getString("ro.kernel.qemu").equals("1");
}
我们必须Build.IS_EMULATOR
检查模拟器。
上面提到的答案还包括官方检查模拟器是否新颖的方法,也许还不够。
但这也许向我们表明,官员将提供官员检查模拟器的方式。
正如上面提到的所有方法一样,现在我们还可以使用两种方法来检查是否为模拟器。
如何访问com.android.internal
包和@hide
然后等待官方开放的SDK。
我的建议:
试试这个从GitHub。
易于检测的Android模拟器
- 在Device Farm(https://aws.amazon.com/device-farm/)中
- BlueStacks
- Genymotion
- Android模拟器
- 安迪46.2.207.0
- MEmu玩
- Nox应用程式播放器
- Koplayer
- .....
如何搭配范例使用:
EmulatorDetector.with(this)
.setCheckTelephony(true)
.addPackageName("com.bluestacks")
.setDebug(true)
.detect(new EmulatorDetector.OnEmulatorDetectorListener() {
@Override
public void onResult(boolean isEmulator) {
}
});
您可以检查IMEI#, http://developer.android.com/reference/android/telephony/TelephonyManager.html#getDeviceId%28%29
如果我记得在模拟器上返回0。但是,没有文档可以保证这一点。尽管仿真器可能不会始终返回0,但注册的电话不会返回0似乎非常安全。在非电话的android设备上,未安装SIM卡的设备或当前未在SIM卡上注册的设备上会发生的情况。网络?
依靠它似乎是一个坏主意。
这也意味着您需要请求读取手机状态的许可,如果您还不需要其他条件的话,那就不好了。
如果不是那样,那么在最终生成签名的应用程序之前,总会有一些麻烦。
0
在Android平板电脑或没有SIM卡的手机上返回。
Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")
如果应用程序在模拟器上运行,则应返回true。
我们应该注意的是不要检测所有仿真器,因为只有几个不同的仿真器。很容易检查。我们必须确保没有将实际设备检测为仿真器。
我使用了名为“ Android设备信息共享 ”对此进行了检查。
在此应用程序上,您可以看到许多设备的各种信息(可能是世界上大多数设备;如果列表中缺少您正在使用的设备,则会自动添加该信息)。
实际上,2.2上的ANDROID_ID始终等于9774D56D682E549C(根据该线程 +我自己的实验)。
因此,您可以检查如下内容:
String androidID = ...;
if(androidID == null || androidID.equals("9774D56D682E549C"))
do stuff;
不是最漂亮的,但能做到。
将文件放入仿真器的文件系统中;由于该文件在实际设备上将不存在,因此该文件应该稳定,可靠并且易于修复。
我已经收集了有关此问题的所有答案,并提出了功能来检测Android是否在vm / emulator上运行:
public boolean isvm(){
StringBuilder deviceInfo = new StringBuilder();
deviceInfo.append("Build.PRODUCT " +Build.PRODUCT +"\n");
deviceInfo.append("Build.FINGERPRINT " +Build.FINGERPRINT+"\n");
deviceInfo.append("Build.MANUFACTURER " +Build.MANUFACTURER+"\n");
deviceInfo.append("Build.MODEL " +Build.MODEL+"\n");
deviceInfo.append("Build.BRAND " +Build.BRAND+"\n");
deviceInfo.append("Build.DEVICE " +Build.DEVICE+"\n");
String info = deviceInfo.toString();
Log.i("LOB", info);
Boolean isvm = false;
if(
"google_sdk".equals(Build.PRODUCT) ||
"sdk_google_phone_x86".equals(Build.PRODUCT) ||
"sdk".equals(Build.PRODUCT) ||
"sdk_x86".equals(Build.PRODUCT) ||
"vbox86p".equals(Build.PRODUCT) ||
Build.FINGERPRINT.contains("generic") ||
Build.MANUFACTURER.contains("Genymotion") ||
Build.MODEL.contains("Emulator") ||
Build.MODEL.contains("Android SDK built for x86")
){
isvm = true;
}
if(Build.BRAND.contains("generic")&&Build.DEVICE.contains("generic")){
isvm = true;
}
return isvm;
}
在模拟器,Genymotion和Bluestacks上进行了测试(2015年10月1日)。
检查答案,在使用LeapDroid,Droid4x或Andy仿真器时,它们都无效,
适用于所有情况的内容如下:
private static String getSystemProperty(String name) throws Exception {
Class systemPropertyClazz = Class.forName("android.os.SystemProperties");
return (String) systemPropertyClazz.getMethod("get", new Class[]{String.class}).invoke(systemPropertyClazz, new Object[]{name});
}
public boolean isEmulator() {
boolean goldfish = getSystemProperty("ro.hardware").contains("goldfish");
boolean emu = getSystemProperty("ro.kernel.qemu").length() > 0;
boolean sdk = getSystemProperty("ro.product.model").equals("sdk");
return goldfish || emu || sdk;
}
由于Genymotion的基础仿真引擎是VirtualBox,并且不会很快改变,因此我发现以下代码最可靠:
public static boolean isGenymotion() {
return Build.PRODUCT != null && Build.PRODUCT.contains("vbox");
}
无论使用哪种代码来执行模拟器检测,我会强烈建议编写单元测试,以覆盖所有的Build.FINGERPRINT
,Build.HARDWARE
而Build.MANUFACTURER
你是取决于值。以下是一些示例测试:
@Test
public void testIsEmulatorGenymotion() throws Exception {
assertThat(
DeviceUtils.isRunningOnEmulator(
"generic/vbox86p/vbox86p:4.1.1/JRO03S/eng.buildbot.20150217.102902:userdebug/test-keys",
"vbox86", "Genymotion")).isTrue();
assertThat(
DeviceUtils.isRunningOnEmulator(
"generic/vbox86p/vbox86p:5.1/LMY47D/buildbot06092001:userdebug/test-keys", "vbox86",
"Genymotion")).isTrue();
}
@Test
public void testIsEmulatorDefaultAndroidEmulator() throws Exception {
assertThat(
DeviceUtils.isRunningOnEmulator(
"generic_x86/sdk_google_phone_x86/generic_x86:5.0.2/LSY66H/1960483:eng/test-keys", "goldfish",
"unknown")).isTrue();
assertThat(
DeviceUtils.isRunningOnEmulator(
"Android/sdk_google_phone_x86_64/generic_x86_64:6.0/MASTER/2469028:userdebug/test-keys",
"ranchu", "unknown")).isTrue();
}
@Test
public void testIsEmulatorRealNexus5() throws Exception {
assertThat(
DeviceUtils.isRunningOnEmulator("google/hammerhead/hammerhead:6.0.1/MMB29K/2419427:user/release-keys",
"hammerhead", "LGE")).isFalse();
}
...这是我们的代码(为简洁起见,删除了调试日志和注释):
public static boolean isRunningOnEmulator() {
if (sIsRunningEmulator == null) {
sIsRunningEmulator = isRunningOnEmulator(Build.FINGERPRINT, Build.HARDWARE, Build.MANUFACTURER);
}
return sIsRunningEmulator;
}
static boolean isRunningOnEmulator(String fingerprint, String hardware, String manufacturer) {
boolean isEmulatorFingerprint = fingerprint.endsWith("test-keys");
boolean isEmulatorManufacturer = manufacturer.equals("Genymotion")
|| manufacturer.equals("unknown");
if (isEmulatorFingerprint && isEmulatorManufacturer) {
return true;
} else {
return false;
}
}
另一个选择是检查您是处于调试模式还是生产模式:
if (BuildConfig.DEBUG) { Log.i(TAG, "I am in debug mode"); }
简单可靠。
并非完全是问题的答案,但是在大多数情况下,您可能希望区分调试/测试会话和用户群的生命周期。
就我而言,我在调试模式下将Google Analytics(分析)设置为dryRun(),因此这种方法对我来说完全可以使用。
对于更高级的用户,还有另一种选择。gradle构建变体:
在应用程序的gradle文件中添加新的变体:
buildTypes {
release {
// some already existing commands
}
debug {
// some already existing commands
}
// the following is new
test {
}
}
在您的代码中检查构建类型:
if ("test".equals(BuildConfig.BUILD_TYPE)) { Log.i(TAG, "I am in Test build type"); }
else if ("debug".equals(BuildConfig.BUILD_TYPE)) { Log.i(TAG, "I am in Debug build type"); }
现在,您有机会构建3种不同类型的应用程序。
android.os.Build
。