12

我有使用 Android 2.1 的应用程序,它利用 LocationManager 来获取高度。但是现在,我需要使用需要 API 级别 9 (2.3) 的 SensorManager 来获取高度。

如何SensorManager.getAltitude(float, float)通过放置条件并通过函数名称调用它(在普通 Java 中可能)将其放入我的 2.1 android 应用程序中?

先感谢您

更新 1 如果您注意到我的应用程序需要使用 Android 2.1 进行编译。这就是为什么我正在寻找一种通过名称或任何其他可以编译的方式调用函数的方法。

4

6 回答 6

22

您需要针对您需要的最高 api 进行构建,然后为您想要支持的其他级别有条件地编写备用代码路径

要在执行时检查当前 API 级别,Android 文档的最新建议是执行以下操作:

    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD)
    {
        ...

但是,一旦引入了这种复杂性,就必须非常小心。目前没有自动检查所有代码路径的方法,以确保 minSdkVersion 之上的所有 api 级别调用都有替代调用来支持所有版本。如果存在可以执行此类操作的单元测试工具,也许有人可以插话。

于 2011-09-14T01:20:31.773 回答
9

您可以使用反射调用该方法,并在出现错误(例如缺少类或方法)时优雅地失败。见java.lang.reflect

另一种选择是在第 9 级编译代码,但用 try/catch 包围以捕获在较低级别执行时可能出现的错误。不过,这可能很容易出错,我会三思而后行。


更新

这是测试代码

public void onCreate(Bundle savedInstanceState)
{
    try {
        // First we try reflection approach.
        // Expected result
        //    in 2.3 we print some value in log but no exception
        //    in 2.2 we print NoSuchMethodException
        // In both levels we get our screen displayed after catch
        Method m = SensorManager.class.getMethod("getAltitude",Float.TYPE, Float.TYPE);
        Float a = (Float)m.invoke(null, 0.0f, 0.0f);
        Log.w("test","Result 1: " + a);
    } catch (Throwable e) {
        Log.e("test", "error 1",e);
    }

    try {
        // Now we try compiling against 2.3
        // Expected result
        //    in 2.3 we print some value in log but no exception
        //    in 2.2 we print NoSuchMethodError (Note that it is an error not exception but it's still caught)
        // In both levels we get our screen displayed after catch
        float b = SensorManager.getAltitude(0.0f, 0.0f);
        Log.w("test","Result 2: " + b);

    } catch (Throwable e) {
        Log.e("test", "error 2",e);
    }

    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
}

结果:

2.3

09-14 07:04:50.374: DEBUG/dalvikvm(589): Debugger has detached; object registry had 1 entries
09-14 07:04:50.924: WARN/test(597): Result 1: NaN
09-14 07:04:51.014: WARN/test(597): Result 2: NaN
09-14 07:04:51.384: INFO/ActivityManager(75): Displayed com.example/.MyActivity: +1s65ms

2.2

09-14 07:05:48.220: INFO/dalvikvm(382): Could not find method android.hardware.SensorManager.getAltitude, referenced from method com.example.MyActivity.onCreate
09-14 07:05:48.220: WARN/dalvikvm(382): VFY: unable to resolve static method 2: Landroid/hardware/SensorManager;.getAltitude (FF)F
09-14 07:05:48.220: DEBUG/dalvikvm(382): VFY: replacing opcode 0x71 at 0x0049
09-14 07:05:48.220: DEBUG/dalvikvm(382): VFY: dead code 0x004c-0064 in Lcom/example/MyActivity;.onCreate (Landroid/os/Bundle;)V
09-14 07:05:48.300: ERROR/test(382): error 1
        java.lang.NoSuchMethodException: getAltitude
        at java.lang.ClassCache.findMethodByName(ClassCache.java:308)

跳过堆栈跟踪

09-14 07:05:48.300: ERROR/test(382): error 2
    java.lang.NoSuchMethodError: android.hardware.SensorManager.getAltitude
    at com.example.MyActivity.onCreate(MyActivity.java:35)

跳过更多堆栈跟踪

09-14 07:05:48.330: DEBUG/dalvikvm(33): GC_EXPLICIT freed 2 objects / 64 bytes in 180ms
09-14 07:05:48.520: INFO/ActivityManager(59): Displayed activity com.example/.MyActivity: 740 ms (total 740 ms)
于 2011-09-14T01:11:23.843 回答
4

您可以利用在访问类之前不加载类的方式,以便轻松解决不需要反射的问题。您使用带有静态方法的内部类来使用您的新 API。这是一个简单的例子。

public static String getEmail(Context context){
    try{
        if(Build.VERSION.SDK_INT > 4) return COMPATIBILITY_HACK.getEmail(context);
        else return "";
    }catch(SecurityException e){
        Log.w(TAG, "Forgot to ask for account permisisons");
        return "";
    }
}


//Inner class required so incompatibly phones won't through an error when this class is accessed. 
    //this is the island of misfit APIs
    private static class COMPATIBILITY_HACK{

        /**
         * This takes api lvl 5+
         * find first gmail address in account and return it
         * @return
         */
        public static String getEmail(Context context){
            Account[] accounts = AccountManager.get(context).getAccountsByType("com.google");
            if(accounts != null && accounts.length > 0) return accounts[0].name;
            else return "";
        }
     }
于 2011-09-14T01:25:07.107 回答
3

当问题是“我在当前 API 级别是否有此类方法?” 然后使用分支,如:

class SomeClass {
    public void someMethod(){
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD)
        {
           //use classes and/or methods that were added in GINGERBREAD
        }
    }
}

为此,您需要使用 Gingerbread 或更高版本的 Android 库。否则,代码将无法使用 Gingerbread 中添加的类进行编译。

这个解决方案比令人作呕的反射更干净。请注意,dalvik 将记录一个(非致命的)错误,指出在尝试加载 SomeClass 时找不到在 GINGERBREAD 中添加的类,但应用程序不会崩溃。如果我们尝试使用该特定类并进入 IF 分支,它只会崩溃 - 但我们不会这样做(除非我们在 GINGERBREAD 或更高版本上)。

请注意,当您有一个永远存在的类但在 Gingerbread 中添加了一个新方法时,该解决方案也有效。在运行时,如果您在 pre-Gingerbread 上运行,您只需不进入 IF 分支并且不调用该方法,因此应用程序不会崩溃。

于 2012-10-25T12:49:14.027 回答
0

在这里,您如何使用反射(从不可用的级别调用 StrictMode 类:

  try {
          Class<?> strictmode = Class.forName("android.os.StrictMode");
          Method enableDefaults = strictmode.getMethod("enableDefaults");
          enableDefaults.invoke(null, new Object[] {});
  } catch (Exception e) {
          Log.i(TAG, e.getMessage());
  }
于 2011-09-14T02:51:36.517 回答
0

我还没有尝试过 - 但应该可以使用一些代码生成来创建一个代理库(每个 API 级别),它将包装整个本机 android.jar 并且其实现将尝试从 android.jar 调用方法.

这个代理库将使用上面提到的内部静态类方式或反射来使 dalvikvm 懒惰地链接到请求的方法。

它将让用户访问她想要的所有 API(假设她将检查正确的 API 级别)并防止令人不快的 dalvikvm 日志消息。您还可以嵌入每个方法的 API 级别并抛出一个可用的异常(BadApiLevelException 或其他东西)

(任何人都知道为什么 Google/Android 还没有做这样的事情吗?)

于 2011-10-10T18:21:39.023 回答