Android 适配一篇就够 - 编译版本?support?API 兼容?图片适配?

本文介绍 Android 不同系统及图片资源的常见适配问题。

compileSdkVersion, targetSdkVersion, minSdkVersion, buildToolsVersion

  • minSdkVersion :应用支持的Android最低版本,若系统版本高于该版本则无法安装;
  • buildToolsVersion:使用哪个版本的build工具,一般build版本会随着android版本的发布而发布,所以一般选取最新的sdk版本;
  • compileSdkVersion:用什么版本的sdk编译程序,只在编译期起作用,另外support的版本要和这个版本一致,不然会出错。建议使用最新的Android系统版本;
  • targetSdkVersion :targetSdkVersion 是 Android 系统提供前向兼容的主要手段。这是什么意思呢?随着 Android 系统的升级,某个系统的 API 或者模块的行为可能会发生改变,但是为了保证老 APK 的行为还是和以前兼容。只要 APK 的 targetSdkVersion 不变,即使这个 APK 安装在新 Android 系统上,其行为还是保持老的系统上的行为,这样就保证了系统对老应用的前向兼容性;
    • 举个例子:比如某个api在Android5.0前后有改动,应用运行时系统会检测targetSdkVersion版本,如果小于21,就以旧版api功能运行,如果大于21,就以新版功能运行;
    • 若targetSdkVersion(24) > 系统版本(6.0):如果没动态申请权限就会崩溃;
    • 系统版本(6.0)> targetSdkVersion(22):如果没动态申请权限则不会崩溃,按照老版本方式运行;
  • 系统是如何做到的呢?很容易想到,其实就是判断,也就是说发生这种兼容行为的变化时,一般都会在原来的地方保存新旧两种逻辑,并通过 if-else 方法来判断执行哪种逻辑。判断条件就是应用设置的targetSdkVersion版本,比如系统源码AlarmManger.java中就有如下逻辑:
private final boolean mAlwaysExact;  
AlarmManager(IAlarmManager service, Context ctx) {  
    mService = service;
    final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion;
    mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KITKAT);
}
  • 所以说修改了 APK 的 targetSdkVersion 行为会发生变化,或者说修改 targetSdkVersion 需要做完整的测试。

support包作用

  • Android Support Library 可以分为两类:兼容库和组件库
  • 兼容库
    • 为老版本系统提供新API。说白了就是,让老版本的系统也能用上新版本才有的功能;
    • 如果就这么一味的维护这个support库,也是不太可能的,毕竟新系统不断更新,这个库就会不断增大,因此support也有很多版本,比如v7.xx 或 v4.xx,其中v7依赖了v4,另外也需要开发者根据自己当前工程的编译版本来考虑使用什么版本的support库,这也是上面提到的“support版本要和compileSdkVersion版本对应”的原因。
  • 组件库
    • Android在推出新版本的时候往往也会推出一些新的组件,这些组件自身并没有调用底层的framework的API,因此在旧版本系统上进行兼容就显得很方便,进行开发时只需引入相应的组件依赖即可。Android Support Library提供了诸如v7-recyclerview,v7-cardview,v7-gridlayout等更小更灵活的组件库。

Android 高版本API方法在低版本系统上的兼容性处理

  • Android 版本更替,新的版本新的方法带来许多便利,但无法在低版本系统上运行,如果兼容性处理不恰当,APP在低版本系统上,运行时将会crash。
  • 举例:根据给出路径,获取此路径所在分区的总空间大小。
    • 获取文件系统用量情况,在API level 9及其以上的系统,可直接调用File对象的相关方法getTotalSpace() 即可, 但是在API level 8 以下系统File对象并不存在此方法。
    • 如果minSdkVersion设置为8,那么build时候会报以下错误:
      Call requires API level 9 (current min is 8)
    • 为了编译可以通过,可以添加 @SuppressLint("NewApi") 或者 @TargeApi(9)。
      用@TargeApi($API_LEVEL)显式表明方法的API level要求,而不是@SuppressLint("NewApi");
    • 但是这样只是能编译通过,到了API level8的系统运行,将会引发 java.lang.NoSuchMethodError。
    • 正确的做法
      • 判断运行时版本,在低版本系统不调用此方法,同时为了保证功能的完整性,需要提供低版本功能实现
/**
 * Returns the total size in bytes of the partition containing this path.
 * Returns 0 if this path does not exist.
 * 
 * @param path
 * @return -1 means path is null, 0 means path is not exist.
 */
@TargetApi(Build.VERSION_CODES.GINGERBREAD) 
    // using @TargeApi instead of @SuppressLint("NewApi")
@SuppressWarnings("deprecation")
public static long getTotalSpace(File path) {
    if (path == null) {
        return -1;
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
        return path.getTotalSpace();
    }
    // implements getTotalSpace() in API lower than GINGERBREAD
    else {
        if (!path.exists()) {
            return 0;
        } else {
            final StatFs stats = new StatFs(path.getPath());
            // Using deprecated method in low API level system, 
            // add @SuppressWarnings("description") to suppress the warning
            return (long) stats.getBlockSize() * (long) stats.getBlockCount();
        }
    }
}
  • 总结:在使用高于minSdkVersion API level的方法需要:
    • 编译期:用@TargeApi($API_LEVEL) 使可以编译通过(不建议使用@SuppressLint("NewApi"));
    • 运行期:判断API level; 仅在足够高、有此方法的API level系统中,调用此方法;保证功能完整性,保证低API版本通过其他方法提供功能实现。

图片(drawable)适配问题

基本原则:先查找和屏幕密度最匹配的目录,如果没有,则依次向高密度目录查找,如果查到最高也没有,则查找 drawable-nodpi 目录(该目录无论设备密度如何,系统都不会缩放此目录中的资源),如果还是没有,再依次像低密度目录查找;

常见问题
  • 放错目录会怎样?图片大小?内存大小?
  • 视觉给的两倍图 @2x 和三倍图 @3x 如何使用?
具体实例

我们以具体的实例分析该问题。实例:一个本该放在 hdpi(对应设备 dpi:240) 的图片,被放在了 xxhdpi(对应设备 dpi:480) 目录,图片还是在 dpi 为 240 的设备上显示时,大小是放大还是缩小?放大或缩小几倍?内存占用是增大还是减小?增大或者减小几倍?

  • 大小变化:放在了 xxhdpi 目录,该图片被认为是为高密度设备需要的,现在要显示在低密度设备上,图片会被缩小。至于缩小倍数,可以直接用 480/240=2 得到,当然也可以使用“缩放因子”得到:官方提供的六种通用密度对应着设备的逻辑密度(是以 mdpi 即 dpi 为 160 为基线的),逻辑密度(即缩放因子 density)计算公式为:density=dpi/160。因此 hdpi 的缩放因子是 1.5,xxhdpi 的缩放因子是 3,因此大小缩小了 2 倍

想要说明的是,设备真实的像素密度(dpi)并不一定就是 240 或者 480,官方提供的六种通用密度本身就是一个范围,ldpi 对应的是 0~120,mdpi 对应的是 120~160,hdpi对应的是 160~240 等等。那个一个设备的真实缩放大小可以这样计算: scale = 设备 dpi / 图片所在 drawable 的(最大) dpi。

  • 内存变化:

先说一个常见误区:我们在电脑上看到的 png(或jpg) 格式的图片,png(jpg) 只是这张图片的容器,它们是经过相对应的压缩算法将原图每个像素点信息转换用另一种数据格式表示,以此达到压缩目的,减少图片文件大小。而当这张图片被加载到内存时,会先解析图片文件本身的数据格式,然后还原为位图,也就是 Bitmap 对象,Bitmap 的大小取决于像素点的数据格式以及分辨率。所以二者是两码事。其实这里也可以直接明确一个结论:图片的不同格式:png 或者 jpg 对于图片所占用的内存大小其实并没有影响。

首先要了解图片内存占用计算公式:内存大小 = 分辨率 x 每个像素点大小,其中分辨率是图片加载到内存后像素点的总个数(宽度 x 高度),每个像素的大小取决于使用的数据格式,如 ARGB_8888 格式就会占用 4 个字节。这里特别说明了,分辨率是图片加载到内存后的像素点个数,并不是加载前的像素数,二者关系为:加载后宽度/高度 = 加载前宽度/高度 x (设备 dpi / 图片所在 drawable 的dpi)。因此答案也就一目了然了,新的宽高都减少了1/2,因此总内存大小减小了1/4

但是如果图片位于磁盘或者 asset 目录呢?其实就不会有任何影响了,无论什么设备,直接按照原图加载前像素大小计算即可。当然,其中奥秘可以参考 Bitmap、BitmapFactory 相关源码,会发现只有 decodeResource() 方法内部会根据 dpi 进行分辨率的转换,其他 decodeXXX() 就没有了。

参考
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266