Подтвердить что ты не робот

Android: Как я могу назвать метод, существующий на другом уровне API?

У меня есть приложение, использующее Android 2.1, который использует LocationManager для получения высоты. Но теперь мне нужно получить высоту с помощью SensorManager, для которого требуется уровень API 9 (2.3).

Как я могу поместить SensorManager.getAltitude(float, float) в мое приложение для Android 2.1, поставив условие и вызвав его именем функции (возможно в обычной Java)?

Заранее благодарю

ОБНОВЛЕНИЕ 1 Если вы заметили, что мое приложение необходимо скомпилировать с помощью Android 2.1. Вот почему я ищу способ вызова функции по имени или любым другим способом, который может быть скомпилирован.

4b9b3361

Ответ 1

Вы можете вызвать метод с использованием отражения и изящно с ошибкой в ​​случае ошибок (например, отсутствующий класс или методы). См. java.lang.reflect

Другим вариантом является компиляция кода на уровне 9, но окружение с помощью try/catch, чтобы уловить ошибки, которые возникли бы при выполнении на более низком уровне. Хотя это может быть довольно склонно к ошибкам, и я бы дважды подумал об этом.


Update

Вот тестовый код

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 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)

Ответ 2

Вам нужно построить против самого высокого api, который вам нужен, а затем закодировать альтернативные коды кода условно для других уровней, которые вы хотите поддерживать

Чтобы проверить текущий уровень API во время выполнения, последняя рекомендация из документов Android состоит в том, чтобы сделать что-то вроде этого:

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

Как только вы вводите эту сложность, вы должны быть очень осторожны. В настоящее время нет автоматического способа проверки всех путей кода, чтобы убедиться, что все вызовы уровня api над minSdkVersion имеют альтернативные вызовы для поддержки всех версий. Может быть, кто-то может перезвонить, если существует инструмент тестирования единицы, который может сделать что-то вроде этого.

Ответ 3

Вы можете воспользоваться тем, как класс не загружается до тех пор, пока он не будет доступен для простой работы, что не требует отражения. Вы используете внутренний класс со статическими методами для использования вашего нового apis. Вот простой пример.

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 "";
        }
     }

Ответ 4

Когда возникает вопрос: "У меня есть этот класс или метод на текущем уровне 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
        }
    }
}

Для этого вам нужно использовать библиотеку Android, которая является Gingerbread или выше. В противном случае код не будет компилироваться с классами, добавленными в Gingerbread.

Это решение намного чище, чем отвратительное отражение. Обратите внимание, что dalvik будет регистрировать (не смертельную) ошибку, заявляя, что он не может найти классы, добавленные в GINGERBREAD при попытке загрузить SomeClass, но приложение не будет разбиваться. Это может привести к сбою, если мы попытаемся использовать этот конкретный класс и ввести ветвь IF - но мы этого не делаем (если только мы не находимся на GINGERBREAD или позже).

Обратите внимание, что решение также работает, когда у вас есть класс, который был там навсегда, но новый новый метод был добавлен в Gingerbread. Во время выполнения, если вы работаете на pre-Gingerbread, вы просто не вводите ветвь IF и не вызываете этот метод, поэтому приложение не будет аварийно.

Ответ 5

Вот как вы это делаете, используя отражение (вызов класса 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());
  }

Ответ 6

Я не пробовал - но для создания прокси-библиотеки (по уровню API), которая будет обертывать весь собственный файл android.jar, реализация которого будет пытаться вызвать методы из android.jar.

Этот прокси-сервер будет использовать либо вышеупомянутый способ или отражение внутри-статического класса, чтобы заставить dalvikvm лениво ссылаться на запрошенный метод.

Это позволит пользователю получить доступ ко всему API, который она хочет (при условии, что она проверяет правильный уровень API) и предотвратит неприятные сообщения журнала dalvikvm. Вы также можете внедрить каждый уровень API-метода и использовать исключение для использования (исключение BadApiLevelException или что-то еще)

(Кто-нибудь знает, почему Google/Android еще не делает что-то подобное?)