QQ 音乐 Android 吃 Pie 之路

栏目: IOS · Android · 发布时间: 7年前

内容简介:上篇:Android P 行为变更适配Android P 这次有很多行为变更,其中不乏一些需要亟需适配的变更。在 Android 8.0 时代各个手机厂商就开始发布自己的全面屏手机,但是此时 Android 官方并未支持到该功能,所以各个厂商都各自实现了一套全面屏判断逻辑,对于开发者来说甚是麻烦。终于在 Android P 里官方收归了该功能的判断逻辑,Android P 和之后的版本完全可以使用官方 API 来判断全面屏,当然前提是第三方厂商按照 google 官方接口去实现。Android P 版本判

上篇:Android P 行为变更适配

Android P 这次有很多行为变更,其中不乏一些需要亟需适配的变更。

一、全面屏检测

在 Android 8.0 时代各个手机厂商就开始发布自己的全面屏手机,但是此时 Android 官方并未支持到该功能,所以各个厂商都各自实现了一套全面屏判断逻辑,对于开发者来说甚是麻烦。终于在 Android P 里官方收归了该功能的判断逻辑,Android P 和之后的版本完全可以使用官方 API 来判断全面屏,当然前提是第三方厂商按照 google 官方接口去实现。Android P 版本判断全面屏代码很简单,但是在适配过程中你可能会在网上发现如下判断代码:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    decorView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
        @RequiresApi(api = 28)
        @Override
        public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
            if (windowInsets != null) {
                DisplayCutout cutout = windowInsets.getDisplayCutout();
                if (cutout != null) {
                    List<Rect> rects = cutout.getBoundingRects();
                    //通过判断是否存在rects来确定是否全面屏手机
                    if (rects != null && rects.size() > 0) {
                        isNotchScreen = true;
                    }
                }
            }
            return windowInsets;
        }
    });
}

这段代码确实可以判断出全面屏与否,但是会造成一个很严重的后果,就是在某些手机(pixel 和 vivo x21 均出现该情况)上底部导航栏会透明,导致应用内容会透到导航栏从而被遮挡,大大影响内容展示。最后经过仔细排查发现仅仅因为在上面那段代码中调用了 setOnApplyWindowInsetsListener 函数,该函数在 Android 官网有详细介绍,是用来在 Android 21 版本之后代替 fitSystemWindows 函数,目的是让 View 根据 Window 的缩进进行相应处理,调用后会影响系统状态栏和导航栏对应用内容的展示,对此的介绍资料网上有很多,就不赘述了。真正完美判断全面屏的代码如下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    WindowInsets windowInsets = decorView.getRootWindowInsets();
    if (windowInsets != null) {
        DisplayCutout displayCutout = windowInsets.getDisplayCutout();
        if (displayCutout != null) {
            List<Rect> rects = displayCutout.getBoundingRects();
            //通过判断是否存在rects来确定是否刘海屏手机
            if (rects != null && rects.size() > 0) {
                isNotchScreen = true;
            }
        }
    }
}

二、非 SDK API 适配详解

2.1 非 SDK API 名单介绍

Android P 版本最大最严格的特性变更应该非 SDK 接口限制莫属了。对于非 SDK API 里面的部分名单来说,就算在不修改 targetSdkVersion 的前提下,不管是直接、反射还是通过 JNI 调用都会造成调用失败、抛出 NoSuchFieldExceptionNoSuchMethodException 等严重后果,该行为影响范围波及所有调用此接口的应用。

非 SDK API 名单总共分为三类:light grey list (浅灰名单)、dark grey list (深灰名单)、dark list(黑名单),详情:

QQ 音乐 Android 吃 Pie 之路

2.2 非 SDK API 名单扫描

所以对于我们应用开发者来说,当前首要任务是适配深灰名单和黑名单。目前 google 官方提供了一个可以实时查询三个名单里面 API 列表的网站:https://android.googlesource.com/platform/frameworks/base/+/master/config/。在之前 DP 版本时开发者如果遇到了不得不使用的黑名单或者深灰名单 API,需要向 google 官方及时提出反馈(反馈url:https://issuetracker.google.com/issues/new?component=328403&template=1027267),申请将其移动到浅灰名单中,但是目前正式版本已经发布,未得知该申请通道是否仍有效。

详细了解了非 SDK API 之后,下一步当然是将应用代码里面的深灰名单和黑名单 API 调用找出来一一修改。目前官方提供了一个非常实用的扫描工具,该 工具 可以把应用里面三个类型名单的 API 调用都扫描出来(但是可能会有遗漏),使用方法也很简单:

  1. 打包一个应用 APK,建议使用 release 包,排除一些未使用到的单元测试类或者其他因素的影响,将 APK 放到工具指定目录下;

  2. 执行命令 ./appcompat.sh --dex-file=test.apk ,在终端上会输出三个名单每个 API 的详细调用处:

    #1: Linking dark greylist Landroid/os/SystemProperties;->get(Ljava/lang/String;)Ljava/lang/String; use(s):
            Ltmsdkobf/gv;->a(Ljava/lang/String;)Ljava/lang/String;
    
     #2: Linking dark greylist Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; use(s):
            Ltmsdkobf/gp;->b(Landroid/content/Context;)Ljava/lang/String;
    
      ....

2.3 非 SDK API 适配

经过上一步扫描出应用内非 SDK API 调用之后,接下来就可以直接开始适配。适配的原则是优先黑名单和深灰名单,浅灰名单在官方未有替代 API 之前可以暂时不适配,在 Android P 上运行也不会有任何问题。扫描完成之后,不出意外大家应该会有三类需要适配的 API 调用:

  1. 应用代码本身调用到了非 SDK API 接口;

    针对应用代码本身调用到了非 SDK API 接口,用的比较频繁的例如 SystemProperties.get ,就需要去寻找另外一个可以替代的合法 API,如果找不到就只能认为该 API 调用失败从而走失败逻辑,如果实在必须要用到该 API 就尽早去向 google 申请移动到浅灰名单中。

  2. 第三方库调用到了非 SDK API 接口;

    针对第三方库调用到了非 SDK API 接口,解决办法当然是直接查询相关资料或者联系库提供方,确认是否有适配 Android P 新版本的 SDK。还有需要提到的一点,就算更换适配完成的第三方 SDK 后,仍然可能会在同一地方扫描出非 SDK API 的调用,这是因为适配工程师只是在调用处加了一个 try-catch 保护逻辑,虽然这样也勉强叫做适配完成,但是还是强烈建议大家使用如下的适配方式:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
     // Android P or above
     } else {
     // below Android P
     }

    严格按照上面的适配方案,扫描工具就不会再扫描出此处的非 SDK API 调用,我们也无需每次都去确认所有非 SDK API 调用处都加了保护逻辑。

    当然如果第三方库没有适配也没有近期适配的意向,目前有两种方法:第一种是屏蔽入口;第二种是反编译 SDK,在关键地方加上适配代码;

  3. Android 官方库调用到了非 SDK API 接口;

    没错!Android 官方库也会被扫描出非 SDK API 调用,针对这种情况,需要分情况讨论:

    该 API 调用查看 v7 support 包源码可以发现已经被 try-catch 住了,测试了相关类也可以正常运行,而且在适配过程中升级 rc 版本的 support-v7 包会导致应用编译不过,所以目前 QQ 音乐暂时认定无需升级到最新版本的 support-v7。除上面介绍的特殊情况之外还是建议更换最新版本的官方 SDK。

三、电源管理改进

3.1 应用待机群组

Android P 上对电源管理又做了一系列的改进措施,不管应用 targetApi 版本是否已经升级到 P,系统都会依据应用最近的使用时间和频率来给应用进行待机分组,然后根据应用所属群组限制应用可以访问的资源,目前总共有五类分组:

  1. 活跃:

    一般为正在使用或者在前台运行的应用,例如:

  • 应用启动一个 Activity;

  • 应用正在运行前台 Service;

  • 应用的同步适配器关联上了一个前台应用;

  • 用户点击了应用的一个通知;

    系统不会对该类应用有任何的限制;

  • 工作集:

    应用经常运行,但是当前未属于活跃状态就会被归属于工作集,该群组的应用在运行作业和触发闹钟方面会被施加轻度的限制;

  • 常用:

    应用如果被定期使用,但不是每天的话就会被归到该工作群组。该群组的应用在运行作业和触发闹钟方面会被施加较强的限制,FCM 消息数量也会有相关限制;

  • 极少使用:

    应用如果不经常使用就会被归到该工作群组,系统会对该群组应用运行作业、触发闹钟和接收高优先级别 FCM 的消息能力方面有严格的限制;

  • 从未使用:

    安装但从未被使用过的应用会被归到该工作群组,该工作群组的应用会被施加极其严格的限制;

  • 更加详细的表述可以参考官网:App Standby Buckets(https://developer.android.com/about/versions/pie/power),不同群组的限制的详细表现见:Power management restrictions(链接:https://developer.android.com/topic/performance/power/power-details)。系统会动态的将手机里面的应用分配到这五类群组里面,也会根据需要变化应用群组,同时借助了机器学习来将一个应用放到更合适的群组里。目前应用可以通过 UsageStatsManager.getAppStandbyBucket() 函数来获取当前所属的应用群组,借助这个结果来更好的提升自己的打开频率,同时可以借助此来模拟处于不同群组能否正常工作。另外,位于低电耗模式白名单中的应用不适用基于应用待机群组的限制。

    3.2 省电模式改进

    Android 9 对省电模式又做了很多改进,开启省电模式之后会有如下限制:

    1. 系统会更加积极的将应用置于待机模式,不管应用是否空闲;

    2. 后台执行限制将适用于所有应用,无论他们的 targetApi 是多少;

    3. 屏幕关闭时,位置服务可能被停用;

    4. 后台应用没有网络访问权限;

    这里需要重点介绍一下后台执行限制,该限制于 Android O 版本引入,主要是为了优化 Android 在多应用多服务运行时,系统负载过大会杀死后台音乐播放等服务导致用户体验下降的问题,它默认只对 targetApi 大于等于 26 的应用生效。目前用户可以通过设置页面对任意应用施加后台执行限制,后台执行限制会对应用有两方面的影响:

    1. 后台服务限制:

      处于前台(可见、具有前台服务或者关联到前台应用)或临时白名单(处理高优先级 FCM、接收短信等广播或者执行通知的 PendingIntent )时,应用可以自由创建和运行前台与后台服务。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用服务,但是超过该时间之后再通过 startService 去启动一个服务就会抛出 java.lang.IllegalStateException: Not allowed to start service Intent 的错误,解决办法是使用 startForegroundService 或者 JobIntentService

    2. 广播限制:

      针对 Android O 和之上的应用无法继续在其清单中为隐式广播注册广播接收器。

    四、Apache HTTP client 相关类找不到

    将 compileSdkVersion 升级到 28 之后,如果在项目中用到了 Apache HTTP client 的相关类,就会抛出找不到这些类的错误。这是因为官方已经在 Android P 的启动类加载器中将其移除,如果仍然需要使用 Apache HTTP client,可以在 Manifest 文件中加入:

    <uses-library android:name="org.apache.http.legacy" android:required="false"/>

    或者也可以直接将 Apache HTTP client 的相关类打包进 APK 中。

    除上面两种适配方式外,QQ 音乐目前采用了另外一种方式。在音乐项目中,我们已经将使用 Apache HTTP client 的模块单独抽离到了一个 module 中,所以暂时只需要保持 module 中的 compileSdkVersion 在 28 以下即可正常编译运行。

    五、其余适配

    4.1 前台 Service

    在 Android P 中,如果 targeSdkVersion 升级到 28,使用前台 Service 必须要申请 FOREGROUND_SERVICE 权限,如果没有申请该权限,系统会抛出 SecurityException ,该权限为普通权限,申请自动授予应用。

    4.2 隐私安全保护

    1. Build.SERIAL 标识修改:在 Android P 中,对隐私保护又做了更加严格的要求。在某些应用中为了识别手机的唯一性可能会用到 Build.SERIAL 这个标识,但这个标识在 Android P 中已经被设置成了 UNKNOWN ,所以会直接导致该功能出现异常。

    2. 多进程 webview 信息访问限制:在 Android P 中为了提升系统的安全性,用户无法在多进程的 webview 中共享数据目录,该目录下存储的是一些 cookies、Http 缓存和其他一些永久、临时的缓存。当下不少应用会把 webview 放在另一个进程中打开以避免内存泄漏,但是他们 cookies 的设置往往还是在主进程中,所以开发者需要仔细排查自己的应用是否有这么使用,webview 相关运行是否正常等。

    4.3 com.android.internal 包下某些类找不到

    升级到 28 之后,应用编译后抛出 com.android.internal 包下面有些类找不到的异常,经过查找发现这些类已经从 SDK 中移除。针对这种情况目前有两种处理办法:

    1. 移除该类的调用逻辑;

    2. 在应用中新建一个同名类,将被移除类的所有代码逻辑复制到新建类中(必要时可能需要将被移除类相关类同时拷贝一份到应用中),然后将应用中所有相关 import 引用直接修改成新建类的包名引用即可;

    下篇:Android P 实用新特性

    Android P 这次当然也有很多丰富的特性,总结了两个对于第三方应用开发者比较实用的特性。

    一、HEIF 图片格式支持

    HEIF(High Efficiency Image Format),高帧率图片格式,采用的是 HEVC 编码格式。苹果于 iOS11 版本开始支持该图片格式,而 Android 则是在  Android O MR1 版本开始支持 HEIF 静态图的软解码,在 P 版本上完全支持该格式的软编解码。HEIF 格式的压缩率是 JPEG 的 2.39 倍,同等大小质量的图片可节省 50% 的空间和网络传输流量,而且支持动图。HEIF 格式比起 GIF 格式来说有着更好的图片展示效果,所以 HEIF 格式图片的目标是用来代替 JPEG 成为主流的图片压缩格式。HEIF 格式图片的扩展名为 .heif 或者 .heic:

    HEIF WebP JPEG
    最大尺寸 无上限 16383x16383 65535x65535
    编码 HEVC VP8 JPEG
    是否支持其他编码 YES NO NO
    支持音频/文字 YES NO NO
    支持多图片 YES YES NO
    支持裁剪 YES NO NO
    支持透明 YES YES NO
    支持缩略图 YES NO YES
    分块加载 YES NO NO

    看上去很美好,但是目前还不是所有的 Android P 机型都会支持 HEIF 格式硬编解码,因为这需要特殊的硬件支持同时还需要缴纳一定的专利费,所以在编解码效率上就会有机型差异,同时 Android P 软编解码也只能支持静态 HEIF 格式图片。目前开发者可以通过版本来判断是否支持 HEIF 编解码,判断逻辑如下:

    fun supportHEIF() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P

    解码代码也很简单,支持将 HEIF 格式图片解码成 Bitmap 和 Drawable:

    @TargetApi(28)
    fun decodeHEIFDrawable(filePath: String): Drawable? {
        if (!supportHEIF()) {
            return null
        }
        var source: ImageDecoder.Source = ImageDecoder.createSource(File(filePath))
        return ImageDecoder.decodeDrawable(source)
    }
    
    @RequiresApi(28)
    fun decodeHEIFBitmap(filePath: String): Bitmap? {
        if (!supportHEIF()) {
            return null
        }
        var source: ImageDecoder.Source = ImageDecoder.createSource(File(filePath))
        return ImageDecoder.decodeBitmap(source)
    }

    另外扫描本地图片则继续使用 ContentResolver 即可,如果设备支持 HEIF 格式,系统会自动扫描上 HEIF 格式的图片:

    var cursor : Cursor = getContext().getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null)

    但是这样还远远没有适配完成,第三方应用适配 HEIF 格式图片有一个很困难的地方是本地虽然可以识别解码 HEIF 格式的图片,但是如果某个用户将其设置为头像上传到后台,后台将其下发给其他不支持 HEIF 图片格式解码的手机,这些手机就肯定有展示问题。解决这个问题目前有两种思路:

    1. 终端在上传之前将其转码成 JPEG 格式的图片,但是这样就根本没有充分利用到 HEIF 图片的高压缩率的优势;

    2. 在到达后端之后,后端将其转码成 JPEG 图片,同时保存一份 HEIF 和 JEPG,到时候根据用户是否可以解码 HEIF 下发不同格式图片。该方案可以充分利用 HEIF 的优点,但是大大增加了后端存储空间和开发工作量。

    二、ImageDecoder

    上面已经介绍到了 ImageDecoder 在解码 HEIF 图片中的应用,但是实际它的功能完全不仅于此,在 Android P 中它可以完全替代 BitmapFactoryBitmapFactory.Options 相关类。 ImageDocoder 类可以通过字节数据、文件和 URI 来解码一张图片。用法和之前一样,首先通过 createSource 方法创建一个图片文件的 ImageDecoder.Source 对象,然后调用 decodeDrawable 或者 decodeBitmap 方法传入之前的 ImageDecoder.Source 对象就能生成图片的 Drawable 或者 Bitmap 对象引用。 ImageDecoder 支持 PNG、JPEG、WEBP、GIF 和 HEIF 多种格式图片的解码,另外解码 GIF 或者 WEBP 格式图片得到的是一个 AnimatedImageDrawable 对象, AnimatedImageDrawable 类的工作原理和 AnimatedVectorDrawable 类似,都是使用一个工作线程来解码,所以解码线程和显示线程互不干扰。 AnimatedImageDrawable 用法也很简单:

    var drawable: Drawable = ImageDecoder.decodeDrawable(source);
    if (drawable is AnimatedImageDrawable){
        image.setImageDrawable(drawable)
        drawable.start()
    }

    ImageDecoder 除了基础的解码功能之外,还有很多非常实用的方法,比如通过设置 OnHeaderDecodedListener 就可以在解析图片之前获取到图片的宽高等信息,同时还可以根据需要设置采样率:

    val listener = object : OnHeaderDecodedListener {
        fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source: Source) {
            decoder.setTargetSampleSize(2)
        }
    }
    val drawable = ImageDecoder.decodeDrawable(source, listener)

    另外还可以通过 setPostProcessor 方法来添加一些自定义的效果,比如最常用的切圆角:

    var drawable = ImageDecoder.decodeDrawable(source) { decoder, info, src ->
        decoder.setPostProcessor { canvas ->
            val path = Path()
            path.setFillType(Path.FillType.INVERSE_EVEN_ODD)
            val width = canvas.getWidth()
            val height = canvas.getHeight()
            path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW)
            val paint = Paint()
            paint.setAntiAlias(true)
            paint.setColor(Color.TRANSPARENT)
            paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC))
            canvas.drawPath(path, paint)
            PixelFormat.TRANSLUCENT
        }
    }

    非常便捷。用法远不仅于此,有了 Canvas 对象,开发者完全可以发挥想象去实现自己想要的炫酷效果。另外如果解码的图片不完整或者包含错误,一般情况下会抛出 DecodeException ,但是如果这个时候通过 setOnPartialImageListener 函数传递一个 OnPartialImageListener 对象,并且在 onPartialImage 函数中返回 true,则图片就会只展示解析成功的一部分而不会抛出 DecodeException

    var drawable = ImageDecoder.decodeDrawable(source) { decoder, info, src ->
        decoder.setOnPartialImageListener { e: ImageDecoder.DecodeException ->
            true
        }
    }

    引用

    https://developer.android.google.cn/about/versions/pie/android-9.0

    https://mp.weixin.qq.com/s/03ospQEdY5HLdYqxEiDX1g

    https://blog.csdn.net/GenlanFeng/article/details/79496359

    https://developer.android.com/about/versions/pie/power

    https://segmentfault.com/a/1190000015947004


    以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

    查看所有标签

    猜你喜欢:

    本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

    Iterative Methods for Sparse Linear Systems, Second Edition

    Iterative Methods for Sparse Linear Systems, Second Edition

    Yousef Saad / Society for Industrial and Applied Mathematics / 2003-04-30 / USD 102.00

    Tremendous progress has been made in the scientific and engineering disciplines regarding the use of iterative methods for linear systems. The size and complexity of linear and nonlinear systems arisi......一起来看看 《Iterative Methods for Sparse Linear Systems, Second Edition》 这本书的介绍吧!

    CSS 压缩/解压工具
    CSS 压缩/解压工具

    在线压缩/解压 CSS 代码

    JSON 在线解析
    JSON 在线解析

    在线 JSON 格式化工具

    Base64 编码/解码
    Base64 编码/解码

    Base64 编码/解码