Android: Как мне вызвать метод, который существует на другом уровне API? - PullRequest
12 голосов
/ 14 сентября 2011

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

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

Заранее спасибо

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

Ответы [ 6 ]

22 голосов
/ 14 сентября 2011

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

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

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

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

8 голосов
/ 14 сентября 2011

Вы можете вызвать метод, используя отражение, и изящно завершить работу в случае ошибок (например, пропущенный класс или методы). См. 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)
5 голосов
/ 14 сентября 2011

Вы можете воспользоваться тем, как класс не загружается до тех пор, пока к нему не будет получен доступ, для удобства обхода, который не требует размышлений. Вы используете внутренний класс со статическими методами, чтобы использовать ваш новый 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 "";
        }
     }
3 голосов
/ 25 октября 2012

Когда возникает вопрос "Есть ли у меня класс или метод на текущем уровне 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 и не вызываете этот метод, поэтому приложение не будет аварийно завершено.

0 голосов
/ 10 октября 2011

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

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

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

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

0 голосов
/ 14 сентября 2011

Вот как вы делаете это, используя отражение (вызов класса 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());
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...