技术总结博客
Android 向下兼容问题
技术概述
我们的软件最早是兼容安卓8及以上系统的,但是后来考虑到一部分人的手机不一定支持,所以要由Android 8.1向下适配至Android 5.1
在Android系统中向下兼容性比较差,但是一个应用APP经过处理还是可以在各个版本间运行的。向下兼容性不好,不同版本的系统其API版本也不同,自然有些接口也不同,新的平台不能使用旧的API,旧的平台也使用不了新的API。为了应用APP有更好的兼容性,咱们可以利用高版本的SDK开发应用,并在程序运行时(Runtime)对应用所运行的平台判断,旧平台使用旧的API,而新平台可使用新的API,这样可以较好的提高软件兼容性。
技术详述
鉴于ANDROID SDK 更新较快,很多新的特性和API在低版本中的可能没有。所以开发过程中尽量要保持对新功能接口的兼容。
一般开发过程中APP都会有一个最低版本的配置,例如如果要兼容到android 2.2系统,则可以设置minSdkVersion=8,这就表明能向下兼容到android 2.2版本,即APP能在android2.2版本上的手机也能正常运行,即使可能某些新特性的功能支持失效,但至少保证不会出现崩溃的问题,而避免此问题的方式就要求开发者在代码中做好兼容和适配。
兼容原则
一般选择APP的最低支持版本原则是尽量向下保持兼容,但也不是说越向下越好,主要的考虑因素有以下几点:
-
各个低版本手机的市场占有率,比如2013年android 2.3的手机还占用一定的市场份额,但到现在为止基本上该份额可以忽略不计了
-
APP的针对用户群体,比如是高端的用户群体,屌丝用户群体,还是中低端用户群体,根据不同的用户群体可以综合出来决定对最低版本的支持。
实战分析及解决问题
如某个工程配置中的最低版本是android2.2,也就是正常来说开发过程中需要基于android SDK为8来做工程开发。但如果你没有基于adroid 2.2 SDK版本开发,而是支持了一个更高的版本,比如android 4.0 SDK开发,那么很多高版本的功能特性(2.3—4.0)在4.0以下的手机中运行就可以存在问题,一般的结果就是直接crash。
下面是基于android2.2 SDK 开发环境编译的最新的工程,其中就有一些直接编译运行不过的错误。下面可以看几个实例:
SampleActivity.java有一处这样写的:
if (savedInstanceState !=null) {
mOrderId =savedInstanceState.getString(EXTRA_ORDER_ID);
mPaySuccess =savedInstanceState.getString(EXTRA_PAY_SUCCESS,"");
}
代码中使用Bundle对象在新版本中才提供的方法而没有加兼容处理,如下官方文档中解释,该方法在android 3.1后才有。
public String getString (String key, String defaultValue) Added in API level 12
如果在低于android 3.0下机器运行和编译该代码,如果不做任何处理,会直接编译通不过。
解决方法:
1. 用android提供的注解 @TargetApi(11)+ 版本号控制做兼容
如果是基于高版本的SDK开发,则新的api肯定会有该方法,如果想让编译的版本在低版本中也能运行,则需要考虑到版本兼容的问题,可以用如下的方式:
/***
* 该api版本兼容获取指定参数
* @param savedInstanceState
* @return
*/
@TargetApi(12)
privateString getPaySucess(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= 12) {
mPaySuccess = savedInstanceState.getString(EXTRA_PAY_SUCCESS,"");
} else {
mPaySuccess = savedInstanceState.getString(EXTRA_PAY_SUCCESS);
if (mPaySuccess ==null){
mPaySuccess = "";
}
}
returnmPaySuccess;
}
2. 用反射的方式调用高版本中的新功能接口进行调用。
如果是基于低版本SDK开发,那么新版本中的新接口肯定会编译不过,这时候可以考虑反射的方式先去查找是否存在这个方法,如果有就代表用户的手机支持该调用方法,如果没有则采用低版本的处理方式。
/***
* 通过放射的方式来获取Bundle中的
* getString(String key,String value)方法
* @return
*/
privateStringgetPaySucessInvoke(Bundle savedInstanceState) {
try {
Class<?> c = Class.forName("android.os.bundle");
Method mGetString2Params =c.getDeclaredMethod("getString", String.class,String.class);
if (mGetString2Params !=null) {
mPaySuccess = (String)mGetString2Params.invoke(null,EXTRA_PAY_SUCCESS,"");
} else {
mPaySuccess = savedInstanceState.getString(EXTRA_PAY_SUCCESS);
if (mPaySuccess ==null){
mPaySuccess ="";
}
}
} catch (Exception e) {
// TODO: handle exception
}
returnmPaySuccess;
}
3. 分离代码,分别在不同的SDK上编译运行,最后ClassLoader动态加载高版本中的相关类接口
此方法应用场景如2,可以将高版本的api接口封装后在高版本的SDK中编译运行jar包,供旧版本的工程中动态加载。
总结
高版本sdk和低版本各有各的好处,基于低版本的SDK开发优点就是你可以支持的手机用户会更多,基本上各个版本的用户都可以用你的应用。
缺点也是非常明显,特别是对开发者来说,需要做好每一个新特性功能的适配和开发,随着版本越来越高,这对开发者后期的维护会越来越困难,越来越多。
基于高版本的SDK开发的话, 优点就是你可以使用最新的功能的api,而且编译也不会出现任何问题。
但是缺点就是你需要时刻对你调用的api保持向下兼容性,因为很有可能你现有调用的某个api在低版本中根本就不存在。这时候你需要考虑低版本系统的用户的运行问题了。
还有个比较土的向下兼容解决方法就是改下minSdkVersion,然后对着ide慢慢改错误,就是比较麻烦