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

事件回顾

第一弹:你的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 更新前,尽量的去避免引发问题。

相关推荐
雨白6 小时前
Android 多线程:理解 Handler 与 Looper 机制
android
sweetying8 小时前
30了,人生按部就班
android·程序员
用户2018792831679 小时前
Binder驱动缓冲区的工作机制答疑
android
真夜9 小时前
关于rngh手势与Slider组件手势与事件冲突解决问题记录
android·javascript·app
用户2018792831679 小时前
浅析Binder通信的三种调用方式
android
用户099 小时前
深入了解 Android 16KB内存页面
android·kotlin
火车叼位10 小时前
Android Studio与命令行Gradle表现不一致问题分析
android
前行的小黑炭12 小时前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app
前行的小黑炭13 小时前
【Android】CoordinatorLayout详解;实现一个交互动画的效果(上滑隐藏,下滑出现);附例子
android·kotlin·app