前言 前阵子适配 Android 9.0 时,遇到一些坑,感觉适配准备得不够充分就上线,会遇到非常多的问题,而现在再过几周,Android Q 正式版就要发布了,今天 Google 宣布发布最后一个 Beta 测试版 Android Q Beta 6 了,所以适配 Android Q 的工作也要尽早完成,Android Q Beta 6 发布文章
适配 Android 9.0 的坑
实际操作 总之,先搞到 Android Q 的手机或者模拟器先,可以查看获取 Android Q 测试版
成功打开 Android Q 版本的模拟器之后,先打开设置看一看,可以看到当前的系统版本号
支持与版本说明
代号、标记和细分版本号
关于代号官方文档
然后直接在 Android Q 上运行应用,查看应用的运行情况,是否存在打不开,卡顿,闪退等比较明显的现象
上面的操作没问题了,于是直接把 targetSdkVersion 改为 29
如何选择 compileSdkVersion, minSdkVersion 和 targetSdkVersion
编译运行,发现出现了非常多的报错,都是类型错误,原本没有说明是可为空的对象,现在变成了可为空的,比如 Parcel 的 readString() 方法,Class 的 cast() 方法等
使用 Kotlin 开发 Android 应用
Parcel readString 可为空
所以要对这些可为空的对象进行处理,让代码可以正常编译运行,最后可以正常运行在 Android Q 的模拟器上
扫描非 SDK 接口 所谓非 SDK 接口,在 iOS 开发中类似于“私有 API”,通俗的说,两者都是官方提供的接口,但前者明文包含在 SDK 中,允许开发者调用;后者出于各方面原因(比如还在测试中或是这个接口仅供系统内部调用),不鼓励甚至禁止开发者调用
当你调用了非 SDK 接口时,会有类似 Accessing hidden XXX 的日志,还指示了访问方式:直接,通过反射或通过 JNI
1 2 Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI) Accessing hidden method Landroid/app/ActivityThread;->currentActivityThread()Landroid/app/ActivityThread; (dark greylist, reflection)
light greylisted :包含方法和字段,它们继续在 Android P 预览版中运行,但 Google 无法保证在未来版本的平台上访问这些方法和字段
dark greylist:开发人员预览版本中无法访问的方法,这些日志消息可以使用 adb logcat 访问,并且会显示在正在运行的应用程序的 PID 下
但是一个大项目到底哪里使用了这些方法,靠自己 review 代码和看日志是不现实的,谷歌官方提供了官方检查器 veridex 来检测一个 apk 中哪里使用了非 SDK 接口,veridex下载
下载完毕后解压,解压后目录结构如下:
可以看到它里面包含了各名单的 txt 文件,其中 appcompat.sh 就是运行脚本
把需要扫描的 apk 放到同一目录下,使用终端 cd 到该目录,运行以下命令:(test.apk 为目标 apk,imprecise:不精确)
1 2 3 4 5 ./appcompat.sh --dex-file=test.apk ./appcompat.sh --dex-file=test.apk >> happy.txt ./appcompat.sh --dex-file=test.apk --imprecise >> happy2.txt
对项目中的条目分类了一下,得到如下列表,观察详细内容,发现 veridex 把 Android 自己调用的也检测出来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 Rxjava: #2: Linking greylist Lsun/misc/Unsafe;->arrayBaseOffset(Ljava/lang/Class;)I use(s): (io.reactivex:rxjava:1.2.1@jar) Lcom/google/common/hash/LittleEndianByteArray$UnsafeByteArray;-><clinit>()V Lcom/google/common/primitives/UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator;-><clinit>()V Lrx/internal/util/unsafe/ConcurrentCircularArrayQueue;-><clinit>()V Lrx/internal/util/unsafe/ConcurrentSequencedCircularArrayQueue;-><clinit>()V Lrx/internal/util/unsafe/SpscUnboundedArrayQueue;-><clinit>()V #3: Linking greylist Lsun/misc/Unsafe;->arrayIndexScale(Ljava/lang/Class;)I use(s): (io.reactivex:rxjava:1.2.1@jar) Lcom/google/common/hash/LittleEndianByteArray$UnsafeByteArray;-><clinit>()V Lcom/google/common/primitives/UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator;-><clinit>()V Lrx/internal/util/unsafe/ConcurrentCircularArrayQueue;-><clinit>()V Lrx/internal/util/unsafe/ConcurrentSequencedCircularArrayQueue;-><clinit>()V Lrx/internal/util/unsafe/SpscUnboundedArrayQueue;-><clinit>()V #5: Linking greylist Lsun/misc/Unsafe;->compareAndSwapLong(Ljava/lang/Object;JJJ)Z use(s): Lcom/google/common/cache/Striped64$Cell;->a(JJ)Z Lcom/google/common/cache/Striped64;->b(JJ)Z Lrx/internal/util/unsafe/MpmcArrayQueueConsumerField;->b(JJ)Z Lrx/internal/util/unsafe/MpmcArrayQueueProducerField;->c(JJ)Z Lrx/internal/util/unsafe/SpmcArrayQueueConsumerField;->b(JJ)Z #6: Linking greylist Lsun/misc/Unsafe;->compareAndSwapObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z use(s): Lcom/google/common/util/concurrent/AbstractFuture$UnsafeAtomicHelper;->a(Lcom/google/common/util/concurrent/AbstractFuture;Lcom/google/common/util/concurrent/AbstractFuture$Listener;Lcom/google/common/util/concurrent/AbstractFuture$Listener;)Z Lcom/google/common/util/concurrent/AbstractFuture$UnsafeAtomicHelper;->a(Lcom/google/common/util/concurrent/AbstractFuture;Lcom/google/common/util/concurrent/AbstractFuture$Waiter;Lcom/google/common/util/concurrent/AbstractFuture$Waiter;)Z Lcom/google/common/util/concurrent/AbstractFuture$UnsafeAtomicHelper;->a(Lcom/google/common/util/concurrent/AbstractFuture;Ljava/lang/Object;Ljava/lang/Object;)Z Lrx/internal/util/unsafe/MpscLinkedQueue;->c(Lrx/internal/util/atomic/LinkedQueueNode;)Lrx/internal/util/atomic/LinkedQueueNode; #8: Linking greylist Lsun/misc/Unsafe;->getLongVolatile(Ljava/lang/Object;J)J use(s): Lrx/internal/util/unsafe/ConcurrentSequencedCircularArrayQueue;->a([JJ)J Lrx/internal/util/unsafe/SpscArrayQueue;->a()J Lrx/internal/util/unsafe/SpscArrayQueue;->b()J Lrx/internal/util/unsafe/SpscUnboundedArrayQueue;->a()J Lrx/internal/util/unsafe/SpscUnboundedArrayQueue;->b()J #9: Linking greylist Lsun/misc/Unsafe;->getObject(Ljava/lang/Object;J)Ljava/lang/Object; use(s): Lrx/internal/util/unsafe/ConcurrentCircularArrayQueue;->a([Ljava/lang/Object;J)Ljava/lang/Object; #10: Linking greylist Lsun/misc/Unsafe;->getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object; use(s): Lrx/internal/util/unsafe/BaseLinkedQueueConsumerNodeRef;->a()Lrx/internal/util/atomic/LinkedQueueNode; Lrx/internal/util/unsafe/BaseLinkedQueueProducerNodeRef;->c()Lrx/internal/util/atomic/LinkedQueueNode; Lrx/internal/util/unsafe/ConcurrentCircularArrayQueue;->b([Ljava/lang/Object;J)Ljava/lang/Object; Lrx/internal/util/unsafe/SpscUnboundedArrayQueue;->a([Ljava/lang/Object;J)Ljava/lang/Object; #12: Linking greylist Lsun/misc/Unsafe;->objectFieldOffset(Ljava/lang/reflect/Field;)J use(s): Lcom/google/common/cache/Striped64$Cell;-><clinit>()V Lcom/google/common/cache/Striped64;-><clinit>()V (2 occurrences) Lcom/google/common/hash/Striped64$Cell;-><clinit>()V Lcom/google/common/hash/Striped64;-><clinit>()V (2 occurrences) Lcom/google/common/util/concurrent/AbstractFuture$UnsafeAtomicHelper;-><clinit>()V (5 occurrences) Lrx/internal/util/unsafe/SpscUnboundedArrayQueue;-><clinit>()V (2 occurrences) Lrx/internal/util/unsafe/UnsafeAccess;->a(Ljava/lang/Class;Ljava/lang/String;)J #13: Linking greylist Lsun/misc/Unsafe;->putObject(Ljava/lang/Object;JLjava/lang/Object;)V use(s): Lcom/google/common/util/concurrent/AbstractFuture$UnsafeAtomicHelper;->a(Lcom/google/common/util/concurrent/AbstractFuture$Waiter;Lcom/google/common/util/concurrent/AbstractFuture$Waiter;)V Lcom/google/common/util/concurrent/AbstractFuture$UnsafeAtomicHelper;->a(Lcom/google/common/util/concurrent/AbstractFuture$Waiter;Ljava/lang/Thread;)V Lrx/internal/util/unsafe/ConcurrentCircularArrayQueue;->a([Ljava/lang/Object;JLjava/lang/Object;)V #14: Linking greylist Lsun/misc/Unsafe;->putOrderedLong(Ljava/lang/Object;JJ)V use(s): Lrx/internal/util/unsafe/ConcurrentSequencedCircularArrayQueue;->a([JJJ)V Lrx/internal/util/unsafe/SpmcArrayQueueProducerField;->d(J)V Lrx/internal/util/unsafe/SpscArrayQueue;->d(J)V Lrx/internal/util/unsafe/SpscArrayQueue;->e(J)V Lrx/internal/util/unsafe/SpscUnboundedArrayQueue;->a(J)V Lrx/internal/util/unsafe/SpscUnboundedArrayQueue;->b(J)V #15: Linking greylist Lsun/misc/Unsafe;->putOrderedObject(Ljava/lang/Object;JLjava/lang/Object;)V use(s): Lrx/internal/util/unsafe/ConcurrentCircularArrayQueue;->b([Ljava/lang/Object;JLjava/lang/Object;)V Lrx/internal/util/unsafe/SpscUnboundedArrayQueue;->a([Ljava/lang/Object;JLjava/lang/Object;)V Google: #4: Linking greylist Lsun/misc/Unsafe;->compareAndSwapInt(Ljava/lang/Object;JII)Z use(s): Lcom/google/common/cache/Striped64;->c()Z #7: Linking greylist Lsun/misc/Unsafe;->getLong(Ljava/lang/Object;J)J use(s): Lcom/google/common/hash/LittleEndianByteArray$UnsafeByteArray$1;->a([BI)J Lcom/google/common/hash/LittleEndianByteArray$UnsafeByteArray$2;->a([BI)J Lcom/google/common/primitives/UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator;->a([B[B)I (2 occurrences) #11: Linking greylist Lsun/misc/Unsafe;->getUnsafe()Lsun/misc/Unsafe; use(s): Lcom/google/common/cache/Striped64;->a()Lsun/misc/Unsafe; Lcom/google/common/hash/LittleEndianByteArray$UnsafeByteArray;->c()Lsun/misc/Unsafe; Lcom/google/common/hash/Striped64;->a()Lsun/misc/Unsafe; Lcom/google/common/primitives/UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator;->a()Lsun/misc/Unsafe; Lcom/google/common/util/concurrent/AbstractFuture$UnsafeAtomicHelper;-><clinit>()V AndroidX #16: Reflection greylist-max-p Landroid/animation/LayoutTransition;->cancel use(s): Landroidx/transition/ViewGroupUtilsApi14;->a(Landroid/animation/LayoutTransition;)V #21: Reflection greylist Landroid/app/Dialog;->mOnKeyListener use(s): Landroidx/core/view/KeyEventDispatcher;->a(Landroid/app/Dialog;)Landroid/content/DialogInterface$OnKeyListener; #29: Reflection greylist Landroid/content/res/Resources;->mResourcesImpl use(s): Landroidx/appcompat/app/ResourcesFlusher;->d(Landroid/content/res/Resources;)V #30: Reflection greylist Landroid/graphics/Typeface;->createFromFamiliesWithDefault use(s): Landroidx/core/graphics/TypefaceCompatApi24Impl;-><clinit>()V Landroidx/core/graphics/TypefaceCompatApi26Impl;->f(Ljava/lang/Class;)Ljava/lang/reflect/Method; Landroidx/core/graphics/TypefaceCompatApi28Impl;->f(Ljava/lang/Class;)Ljava/lang/reflect/Method; #31: Reflection greylist Landroid/media/session/MediaSession;->getCallingPackage use(s): Landroid/support/v4/media/session/MediaSessionCompatApi24;->a(Ljava/lang/Object;)Ljava/lang/String; #33: Reflection greylist Landroid/os/Bundle;->getIBinder use(s): Landroidx/core/app/BundleCompat$BundleCompatBaseImpl;->a(Landroid/os/Bundle;Ljava/lang/String;)Landroid/os/IBinder; #34: Reflection greylist Landroid/os/Bundle;->putIBinder use(s): Landroidx/core/app/BundleCompat$BundleCompatBaseImpl;->a(Landroid/os/Bundle;Ljava/lang/String;Landroid/os/IBinder;)V #35: Reflection greylist Landroid/service/media/MediaBrowserService$Result;->mFlags use(s): Landroidx/media/MediaBrowserServiceCompatApi26;-><clinit>()V #45: Reflection greylist Landroid/view/LayoutInflater;->mFactory2 use(s): Landroidx/core/view/LayoutInflaterCompat;->b(Landroid/view/LayoutInflater;Landroid/view/LayoutInflater$Factory2;)V #46: Reflection greylist Landroid/view/View;->computeFitSystemWindows use(s): Landroidx/appcompat/widget/ViewUtils;-><clinit>()V #47: Reflection greylist Landroid/view/View;->mAccessibilityDelegate use(s): Landroidx/core/view/ViewCompat;->b(Landroid/view/View;)Z #49: Reflection greylist-max-p Landroid/view/View;->mViewFlags use(s): Landroidx/transition/ViewUtils;->a()V #50: Reflection greylist-max-p Landroid/view/animation/Animation;->mListener use(s): Landroidx/fragment/app/FragmentManagerImpl;->a(Landroid/view/animation/Animation;)Landroid/view/animation/Animation$AnimationListener; #51: Reflection greylist Landroid/widget/AbsListView;->mIsChildViewEnabled use(s): Landroidx/appcompat/widget/DropDownListView;-><init>(Landroid/content/Context;Z)V #52: Reflection greylist-max-p Landroid/widget/AutoCompleteTextView;->doAfterTextChanged use(s): Landroidx/appcompat/widget/SearchView$AutoCompleteTextViewReflector;-><init>()V #53: Reflection greylist-max-p Landroid/widget/AutoCompleteTextView;->doBeforeTextChanged use(s): Landroidx/appcompat/widget/SearchView$AutoCompleteTextViewReflector;-><init>()V #54: Reflection greylist-max-p Landroid/widget/AutoCompleteTextView;->ensureImeVisible use(s): Landroidx/appcompat/widget/SearchView$AutoCompleteTextViewReflector;-><init>()V #65: Reflection greylist Ljava/lang/Thread;->inheritableThreadLocals use(s): Lcom/google/common/base/internal/Finalizer;->b()Ljava/lang/reflect/Field; #66: Reflection greylist Llibcore/icu/ICU;->addLikelySubtags use(s): Landroidx/core/text/ICUCompat;-><clinit>()V #67: Reflection greylist Lsun/misc/Unsafe;->allocateInstance use(s): Lcom/google/gson/internal/UnsafeAllocator;->create()Lcom/google/gson/internal/UnsafeAllocator; #68: Reflection greylist Lsun/misc/Unsafe;->theUnsafe use(s): Lcom/google/gson/internal/UnsafeAllocator;->create()Lcom/google/gson/internal/UnsafeAllocator; Lrx/internal/util/unsafe/UnsafeAccess;-><clinit>()V 68 hidden API(s) used: 15 linked against, 53 through reflection 60 in greylist 0 in blacklist 0 in greylist-max-o 8 in greylist-max-p To run an analysis that can give more reflection accesses, but could include false positives, pass the --imprecise flag.
greylist: 灰名单,即当前版本仍能使用的非 SDK 接口,但在下一版本中可能变成被限制的非 SDK 接口
blacklist:黑名单,使用了就会报错。也是我们项目中必须解决的非 SDK 接口
greylist-max-o: 在 targetSDK<=O 中能使用,但是在 targetSDK>=P 中被限制的非 SDK 接口
greylist-max-p: 在 targetSDK<=P 中能使用,但是在 targetSDK>=Q 中被限制的非 SDK 接口
使用非官方 API 的后果 在 Developer Preview 的后续版本中,访问非 SDK 接口的各种方法都会产生错误或其他不良结果,下面列出了几种示例:
Android Q 暗黑模式适配
1 2 3 4 5 6 7 fun getDarkModeStatus (context: Context ) : Boolean { val mode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK return mode == Configuration.UI_MODE_NIGHT_YES }
在需要跟随夜间模式的 Activity 中添加 android:configChanges="uiMode"
选项,并重写 onConfigurationChanged() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <activity android:name=".TestActivity" android:configChanges="uiMode" />override fun onConfigurationChanged (newConfig: Configuration ) { super .onConfigurationChanged(newConfig) when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) { Configuration.UI_MODE_NIGHT_YES -> { } Configuration.UI_MODE_NIGHT_NO -> { } } }
关于 configChanges
Android Q 全面屏及手势适配
左图 18.5:9 设备上最大纵横比设置为 16:9 的应用
右图 18.5:9 设备上最大纵横比设置为 18.5:9 的应用
1 2 3 4 <meta-data android:name ="android.max_aspect" android:value ="2.22" />
建议将应用设计为支持 2.1 或更高的纵横比,如果未设置任何值,并且 android:resizeableActivity
不为 true,则最大纵横比默认为 1.86(大约相当于 16:9),应用将无法充分利用额外的屏幕空间
打开全面屏:
这张图片基本上指的是过去这么长时间,一般应用可以利用的在屏幕上的一个所谓的“安全范围”,在 Android Q 上,我们首先建议大家去考虑使用最下面的导航栏区域
在没有适配全面屏的应用中,会看到如下效果:
官方文档
手势操作再添新机制,Android Q Beta 5 更新详解 | 剧透
Android Q Labs | Android Q 手势导航