关于「幽灵调用」一事第三弹:完结?

事件回顾

第一弹:你的App是否有出现过幽灵调用?

第二弹:「幽灵调用」背后的真相:一个隐藏多年的Android原生Bug

前面从一个常见的 BUG 的分析定位到是由编译器 R8 优化导致的问题,调用了不该出现的函数,若程序一直稳定运行没有发生错误,其实问题也挺严重的,因为它会因为错误的函数调用,而导致对象自身的内存被污染,例如原本一某成员是个状态量,被污染成其它状态值,导致程序按非预期路径发展。

测试程序

根据第一弹的案例提到的B、C类,我们设计同样的类结构来模拟该优化现像。

类的设计

typescript 复制代码
public class TextureSubView extends TextureView {
    public TextureSubView(@NonNull Context context) {
        super(context);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }

    static void ini() {
        Log.i("reuse", "class TextureSubView ini");
    }
}
typescript 复制代码
public class ImageSubView extends AppCompatImageView {
    public ImageSubView(@NonNull Context context) {
        super(context);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }

    static void ini() {
        Log.i("reuse", "class ImageSubView ini");
    }
}

防止未初始化

kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            ReuseTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
        TextureSubView.ini();
        ImageSubView.ini();
    }
}

开启 R8 优化

java 复制代码
buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    debug {
        debuggable false
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
kotlin 复制代码
-keep class penguin.reuse.dex.TextureSubView { *; }
-keep class penguin.reuse.dex.ImageSubView { *; }

风险检测

使用 core-parser 检测功能,我们可把该复用 dex_pc_ptr 的函数检测出来。

项目详情见:基于 Core 文件的 Android 调试与分析套件

shell 复制代码
# ./data/core-parser -p `pidof penguin.reuse.dex`
arduino 复制代码
core-parser> space --full-check
ERROR: verify class reuse dex_pc_ptr method
[0x7180036410] public void penguin.reuse.dex.ImageSubView.onAttachedToWindow()
[0x4a05f940] public final void androidx.appcompat.widget.ContentFrameLayout.onAttachedToWindow()

ERROR: verify class reuse dex_pc_ptr method
[0x4a05f6d8] public final void androidx.appcompat.widget.ActionBarContextView.onDetachedFromWindow()
[0x71800363a0] public void penguin.reuse.dex.TextureSubView.onDetachedFromWindow()
[0x7180036430] public void penguin.reuse.dex.ImageSubView.onDetachedFromWindow()
[0x4a072f10] public final void k.D.onDetachedFromWindow()
[0x4a05f960] public final void androidx.appcompat.widget.ContentFrameLayout.onDetachedFromWindow()
java 复制代码
core-parser> method 0x71800363a0 --dex 
public void penguin.reuse.dex.TextureSubView.onDetachedFromWindow() [dex_method_idx=8200]
DEX CODE:
  0x7253b5267c: 106f 0e51 0000           | invoke-super {v0}, void android.view.View.onDetachedFromWindow() // method@3665
  0x7253b52682: 000e                     | return-void
java 复制代码
core-parser> method 0x7180036430 --dex
public void penguin.reuse.dex.ImageSubView.onDetachedFromWindow() [dex_method_idx=8194]
DEX CODE:
  0x7253b5267c: 106f 0e51 0000           | invoke-super {v0}, void android.view.View.onDetachedFromWindow() // method@3665
  0x7253b52682: 000e                     | return-void

差异化处理

为 TextureSubView、ImageSubView 添加新的成员变量 isAttach 分别处理到会被优化的函数上。

scala 复制代码
public class TextureSubView extends TextureView {
    boolean isAttach = false;
...
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        isAttach = true;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        isAttach = false;
    }
...
}
scala 复制代码
public class ImageSubView extends AppCompatImageView {
    boolean isAttach = false;
...
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        isAttach = true;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        isAttach = false;
    }
...
}
arduino 复制代码
core-parser> space --full-check
ERROR: verify class reuse dex_pc_ptr method
[0x4a05f840] public final void androidx.appcompat.widget.ContentFrameLayout.onDetachedFromWindow()
[0x4a05f5b8] public final void androidx.appcompat.widget.ActionBarContextView.onDetachedFromWindow()
[0x4a072f10] public final void k.D.onDetachedFromWindow()
scala 复制代码
core-parser> class androidx.appcompat.widget.ContentFrameLayout -i
[0x4a018740]
public class androidx.appcompat.widget.ContentFrameLayout extends android.widget.FrameLayout {
  // Implements:
    android.graphics.drawable.Drawable$Callback
    android.view.KeyEvent$Callback
    android.view.accessibility.AccessibilityEventSource
    android.view.MiRadiiChangedCallback
    android.view.ViewParent
    android.view.ViewManager
}

core-parser> class androidx.appcompat.widget.ActionBarContextView -i
[0x4a015ed8]
public class androidx.appcompat.widget.ActionBarContextView extends android.view.ViewGroup {
  // Implements:
    android.graphics.drawable.Drawable$Callback
    android.view.KeyEvent$Callback
    android.view.accessibility.AccessibilityEventSource
    android.view.MiRadiiChangedCallback
    android.view.ViewParent
    android.view.ViewManager
}

core-parser> class k.D -i
[0x4a002710]
public class k.D extends android.widget.TextView {
  // Implements:
    android.graphics.drawable.Drawable$Callback
    android.view.KeyEvent$Callback
    android.view.accessibility.AccessibilityEventSource
    android.view.MiRadiiChangedCallback
    android.view.ViewTreeObserver$OnPreDrawListener
}
继承 父级函数
ContentFrameLayout FrameLayout android.view.ViewGroup.onDetachedFromWindow
ActionBarContextView ViewGroup android.view.ViewGroup.onDetachedFromWindow
k.D TextView android.view.View.onDetachedFromWindow

依旧存在其它来自公共库代码被优化的风险。而做了差异化处理的函数添加新成员变量,而不同类型中 field Id 的不同二产生不同的字节码结果,因此不会被优化在一块。

php 复制代码
core-parser> method 0x7180036290 --dex
public void penguin.reuse.dex.ImageSubView.onDetachedFromWindow() [dex_method_idx=8186]
DEX CODE:
  0x7253c4a634: 106f 0e40 0001           | invoke-super {v1}, void android.view.View.onDetachedFromWindow() // method@3648
  0x7253c4a63a: 0012                     | const/4 v0, #+0
  0x7253c4a63c: 105c 0e7a                | iput-boolean v0, v1, Lpenguin/reuse/dex/ImageSubView;.isAttach:Z // field@3706
  0x7253c4a640: 000e                     | return-void
php 复制代码
core-parser> method 0x71800361e0 --dex
public void penguin.reuse.dex.TextureSubView.onDetachedFromWindow() [dex_method_idx=8192]
DEX CODE:
  0x7253c4a82c: 106f 0e40 0001           | invoke-super {v1}, void android.view.View.onDetachedFromWindow() // method@3648
  0x7253c4a832: 0012                     | const/4 v0, #+0
  0x7253c4a834: 105c 0e7b                | iput-boolean v0, v1, Lpenguin/reuse/dex/TextureSubView;.isAttach:Z // field@3707
  0x7253c4a838: 000e                     | return-void

无法做到完全避免复用的问题

R8 优化

第二弹当中,已经提到了具体的修复提交,但实际上我们暂时是无法使用的。

2025年9月18日,R8修复:Disable code deduplication for code objects containing invoke-super

2023年05月08日,R8引入:Dedup code objects on api 31 and above

AGP 版本

Android Gradle 插件 8.​13 版本说明

API 级别 最低 Android Studio 版本 最低 AGP 版本
36.0 Meerkat 2024.3.1 Patch 1 8.9.1
35 Koala 功能更新 2024.2.1 8.6.0
34 Hedgehog 2023.1.1 8.1.1
33 Flamingo 2022.2.1 7.2

查阅相关的版本详情,而当前我测试用的版本是 agp-8.5.1,查询分支历史应该在 agp-8.2.0 开始会有此类优化现象,也就是大家去年(2024)更新 Android Studio 软件开始导入,正式修复释放的版本应该会在 agp-9.x.x 版本。

避免优化?

kotlin 复制代码
-dontshrink
-keep class penguin.reuse.dex.TextureSubView { *; }
-keep class penguin.reuse.dex.ImageSubView { *; }
arduino 复制代码
core-parser> space --full-check
ERROR: verify class reuse dex_pc_ptr method
[0x7180071640] public void penguin.reuse.dex.ImageSubView.onDetachedFromWindow()
[0x71800715b0] public void penguin.reuse.dex.TextureSubView.onDetachedFromWindow()

添加 -dontshrink 参数减少更多的代码压缩,问题依旧存在,相比之前少了部分复用函数。

typescript 复制代码
core-parser> method 0x4a338698 --dex
public void androidx.appcompat.widget.ActionBarContextView.onDetachedFromWindow() [dex_method_idx=23009]
DEX CODE:
  0x71e92237e4: 106f 53d4 0001           | invoke-super {v1}, void android.view.View.onDetachedFromWindow() // method@21460
  0x71e92237ea: 1054 7a39                | iget-object v0, v1, Lt/a;.z:Landroidx/appcompat/widget/a; // field@31289
  0x71e92237ee: 0038 000a                | if-eqz v0, 0x71e9223802 //+10
  0x71e92237f2: 106e 5c6c 0000           | invoke-virtual {v0}, boolean androidx.appcompat.widget.a.F() // method@23660
  0x71e92237f8: 1154 7a39                | iget-object v1, v1, Lt/a;.z:Landroidx/appcompat/widget/a; // field@31289
  0x71e92237fc: 106e 5c6d 0001           | invoke-virtual {v1}, boolean androidx.appcompat.widget.a.G() // method@23661
  0x71e9223802: 000e                     | return-void
core-parser>

关闭了压缩,未使用到的内容也会编译进入,因此产生不同的字节码。于是在配合差异化处理即可。

结语

当前没有很好办法去避免此优化问题,差异化确保自己编写的代码不被复用,不能保证使用的三方库代码编译优化复用的情况,在 AGP 更新前,尽量的去避免引发问题。

相关推荐
00后程序员张2 小时前
iOS 上架费用全解析 开发者账号、App 审核、工具使用与开心上架(Appuploader)免 Mac 成本优化指南
android·macos·ios·小程序·uni-app·cocoa·iphone
来来走走2 小时前
Android开发(Kotlin) 扩展函数和运算符重载
android·开发语言·kotlin
wuwu_q2 小时前
用通俗易懂 + Android 开发实战的方式,详细讲解 Kotlin Flow 中的 retryWhen 操作符
android·开发语言·kotlin
天选之女wow2 小时前
【代码随想录算法训练营——Day60】图论——94.城市间货物运输I、95.城市间货物运输II、96.城市间货物运输III
android·算法·图论
沐怡旸3 小时前
【底层机制】Android对Linux线程调度的移动设备优化深度解析
android·面试
正经教主4 小时前
【咨询】Android Studio 第三方手机模拟器对比【202511】
android·ide·android studio
Jomurphys5 小时前
网络 - 缓存
android
似霰6 小时前
安卓14移植以太网&&framework-connectivity-t 编译问题
android·framework·安卓·ethernet
Android-Flutter6 小时前
kotlin - 显示HDR图(heic格式),使用GainMap算法,速度从5秒提升到0.6秒
android·kotlin