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 中的错误。

同时,如果您不关心在程序退出/库卸载时调用库中的 staticthread_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 及更低版本上运行速度非常快的主要内容,如果未能解决你的问题,请参考以下文章

C++学习(二八七)获取Android手机各种路径

使用 C++ 在 android 中理解和实现本机 Binder

使用本机库的 Android 社​​交网络 sdk

添加对本机 Visual C++ 项目的引用

Android 11 JNI 禁用 fdsan

从 Android 中的本机 c++ 文件调用本机 opencv