欢迎你们前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~javascript
Android P 此次有不少行为变动,其中不乏一些须要亟需适配的变动。android
在 Android 8.0 时代各个手机厂商就开始发布本身的全面屏手机,可是此时 Android 官方并未支持到该功能,因此各个厂商都各自实现了一套全面屏判断逻辑,对于开发者来讲甚是麻烦。终于在 Android P 里官方收归了该功能的判断逻辑,Android P 和以后的版本彻底可使用官方 API 来判断全面屏,固然前提是第三方厂商按照 google 官方接口去实现。Android P 版本判断全面屏代码很简单,可是在适配过程当中你可能会在网上发现以下判断代码:web
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 的缩进进行相应处理,调用后会影响系统状态栏和导航栏对应用内容的展现,对此的介绍资料网上有不少,就不赘述了。真正完美判断全面屏的代码以下:apache
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; } } } }
Android P 版本最大最严格的特性变动应该非 SDK 接口限制莫属了。对于非 SDK API 里面的部分名单来讲,就算在不修改 targetSdkVersion 的前提下,无论是直接、反射仍是经过 JNI 调用都会形成调用失败、抛出 NoSuchFieldException
或 NoSuchMethodException
等严重后果,该行为影响范围波及全部调用此接口的应用。canvas
非 SDK API 名单总共分为三类:light grey list (浅灰名单)、dark grey list (深灰名单)、dark list(黑名单),详情:segmentfault
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),申请将其移动到浅灰名单中,可是目前正式版本已经发布,未得知该申请通道是否仍有效。api
详细了解了非 SDK API 以后,下一步固然是将应用代码里面的深灰名单和黑名单 API 调用找出来一一修改。目前官方提供了一个很是实用的扫描工具,该工具能够把应用里面三个类型名单的 API 调用都扫描出来(可是可能会有遗漏),使用方法也很简单:缓存
./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; ....通过上一步扫描出应用内非 SDK API 调用以后,接下来就能够直接开始适配。适配的原则是优先黑名单和深灰名单,浅灰名单在官方未有替代 API 以前能够暂时不适配,在 Android P 上运行也不会有任何问题。扫描完成以后,不出意外你们应该会有三类须要适配的 API 调用:
SystemProperties.get
,就须要去寻找另一个能够替代的合法 API,若是找不到就只能认为该 API 调用失败从而走失败逻辑,若是实在必需要用到该 API 就尽早去向 google 申请移动到浅灰名单中。该 API 调用查看 v7 support 包源码能够发现已经被 try-catch 住了,测试了相关类也能够正常运行,并且在适配过程当中升级 rc 版本的 support-v7 包会致使应用编译不过,因此目前 QQ 音乐暂时认定无需升级到最新版本的 support-v7。除上面介绍的特殊状况以外仍是建议更换最新版本的官方 SDK。
Android P 上对电源管理又作了一系列的改进措施,无论应用 targetApi 版本是否已经升级到 P,系统都会依据应用最近的使用时间和频率来给应用进行待机分组,而后根据应用所属群组限制应用能够访问的资源,目前总共有五类分组:
更加详细的表述能够参考官网: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()
函数来获取当前所属的应用群组,借助这个结果来更好的提高本身的打开频率,同时能够借助此来模拟处于不一样群组可否正常工做。另外,位于低电耗模式白名单中的应用不适用基于应用待机群组的限制。
Android 9 对省电模式又作了不少改进,开启省电模式以后会有以下限制:
这里须要重点介绍一下后台执行限制,该限制于 Android O 版本引入,主要是为了优化 Android 在多应用多服务运行时,系统负载过大会杀死后台音乐播放等服务致使用户体验降低的问题,它默认只对 targetApi 大于等于 26 的应用生效。目前用户能够经过设置页面对任意应用施加后台执行限制,后台执行限制会对应用有两方面的影响:
PendingIntent
)时,应用能够自由建立和运行前台与后台服务。 进入后台时,在一个持续数分钟的时间窗内,应用仍能够建立和使用服务,可是超过该时间以后再经过 startService 去启动一个服务就会抛出 java.lang.IllegalStateException: Not allowed to start service Intent
的错误,解决办法是使用 startForegroundService
或者 JobIntentService
;将 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 如下便可正常编译运行。
在 Android P 中,若是 targeSdkVersion 升级到 28,使用前台 Service 必需要申请 FOREGROUND_SERVICE
权限,若是没有申请该权限,系统会抛出 SecurityException
,该权限为普通权限,申请自动授予应用。
UNKNOWN
,因此会直接致使该功能出现异常。升级到 28 以后,应用编译后抛出 com.android.internal
包下面有些类找不到的异常,通过查找发现这些类已经从 SDK 中移除。针对这种状况目前有两种处理办法:
Android P 此次固然也有不少丰富的特性,总结了两个对于第三方应用开发者比较实用的特性
。
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 图片格式解码的手机,这些手机就确定有展现问题。解决这个问题目前有两种思路:
上面已经介绍到了 ImageDecoder
在解码 HEIF 图片中的应用,可是实际它的功能彻底不只于此,在 Android P 中它能够彻底替代 BitmapFactory
和 BitmapFactory.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
问答
Android - 如何修复权限异常?
相关阅读
Android音频系统
Android 基本常识
Android全局异常处理
【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识
此文已由做者受权腾讯云+社区发布,更多原文请点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
海量技术实践经验,尽在云加社区!