Android应用开发之(让您的应用向后兼容)

Android应用开发之(让你的应用向后兼容)
目前市场上存在多种类型的Android设备,它们运行在不同的SDK版本上。对开发者而言,需要考虑向后兼容性的问题,请问你是想让你的应用在多种设备上运行,还是仅在最新的版本上运行呢?大多情况下,答案应该是前者,你既想使用最新的SDK api ,同时又想支持旧设备。

设置minSdkVersion

如果你在应用中使用了新的api,如录制视频(该功能是 Android 1.5 (API Level 3) 提供的新功能),那你需要在application's manifest 中加入 <android:minSdkVersion>属性,确保你的应用不会在低版本的设备上运行,如:你的应用依赖 最低版本为API Level 3, 你需要指定“3” 作为你的最低的SDK版本:

<manifest>

...

<uses-sdk android:minSdkVersion="3" />

...

</manifest>



然而,有时候你会遇到要使用一个有用的,但不是必须的特性的情况,如在有物理键盘设备上使用软件盘,是否可以通过其他途径使你可以使用新特性而在低版本的设备上使用,又不出错呢?



使用反射

假设你想使用一个新特性如:android.os.Debug.dumpHprofData(String filename). 虽然Debug类在Android1.0已经存在,但是这个方法是在Anroid 1.5 (API Level 3)中新增的,如果你想使用用它,在 Android 1.1 或者早期的版上运行就会出错。



简单方式我们可以通过反射使用该方法,需要一次查找和缓存结果在Method对象中,调用Method.invoke执行和解析结果,可以参加如下代码:

public class Reflect {

private static Method mDebug_dumpHprofData;



static {

  initCompatibility();

};



private static void initCompatibility() {

  try {

      mDebug_dumpHprofData = Debug.class.getMethod(

              "dumpHprofData", new Class[] { String.class } );

      /* success, this is a newer device */

  } catch (NoSuchMethodException nsme) {

      /* failure, must be older device */

  }

}



private static void dumpHprofData(String fileName) throws IOException {

  try {

      mDebug_dumpHprofData.invoke(null, fileName);

  } catch (InvocationTargetException ite) {

      /* unpack original exception when possible */

      Throwable cause = ite.getCause();

      if (cause instanceof IOException) {

          throw (IOException) cause;

      } else if (cause instanceof RuntimeException) {

          throw (RuntimeException) cause;

      } else if (cause instanceof Error) {

          throw (Error) cause;

      } else {

          /* unexpected checked exception; wrap and re-throw */

          throw new RuntimeException(ite);

      }

  } catch (IllegalAccessException ie) {

      System.err.println("unexpected " + ie);

  }

}



public void fiddle() {

  if (mDebug_dumpHprofData != null) {

      /* feature is supported */

      try {

          dumpHprofData("/sdcard/dump.hprof");

      } catch (IOException ie) {

          System.err.println("dump failed!");

      }

  } else {

      /* feature not supported, do something else */

      System.out.println("dump not supported");

  }

}

}



使用静态初始化程序调用initCompatibility来查看方法,如果成功,使用一个和原来方法一模一样的私有方法来完成调用,模仿原来的方法返回值(如果有)或者抛出异常,示例中的fiddle方法显示了,应用程序如何选择调用新的api或者使用新的方法做一些不同的操作。

每增加一个你想要调用的方法,你需要添加一个私有的Method字段,字段初始化程序,并封装到类中

当一个方法声明在以前没有定义的类中,这会比较复杂,当然,调用Method.invoke()方法比直接调用个这个方法要慢得多,这些问题可以通过使用包装类来缓解

使用包装类

创建一个类来封装所有新的或者已经存在的类暴露的API,每个包装类中的方法是通过类似原方法方式返回相同结果。

如果目标类和方法存在,你会得到与直接调用类相同的行为,但需要花费少量的开销,如果目标类或方法不存在,包装类的初始化程序会失败。这样应用就要避免对新特性的调用。

假如要添加了下面这个新类:

public class NewClass {

private static int mDiv = 1;



private int mMult;



public static void setGlobalDiv(int div) {

  mDiv = div;

}



public NewClass(int mult) {

  mMult = mult;

}



public int doStuff(int val) {

  return (val * mMult) / mDiv;

}

}

我们根据它创建包装类:

class WrapNewClass {

private NewClass mInstance;



/* class initialization fails when this throws an exception */

static {

  try {

      Class.forName("NewClass");

  } catch (Exception ex) {

      throw new RuntimeException(ex);

  }

}



/* calling here forces class initialization */

public static void checkAvailable() {}



public static void setGlobalDiv(int div) {

  NewClass.setGlobalDiv(div);

}



public WrapNewClass(int mult) {

  mInstance = new NewClass(mult);

}



public int doStuff(int val) {

  return mInstance.doStuff(val);

}

}

在包装类中添加于原始类一样的构造函数与方法,并添加一个静态初始化程序测试新类是否存在,如果NewClass不存在,WrapNewClass会初始化失败,确保包装类不被随意使用,通过方法checkAcailable这种简单的方式进行类初始化,使用方法如下:

public class MyApp {

private static boolean mNewClassAvailable;



/* establish whether the "new" class is available to us */

static {

  try {

      WrapNewClass.checkAvailable();

      mNewClassAvailable = true;

  } catch (Throwable t) {

      mNewClassAvailable = false;

  }

}



public void diddle() {

  if (mNewClassAvailable) {

      WrapNewClass.setGlobalDiv(4);

      WrapNewClass wnc = new WrapNewClass(40);

      System.out.println("newer API is available - " + wnc.doStuff(10));

  } else {

      System.out.println("newer API not available");

  }

}

}

如果checkAvailable 方法调用成功,证明新类存在,如果失败,新类不不存在,需要进行异常处理。需要注意的是如果不存在新类,在执行checkAvailable 前就已经失败,字节码验证器不接受不存在类的引用,此代码是结构化的方式,最终的结果与调用Class.forName验证的异常一致

已存在的包装类新增了方法,你只需要将新方法加入到包装类中,在调用旧方法时,WrapNewClass的静态初始化程序会做一次反射的检查操作

测试很重要

你需要在你的应用支持的版本中进行测试,很明显,你的应用在不同的设备上运行时存在差异, 记住:一定要这样做

你在可以通过模拟器进行版本兼容性测试,Android SDK允许你创建多个不同版本的模拟器,注意不同版本上的差异。