Android 应用程序的向后兼容(译)_移动开发_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > 移动开发 > Android 应用程序的向后兼容(译)

Android 应用程序的向后兼容(译)

 2011/1/15 7:48:44  LoveZhou  http://lovezhou.javaeye.com  我要评论(0)
  • 摘要:(英语水平有限,翻译的不好请多多指教与谅解)通过交通运输,消费者可以有机会使用到来自全世界的各种各样的Android设备,在众多的设备中,也运行着不同版本的Android系统平台,一些设备上运行着新版本的系统,一些设备上运行着旧版本的系统。作为一个开发者,在你的应用程序中,需要实现向下兼容――取决于你想在所有设备上运行你的程序,还是只想运行在最新的系统平台上?在有些时候,如果可以兼容较旧版本的设备同时,在支持新版本系统的设备上采用新的API将是有帮助的
  • 标签:程序 android 应用 应用程序 兼容
(英语水平有限,翻译的不好请多多指教与谅解)
通过交通运输,消费者可以有机会使用到来自全世界的各种各样的Android设备,在众多的设备中,也运行着不同版本的Android系统平台,一些设备上运行着新版本的系统,一些设备上运行着旧版本的系统。作为一个开发者,在你的应用程序中,需要实现向下兼容――取决于你想在所有设备上运行你的程序,还是只想运行在最新的系统平台上?在有些时候,如果可以兼容较旧版本的设备同时,在支持新版本系统的设备上采用新的API将是有帮助的。
设置最低的Sdk版本
如果使用新的API对应用程序有利――假设你使用Android1.5(API Level 3)中的API刻录视频文件――你应该在应用程序的功能描述文件中添加一个<android:minSdkVersion>标签,来确保你的程序不会被安装在更低版本的设备上。举例说明,如果在你的应用程序中,引用的API 为API Level 3,你应该设置“3”为最低Sdk版本。
<manifest>
   ...
   <uses-sdk android:minSdkVersion="3" />
   ...
  </manifest>
然而,如果你想加入一个实用的但是非必要的特性,就像如果硬件支持的时候在屏幕上弹出一个键盘,你可以允许你的应用使用新的特点,而同时不会在低版本的设备上失去作用(就是指应用程序,在所有的版本的设备上都可以使用,在支持新特性的设备上就可以使用新特性,不支持就不使用)。
使用反射
假设有一个新的函数你想调用,像android.os.Debug.dumpHprofData(String filename)。Debug类在1.0版本中就已经存在了,但是这个方法确实1.5版本才推出的。如果直接调用这个方法,你的应用程序在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方法,initCompatibility方法做了查找的动作。如果查找成功了,将会调用一个私有的和原来参数、返回值、异常检查都一样的方法。
返回值(如果有返回值)和异常没有被打包,它们以最原始的形式返回。fiddle方法展示了程序在调用新的方法时,根据方法是否存在而做出不同的处理。
对于每一个你想调用的新增的方法,你应该添加一个Method字段,一个静态初始化代码块,并且调用包装后的类。
对于调用一个新的方法来说,这样的实现有点复杂,调用Method.invoke方法也比直接调用方法本身要慢。通过使用包装类,这些问题可以被缓解。
使用包装类
这个思想就是通过一个类把一个已经存在的类或者一个新类,中的新方法包装起来。包装类中的每一个方法,只是通过调用真实的方法并且返回相同的结果。
如果目标类和方法已经存在,直接调用存在的方法就会得到你想要的结果,这时通过包装类这样调用时会有一点点代码重复。如果目标类或者方法不存在,包装类的初始化就会失败,你的应用程序就会知道它应该避免使用新的方法。
假设添加这样一个新的类:
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类的初始化就失败了,证明了包装类不能被使用。checkAvailable方法用来暴力初始化,我们可以这样使用:
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过程中出异常,结果都是一样的。
当一个已经存在的类增加新方法时,我们包装这个类时,只需要把新方法放在包装类中。直接执行方法即可。包装类中的静态初始化代码块会通过反射执行一次查找操作。
测试是关键
你必须在应用程序想要运行的平台版本上都进行测试。当然,在不同的平台版本上,应用程序的行为可能有所不同。记住这个咒语:如果你没测试过它,他将不会工作。
你可以通过改变模拟器的平台版本来测试你应用程序的向后兼容性。通过创建不同API版本的Android 模拟器,Android SDK 上可以很容易的实现测试。创建了不同的模拟器后,你可以在每个版本的平台上都进行一次测试,来观察有什么不同,在AVD文档中或者通过emulator -help-virtual-device.命令可以查看更多的关于模拟器得信息。
发表评论
用户名: 匿名