Android 11 - 本机 C++ 库的 System.loadLibrary 需要 60 多秒,在 Android 10 及更低版本上运行速度非常快
Posted
技术标签:
【中文标题】Android 11 - 本机 C++ 库的 System.loadLibrary 需要 60 多秒,在 Android 10 及更低版本上运行速度非常快【英文标题】:Android 11 - System.loadLibrary for native C++ library takes 60+ seconds, works perfectly fast on Android 10 and below 【发布时间】:2021-02-11 00:26:51 【问题描述】:在我们基于游戏引擎 cocos2d-x 的 android 游戏应用程序中,大部分代码都是用 C++ 编写的,自 Android 11 以来我们遇到了一个非常奇怪和关键的问题:
当原生库在 onLoadNativeLibraries
中加载时,它现在突然需要 60 多秒。在 Android 11 之前,一切正常,加载时间为 0.2-3 秒。现在,当您开始游戏时,您会看到 60 多秒的灰屏。
我们已经发现JNI_OnLoad
会在 60 秒停顿结束后直接调用。
这是onLoadNativeLibraries
的代码
功能:
protected void onLoadNativeLibraries()
try
ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
String libName = bundle.getString("android.app.lib_name");
System.loadLibrary(libName); // line of 60 seconds stall
catch (Exception e)
e.printStackTrace();
我们已经尝试过时间分析,但没有任何成功。它只是如何在该功能上花费大量时间。通过调试暂停也不会导致任何进一步的线索。本机调试器不会在代码的 C++ 端显示任何内容。
有人知道为什么会发生这种情况,或者我们可以尝试解决什么问题吗?任何帮助将不胜感激:)
【问题讨论】:
您是否在清单中明确设置了android:extractNativeLibs
?您使用哪个 Android Gradle 插件版本构建?发布模式和调试模式的加载时间一样吗?
@Michael 不,我们不这样做 - 我们应该设置它吗?我们使用 gradle-5.6.4。在发布模式下也很长,是的。
我的意思是 Android Gradle 插件。您在项目级 build.gradle
文件中指定为依赖项的那个。它应该有一个以 3 或 4 开头的版本。
另外,这种情况总是发生吗?还是仅在安装新版本应用后第一次使用?
@Michael 抱歉,确实有道理。我们有classpath 'com.android.tools.build:gradle:3.6.1'
是的,它总是会发生。 100% 的时间。
【参考方案1】:
简答:
这是 google 修复但尚未部署的 Android 11 中的错误。
同时,如果您不关心在程序退出/库卸载时调用库中的 static 和 thread_local 变量析构函数,请传递标志 -fno-c++-static-destructors
给编译器。 (有关使用 clang 注释的更细粒度的解决方案,请参阅长答案)
我在我的项目(不是 cocos2d)上使用了这个标志,没有任何问题,并且 lib 的加载速度比以前更快。
长答案:
很遗憾,这是 Google 团队在 android 11 (R) 中引入的性能回归。谷歌here正在跟踪该问题。
总而言之,当System.loadLibrary()
被调用时,系统使用__cxa_atexit()为加载的库中包含的每个C++全局变量注册一个析构函数
从Android 11(R)开始,该函数在android中的实现有changed:
在 Q 中,__cxa_atexit 使用块的链表,并调用 mprotect 在要修改的单个块上执行两次。 在 R 中,__cxa_atexit 调用 mprotect 在单个连续的处理程序数组上两次。每个数组 条目是 2 个指针。
当它们是许多 C++ 全局变量时,这种变化会大大降低性能,这似乎是 cocos2d so 库中的情况。
Google 已经修复了https://android-review.googlesource.com/c/platform/bionic/+/1464716,但是 如问题所述:
这最早要等到 3 月的 QPR 才会出现在 Android 11 中,并且 由于这不是安全问题,因此 OEM 无需强制执行 真的拿那个补丁。
Google 团队还建议在应用级别通过删除或跳过全局变量的析构函数来使用 workarounds:
对于特定的全局变量,[[clang::no_destroy]] 属性会跳过析构函数调用。 将 -fno-c++-static-destructors 传递给编译器以跳过所有静态变量的析构函数。该标志也跳过析构函数 对于 thread_local 变量。如果有 thread_local 变量 重要的析构函数,可以用 [[clang::always_destroy]] 覆盖编译器标志。 将 -Wexit-time-destructors 传递给编译器,使其对退出时间析构函数的每个实例发出警告,以突出显示 __cxa_atexit 注册来自。
【讨论】:
-fno-c++-static-destructors
似乎工作得很好。非常感谢。我们已经尝试用各种技巧调试了 4 天,但无济于事。谢谢你:)以上是关于Android 11 - 本机 C++ 库的 System.loadLibrary 需要 60 多秒,在 Android 10 及更低版本上运行速度非常快的主要内容,如果未能解决你的问题,请参考以下文章