`
410063005
  • 浏览: 179981 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

(翻译) 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:

 

 

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

 

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

 

使用反射

 

假设想使用一个新的api, 比如android.os.Debug.dumpHprofData(String name)。 Debug类已经在android 1.0中存在, 但是方法是在Android 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()方法, 该方法进行方法查询。 如果查询成功, 就使用跟原始语法相同的方式(argumnets, return value, checked exceptions)来调用这个私有方法。 返回值(如果有的话)和异常以类似于原始方式的形式被解包和返回。 fiddle()方法展示了应用的逻辑是如何来选择调用新的API,或者是根据新的API是否存在来干点别的事。 

 

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

 

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

 

使用包装类

 

思路是添加一个新的包装类, 其作用是包装新添加的API(这些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;
   }
}

 

然后为NewClass创建一个包装类

 

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

 

 

这个包装类WrapNewClass包含原始类NewClass的各个方法(包括构造方法)的对应的包装方法, 另外还有一个静态初始化块用于检查NewClass类是否存在(注:这里有个小问题, 如果NewClass不存在,WrapNewClass的编译不是通不过吗?答案是, 一般采用新版本的SDK开发, 所以编译不成问题。 但是目标环境可能只支持低版本的SDK, 所以不存在NewClass的定义)。  如果 NewClass不存在, WrapNewClass的初始化过程失败, 注意应保证WrapNewClass(即包装类)不被随意使用。  checkAvailable()方法用于强制执行WrapNewClass的静态初始化块(注:这个初始化块会加载NewClass)。 我们这样使用:

 

 

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()方法调用成功, 我们就知道新的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来查看帮助信息。 

 

分享到:
评论

相关推荐

    .NET Core Succinctly

    With .NET Core, cross-platform develop is easier and backward compatibility is no problem. Author Giancarlo Lelli guides you through the fundamentals of .NET Core in his latest book, .NET Core ...

    Computing_DDR3L_H5TC4G4

    III (DDR3L) Synchronous DRAM, ideally suited for the main memory applications which requires large memory density, high bandwidth and low power operation at 1.35V. DDR3L SDRAM provides backward ...

    usb3.0协议

    2000 to provide a third transfer rate of 480 Mbps while retaining backward compatibility. By 2006, two things in the environment happened: the transfer rates of HDDs exceeded 100MB/s, far outstripping...

    struts2官方开发文档

    We've started planning the next version of Struts aka Struts 3 (or 2.5) which will break backward compatibility, if you want to join please add your two cents here. Getting Started The documentation ...

    Learning PHP 7 High Performance(PACKT,2016)

    PHP 7 is the latest version, providing major backward-compatibility breaks and focusing on high performance and speed. This fast-paced introduction to ...

    Learning PHP 7 High Performance

    About This Book, Make the optimum use of ...providing major backward-compatibility breaks and focusing on high performance and speed., This fast-paced introduction to ...

    Odoo开发手册(英文版)

    Odoo, formerly known as OpenERP, is a great platform for developers. The framework at its core is very rich ...compatibility, most of the presented material should still work with the upcoming versions.

    MMC V6.0 R02C

    - The standard ensures backward compatibility with older versions of the MMC specification while introducing new features and capabilities. - This compatibility is crucial for maintaining a smooth ...

    CUDA_C_Programming_Guide

    Application compatibility refers to the ability of a CUDA program to run on different versions of the CUDA runtime and driver, ensuring backward compatibility. **3.1.5 C/C++ Compatibility** CUDA ...

    building restful web services with spring 5 2e

    Follow the best approaches for dealing with a service’s evolution while maintaining backward compatibility Understand techniques to secure web services Comply with the best ways to test RESTful web ...

    Mastering TypeScript

    The TypeScript compiler and language has brought JavaScript development up to the enterprise level, yet still maintains backward compatibility with existing JavaScript browsers and libraries. ...

    Fstream: Managing Flash Sreams in the File System PPT

    It helps in maintaining the traditional block interface, which is essential for backward compatibility with existing applications and operating systems. #### Garbage Collection and Write ...

    Xcode_Build_Setting_Reference

    It ensures backward compatibility with previous versions of the library. Adjusting this value allows developers to manage changes in the library’s API without breaking existing applications that ...

    Mastering.SoapUI.178398080X

    You will also learn how to integrate SoapUI with Jenkins for CI and SoapUI test with QC with backward- and forward-compatibility. The final part of the book will show you how to virtualize a service ...

    EurekaLog_7.5.0.0_Enterprise

    2)....Added: --el_DisableDebuggerPresent command-line option for compatibility with 3rd party debuggers (AQTime, etc.) 3)....Added: AQTime auto-detect 4)....Fixed: Performance optimizations 5).......

    The.Fortran.2003.Handbook

    Its longevity can be attributed to its initial role as one of the first high-level languages, its standardization, and its continuous enhancement while maintaining backward compatibility. This ...

Global site tag (gtag.js) - Google Analytics