读书人

(通译) Backward Compatibility for A

发布时间: 2013-01-23 10:44:49 作者: rapoo

(翻译) Backward Compatibility for Applications

原文来自Android SDK文档中的?docs/resources/articles/backward-compatibility.html

?

目前有各种Android设备。 这些设备使用不同的Android版本, 有些运行最新的版本, 有些运行较老的版本。 作为开发者, 当考虑如何在应用中保持向后兼容——你是想让你的应用在所有Android设备上运行, 还是只能在最新的版本上运行? 有时有必要既享受新的API带来的便利(如果设备支持的话), 同时继续兼容老的设备。

?

设置minSdkVersion

?

如果应用的重要功能使用了新的API(原文if the use of new API is intergral to the application)——比如需要使用Android 1.5(API Level 3)中引入的新的API录制视频, 那么应当在应用的manifest中添加<android:minSdkVersion>,以保证这个应用不会被安装到更老的设备当中。 如果应用依赖于API Level 3中引入的新API, 应当指定minSdkVersion的值为3:

?

?

?

但是, 如果你只是给应用增加了一个有用但非核心的特性, 比如在可使用实体键盘的情况下提供一个软键盘, 可以使用如下这种方式:既允许在新设备上使用这个特性, 同时不会在老的设备上引起错误。?

?

使用反射

?

假设想使用一个新的api, 比如android.os.Debug.dumpHprofData(String name)。 Debug类已经在android 1.0中存在, 但是方法是在Android 1.5(API Level 3)中新引入的。 如果你直接调用这个方法, 应用会在Android 1.1或更老的设备上崩溃。?

?

最简单的办法是使用反射来调用这个方法。 ?这需要进行一次方法查询并将结果保存在一个Method对象上, 然后调用Method.invoke()方法, 最后解包该方法的返回值。 考虑下面这段代码:

?

?

?

这里使用一个静态块来调用 initCompatibility()方法, 该方法进行方法查询。 如果查询成功, 就使用跟原始语法相同的方式(argumnets, return value, checked exceptions)来调用这个私有方法。 返回值(如果有的话)和异常以类似于原始方式的形式被解包和返回。 fiddle()方法展示了应用的逻辑是如何来选择调用新的API,或者是根据新的API是否存在来干点别的事。?

?

对每个想调用的新方法, 需要在当前类中添加一个额外的私有Method成员变量, 该成员变量对应的初始化方法, 以及调用包装器(原文: call wrapper)。?

?

如果想调用的方法来自于先前未定义的类(注: 比如Android 1.0中没有, 但是Android 1.1新添加的类), 上述过程变得稍微有些复杂。 另外 , 调用Method.invode()比直接调用会慢很多。 可以使用一个包装类(Wrapper class)来部分减少这两个问题。

?

使用包装类

?

思路是添加一个新的包装类, 其作用是包装新添加的API(这些API可能来自已存在的类, 或是新添加的类)。 包装类中的每个方法仅仅是调用相应的目标方法并返回执行结果。?

?

如果目标类和方法存在, 可以直接调用这些类并且有完全一致的行为, 当然, 额外的方法调用会带来少量的性能开销。 如果目标类或方法不存在, 包装类的初始化过程会失败, 应用就知道应当避免调用这些新方法。 考虑新加了如下类:

?

?

?

?

如果checkAvailable()方法调用成功, 我们就知道新的class在系统中存在;如果调用失败, 则不存在, 我们需要随之调整预期。 需要注意的是, 如果字节码校验器确信它不想接受这样一个类, 该类的某个成员变量的Class对象根本不存在(注:在 老版本的设备上可能出现这种情况, WrapNewClass的成员变量mInstance的Class对象不存在), 那么 checkAvailable()方法有可能在开始执行之前就失败。 上面代码的这种写法, 可以保证无论异常是来自字节码校验器还是Class.forName()调用, 执行结果都是一致的。

?

?当包装一个添加了新方法的已存在的类, 只需要将新添加的方法的包装方法添加到这个包装类;要使用原本存在的方法, 直接调用即可。 WrapNewClass的静态块会随着每个反射调用增大。(注:对每个可能的新class需要进行检查, 意味着多个Class.forName()调用)。 ?

?

测试是王道

?

必须在每个版本的Android平台上测试应用是否如期望的那样能够正常运行。 应用在不同的平台上(注:这里的不同平台应该指的是API发生了变化的平台, 而且应用刚好使用反射方法使用了这些API), 其行为应当不一致。 牢记: 如果没验证过, 它很可能不正确。?

?

可以在老版本的模拟器上测试应用的向后兼容性。 Android SDK可以方便地使用不同的API Level创建"Android虚拟设备"。 创建好AVDs之后, 就可以使用新的和老版本来测试, 还能同时打开不同版本的模拟器来观察应用程序行为。 更多信息可以参考文档中的 Creating and Managing Virtual Devices一章, 或者运行emulator -help -virtual-device来查看帮助信息。?

?

读书人网 >移动开发

热点推荐