Android APK性能优化及线上性能监控

192 阅读27分钟

包体积

background:在大型APP性能优化中,包体积一直都是个比较让人头疼的事,做到后期需要的技术难度极高,但是收益却微乎其微...言归正传,虽然在我们打包时经过ProGuard等工具的编译优化,将会去除掉绝大多不被使用到的代码,但总有些代码模块随着不断的迭代功能已经下线,但是代码还是被带到了线上,最终造成apk文件增大。现针对这个问题现提出方案:在运行时监控类的使用情况,基于线上大量用户真实情况,反推出现有的废弃代码及模块,最后下掉这些无用代码及模块,达到减少包体积的的终极目标。

一、覆盖率采集的常见方案

本文所说的代码覆盖率均指类维度的代码覆盖率。

  1. 基于插桩

插桩的方式:编译时期在每个类(通常过滤系统类)插入static代码块,用于标记该class被JVM加载(使用过)。字节开源的bytex插件平台中的coverag-plugin插件就是基于此种方式统计线上代码覆盖率。但这种方式有几个致命的弊端:1.插桩本身会增大包体积,测试时发现插入15万个类的cinit,将会增大约3MB的包体积。2.由于插桩的类在首次被加载的时候都会执行标记逻辑,这会导致APP的性能劣化,特别是启动时的性能劣化

  1. Hack classTable

高德和阿里互娱应该是采用的这种方案采集线上代码覆盖率。classTable变量是Android7之后art虚拟机在ClassLoader.java中加入的成员属性,对应的具体定义在art/runtime/class_table.h中,在Java层classTable对应的只是个内存地址,所有通过ClassLoader加载的Class将会被保存到classTable。通过hack这个classTable,通过其提供的Visit函数能够判断这个类是否被加载过。但这种方案也有一个弊端就是实现相对比较复杂

blog.csdn.net/csdnnews/ar…

mp.weixin.qq.com/s/qCwBF-BNG…

二、基于JVMTI做线上覆盖率采集

1. JVMTI简介(ART TI)

1)简介

JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 Native 编程接口。通过这些接口,开发人员不仅可以调试在虚拟机上运行的 Java 程序,还能查看它们运行的状态、控制环境变量,甚至修改代码逻辑,从而帮助开发人员监控和优化程序性能。但在Android(ART)上被Google官方在Release模式中禁用掉了,如果需要使用则需要hack,同时对于系统版本有一定要求(>=Android8)。下图是Google官方对于ART TI的概念图:

image.png

  1. APP process:我们需要监控的APP进程
  2. Agent代理:需要我们自己实现定制的代理
  3. ART/Core:ART虚拟机
  4. External process:外部进程eg:Android studio profiler

图中的JVMTI Plugin的具体实现是libopenjdkjvmti.so,Android SDK 30以上的手机在apex/com.android.art/lib64/目录下

2)JVMTI能干什么

下面大概介绍一下JVMTI能做哪些黑科技:

  • 重定义 Class
  • 跟踪对象分配和垃圾回收过程
  • 遵循对象的引用树,遍历堆中的所有对象
  • 检查 Java 调用堆栈
  • 暂停和恢复所有线程
  • 其他功能

JVMTI主要通过我们注册的一些回调监听通知相应信息,比如监听线程启动、结束。监听类加载的过程等,而在这些回调中,我们能够做一些黑科技操作,比如在ClassFileLoadHook回调时,我们能拿到将要加载进JVM的class并做出相应修改,实现插桩、类文件修改等操作。JVMTI能做的东西非常多,本次我们主要通过JVMTI实现线上无用类的采集功能。 能够注册的回调:

c++ 体验AI代码助手 代码解读复制代码typedef struct {
                              /*   50 : VM Initialization Event */
    jvmtiEventVMInit VMInit;
                              /*   51 : VM Death Event */
    jvmtiEventVMDeath VMDeath;
                              /*   52 : Thread Start */
    jvmtiEventThreadStart ThreadStart;
                              /*   53 : Thread End */
    jvmtiEventThreadEnd ThreadEnd;
                              /*   54 : Class File Load Hook */
    jvmtiEventClassFileLoadHook ClassFileLoadHook;
                              /*   55 : Class Load */
    jvmtiEventClassLoad ClassLoad;
                              /*   56 : Class Prepare */
    jvmtiEventClassPrepare ClassPrepare;
                              /*   57 : VM Start Event */
    jvmtiEventVMStart VMStart;
                              /*   58 : Exception */
    jvmtiEventException Exception;
                              /*   59 : Exception Catch */
    jvmtiEventExceptionCatch ExceptionCatch;
                              /*   60 : Single Step */
    jvmtiEventSingleStep SingleStep;
                              /*   61 : Frame Pop */
    jvmtiEventFramePop FramePop;
                              /*   62 : Breakpoint */
    jvmtiEventBreakpoint Breakpoint;
                              /*   63 : Field Access */
    jvmtiEventFieldAccess FieldAccess;
                              /*   64 : Field Modification */
    jvmtiEventFieldModification FieldModification;
                              /*   65 : Method Entry */
    jvmtiEventMethodEntry MethodEntry;
                              /*   66 : Method Exit */
    jvmtiEventMethodExit MethodExit;
                              /*   67 : Native Method Bind */
    jvmtiEventNativeMethodBind NativeMethodBind;
                              /*   68 : Compiled Method Load */
    jvmtiEventCompiledMethodLoad CompiledMethodLoad;
                              /*   69 : Compiled Method Unload */
    jvmtiEventCompiledMethodUnload CompiledMethodUnload;
                              /*   70 : Dynamic Code Generated */
    jvmtiEventDynamicCodeGenerated DynamicCodeGenerated;
                              /*   71 : Data Dump Request */
    jvmtiEventDataDumpRequest DataDumpRequest;
                              /*   72 */
    jvmtiEventReserved reserved72;
                              /*   73 : Monitor Wait */
    jvmtiEventMonitorWait MonitorWait;
                              /*   74 : Monitor Waited */
    jvmtiEventMonitorWaited MonitorWaited;
                              /*   75 : Monitor Contended Enter */
    jvmtiEventMonitorContendedEnter MonitorContendedEnter;
                              /*   76 : Monitor Contended Entered */
    jvmtiEventMonitorContendedEntered MonitorContendedEntered;
                              /*   77 */
    jvmtiEventReserved reserved77;
                              /*   78 */
    jvmtiEventReserved reserved78;
                              /*   79 */
    jvmtiEventReserved reserved79;
                              /*   80 : Resource Exhausted */
    jvmtiEventResourceExhausted ResourceExhausted;
                              /*   81 : Garbage Collection Start */
    jvmtiEventGarbageCollectionStart GarbageCollectionStart;
                              /*   82 : Garbage Collection Finish */
    jvmtiEventGarbageCollectionFinish GarbageCollectionFinish;
                              /*   83 : Object Free */
    jvmtiEventObjectFree ObjectFree;
                              /*   84 : VM Object Allocation */
    jvmtiEventVMObjectAlloc VMObjectAlloc;
} jvmtiEventCallbacks;

jvmti官方文档

Google ART TI

本次的代码覆盖率采集功能使用jvmti提供的一个函数GetLoadedClasses

c 体验AI代码助手 代码解读复制代码jint classCount;
jclass *classes;
mJvmtiEnv->GetLoadedClasses(&classCount, &classes);

继续追踪堆栈发现,这个函数其实也是访问classTable变量,只不过jvmti-plugin内部帮我们封装好了,所以我们使用只需要调用接口就行了

image.png

2. 在Android上使用JVMTI的技术难点

由于JVMTI功能十分强大,可能会有各种不同的功能需要用到JVMTI的功能,比如线上代码覆盖率、内存监控、崩溃堆栈分析等,所以我将JVMTI封装为一套SDK——>JVMTI-SDK,整体设计如下: test.png

整个SDK大概分为4层:

  • Native层 负责与JVMTI-Plugin直接通信,且同时向Core层提供相应hack、回调接口等相应能力
  • Core层(Core) JVMTI核心基础层,下与Native通信,向上提供JVMTI基础能力,同时集成基础SDK
  • 功能层(Ability) 主要提供特定方向的功能,如无用类检测,能够使用Core层提供的JVMTI基础能力实现特定方向的复杂功能
  • 整合层(Integration) 直接被宿主依赖,暴露对外提供的接口。同时整合SDK功能,对各种功能进行统一控制

从代码结构来看,每一层都是一个module,本次的无用类检测属于功能层: image.png

由于Google在ART虚拟机中对TI功能进行了限制,必须要Debug模式才能使用TI能力,但我们是需要在线上使用该功能,所以突破Google对于Release包TI的封锁是必然的。 attachAgent的源码流程如下(借用图):

image.png

1)hack IsJdwpAllowed

当我们尝试在Release模式直接加载Agent代理时,程序会发生崩溃抛出异常java.lang.SecurityException: Can't attach agent, process is not debuggable. 在dalvik_system_VMDebug.cc中的void VMDebug_nativeAttachAgent发现首次被拦截的地方

c 体验AI代码助手 代码解读复制代码static void VMDebug_nativeAttachAgent(JNIEnv* env, jclass, jstring agent, jobject classloader) {
  if (agent == nullptr) {
    ScopedObjectAccess soa(env);
    ThrowNullPointerException("agent is null");
    return;
  }

  if (!Dbg::IsJdwpAllowed()) {
    ScopedObjectAccess soa(env);
    ThrowSecurityException("Can't attach agent, process is not debuggable.");
    return;
  }

  std::string filename;
  {
    ScopedUtfChars chars(env, agent);
    if (env->ExceptionCheck()) {
      return;
    }
    filename = chars.c_str();
  }

  Runtime::Current()->AttachAgent(env, filename, classloader);
}

IsJdwpAllowed这个值是debugger.cc的一个静态变量,VMDebug_nativeAttachAgent函数是通过debugger.cc中国的gJdwpAllowed变量来判断,这里首先我们需要hack这个值,由于是静态变量,所以相对比较简单,查询Dbg::setJdwoAllowed的符号:_ZN3art3Dbg14SetJdwpAllowedEb(64位),由于Android7以上对于系统库的dlopen等函数做了限制,所以这里也需要绕过,如dlfunctions、xdl

2)hack is_java_debuggable_属性

上面对于gJdwpAllowed的hack完成之后,发现程序运行不崩溃了,但是还是有问题,c层Agent中拿到的jvmtiEnv指针为nullptr,attachAgent没有成功,我们继续分析源码

image.png

根据第一行系统log,找到对应的报错位置

image.png

最终跟到OpenjdkJvmTI.cc中的IsFullJvmtiAvailable

image.png 发现是is_java_debuggable_runtime的成员属性,hack这个值,需要拿到runtime对象,我们在Agent中可以通过JVM拿到唯一的runtime对象,然后跟上面一样的方式查SetJavaDebuggable函数符号_ZN3art7Runtime17SetJavaDebuggableEb(64位),拿到引用后调用函数修改is_java_debuggable_的值

c 体验AI代码助手 代码解读复制代码class Runtime {

public:
    ...
    
    // Whether Java code needs to be debuggable.
    bool is_java_debuggable_;
    bool IsJavaDebuggable() const {
    return is_java_debuggable_;
    }
    void SetJavaDebuggable(bool value);

    ...
}

经过如上操作Agent就能够attach成功了,能够如愿通过函数GetLoadedClasses拿到JVM中已经加载过的类信息,然后将这些类信息透传到Java层做处理(过滤、压缩等),然后上报后台

3)数据上传

数据的上传也大有讲究,线上全量后数据是非常大的,尽管是本地做缓存,上报压缩过滤后的增量数据,这块的成本也是非常大的,一个可行的方案是,在测试阶段将数据采集后上报后台,后台根据版本信息将这部分信息记录下来,在正式版本上线后通过CDN方式拉取这个缓存文件,且固定一个时间同步这个文件(从新拉取),这样的话,数据至少能减少80%,因为测试阶段基本会覆盖APP主链路,用户就不用从新上报这部分数据。本文暂不深究这块

3. 性能影响

使用JVMTI采集和上报数据,由于对于类的采集不是通过注册监听的方式,而是使用jvmti直接提供的接口,我们主动调用,还能控制采集间隔,所以理论上性能是几乎无影响的,下图是使用该方案在大型APP上的性能分析(Google pixel 4xl debug包):由此可见该方案的对于性能的影响可以称得上是无损方案(CPU的使用率增大其实是上报数据部分做本地diff导致,这部分可以优化,也建议使用idelHandler)

image.png

4. 兼容性适配

由于涉及到虚拟机ART hook,再加上Android8才支持JVMTI 1.2版本,所以会有兼容性风险,事实也确实如此

  1. Android8+的系统才能使用,且Android8的Debug.java不提供attachAgent的方法,Android9才提供,Android8的系统需要反射调用
  2. Android各系统的libart.so的文件目录会不同,这里需要适配,适配以下几个目录

image.png

  1. RuntimeDebugState 适配

上述方案在线上灰度的时候,发现三星的某类机型、OPPO的某类机型会在Android13有崩溃情况,首先怀疑是厂商定制了ROM,但是后来在Google pixel的Android14上也有相应崩溃,基本排除厂商定制ROM,排查发现是jvmtiEnv指针为空导致的,进一步排查发现是由于hack is_java_debuggable_这个Runtime成员属性的时候失败了。考虑到之前的版本没有问题,但是Android14上有问题,所以猜测可能是Android14更改了Runtime结构,导致hack失效。追踪最新的Android14源码:(不想跟着追源码直接看最后的结论)

frameworks/base/core/java/android/os/Debug.java

image.png

libcore/dalvik/src/main/java/dalvik/system/VMDebug.java

image.png

art/runtime/native/dalvik_system_VMDebug.cc

image.png

这里发现IsJdwpAllowed这个hack点没有变化,继续去Runtime的AttachAgent art/runtime/runtime.cc

image.png

image.png

image.png 看到这里发现已经变了,isJavaDebuggable居然不是判断的is_java_debuggable_这个成员属性的值了,而是一个枚举类型,但是仍然看不出问题,因为这里并没有被拦截,继续追踪,就要浮出水面了

image.png 跟踪到plugin的Load函数里

image.png

art/openjdkjvmti/OpenjdkJvmTi.cc到JVMTI-Plugin的初始化函数

image.png

GetEnvHandler

image.png

IsFullJvmtiAvailable()

image.png

image.png OK了大功告成

结论:Android高系统版本更改了Runtime结构,删除了is_java_debuggable_属性,所以上面对于此的hack将在Android高版本失效,增加了RuntimeDebugState枚举类型来代替之前的bool类型,其实是增加了一种状态,之前的bool表示不了3种状态,将之前的hack在高版本替换为kJavaDebuggableAtInit即可,看runtime的初始化代码发现,默认值为kNonJavaDebuggable

c 体验AI代码助手 代码解读复制代码class Runtime {
 public:
 ...
  enum class RuntimeDebugState {
    // This doesn't support any debug features / method tracing. This is the expected state usually.
    kNonJavaDebuggable,
    // This supports method tracing and a restricted set of debug features (for ex: redefinition
    // isn't supported). We transition to this state when method tracing has started or when the
    // debugger was attached and transition back to NonDebuggable once the tracing has stopped /
    // the debugger agent has detached..
    kJavaDebuggable,
    // The runtime was started as a debuggable runtime. This allows us to support the extended set
    // of debug features (for ex: redefinition). We never transition out of this state.
    kJavaDebuggableAtInit
  };
  
  // Whether Java code needs to be debuggable.
  RuntimeDebugState runtime_debug_state_;
  
  bool IsJavaDebuggable() const {
    return runtime_debug_state_ == RuntimeDebugState::kJavaDebuggable ||
           runtime_debug_state_ == RuntimeDebugState::kJavaDebuggableAtInit;
  }
  
  bool IsJavaDebuggableAtInit() const {
    return runtime_debug_state_ == RuntimeDebugState::kJavaDebuggableAtInit;
  }
  
  EXPORT void SetRuntimeDebugState(RuntimeDebugState state);
  ...
}

跟上面的hack方式类似,直接拿到高系统版本的libart.so文件,查SetRuntimeDebugState这个函数的符号:_ZN3art7Runtime20SetRuntimeDebugStateENS0_17RuntimeDebugStateE(64位),在AttachAgent之前将runtime中的这个值设置为kJavaDebuggableAtInit。OK适配结束,为了防止后续版本继续改runtime,导致之前的hack失效引发线上crash,建议增加hack失败的检查作为兜底,确保线上的稳定性。

作者:ZJcoming
链接:juejin.cn/post/734606…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

内存篇

JVM TI可以用来监控APP的哪些性能指标

1. 内存使用与垃圾回收

  • 堆内存与非堆内存监控
    通过JVM TI可实时获取堆内存各区域(如Eden、Survivor、老年代)的使用量、峰值及内存池分布情况

    5

    6

    7

    。例如,检测内存泄漏时,可追踪对象分配路径并分析未被回收的对象引用链

    10

  • 垃圾回收(GC)活动
    监控GC频率、耗时(如Young GC和Full GC的停顿时间)、回收的内存量,以及GC算法对应用性能的影响

    2

    5

    6

    。例如,通过GarbageCollectionStartGarbageCollectionFinish事件记录GC周期

    10

​2. 线程状态与并发问题

  • 线程生命周期与状态
    捕获线程的创建、启动、阻塞和终止事件,统计活跃线程数及状态分布(如RUNNABLE、BLOCKED、WAITING)

    3

    4

    10

    。例如,通过ThreadStartThreadEnd回调跟踪线程动态。

  • 死锁检测与线程资源竞争
    分析线程持有的锁及等待链,定位因锁竞争导致的性能瓶颈

    2

    4

    。例如,通过MonitorContendedEnter事件发现高并发场景下的锁争用问题

    10

​3. 类加载与代码执行

  • 类加载与卸载统计
    监控类加载次数、卸载数量及加载耗时,帮助识别类加载器泄漏或重复加载问题

    3

    7

    10

    。例如,通过ClassLoadClassPrepare事件追踪类加载过程。

  • 方法执行耗时分析
    利用MethodEntryMethodExit回调记录方法调用时间,定位高耗时方法

    7

    10

    。结合代码覆盖率统计(如网页7提到的类使用监控),可进一步优化无用代码

    7

​4. CPU与系统资源

  • CPU使用率与上下文切换
    监控应用占用的CPU时间片、用户态与内核态CPU消耗,以及线程上下文切换频率

    2

    5

    6

    。例如,结合线程状态分析CPU密集型任务对性能的影响。

  • I/O与网络资源
    间接关联磁盘读写和网络传输延迟(需结合系统级监控工具)

    9

    ,但JVM TI可通过对象分配追踪I/O相关资源占用

    10

​5. 动态代码修改与调试

  • 代码热替换(HotSwap)​
    支持在运行时重定义类(RedefineClasses),用于动态修复代码缺陷或注入监控逻辑

    10

    。例如,通过ClassFileLoadHook修改字节码以实现无侵入性能采样

    7

  • 断点与单步调试
    设置断点(Breakpoint)并触发单步执行事件(SingleStep),用于复杂问题的现场诊断

    10

​注意事项与限制

  1. Android平台限制
    JVM TI在Android中默认仅支持可调试应用(android:debuggable=true),但可通过反射或自定义Agent绕过部分限制

    10

  2. 性能开销控制
    高频事件(如方法进入/退出)可能产生较大性能损耗,需通过采样或过滤机制优化

    7

    10

  3. 兼容性
    ART TI功能在Android 8.0及以上版本逐步完善,低版本需依赖特定Hack方案

    7

    10

​应用场景示例

  • 内存泄漏排查:结合对象分配跟踪(ObjectAlloc事件)和GC日志,定位未释放对象

    5

    10

  • 线上性能分析:通过无侵入Agent(如网页7的代码覆盖率方案)统计类使用情况,优化包体积

    7

  • 高并发调优:分析线程池利用率及锁竞争,优化线程配置

    2

    4

综上,JVM TI为深度性能分析提供了底层能力支持,但需结合实际场景选择合适的监控策略和工具链(如结合Prometheus、SkyWalking等平台实现可视化

1

5

6

)。

app怎么监控binder调用

基于搜索结果,监控Android应用的Binder调用可通过以下方法实现:

​1. Java层动态代理监听(适用Android 10+)​

  • ProxyTransactListener机制
    Android 10引入的隐藏API Binder.ProxyTransactListener允许在Binder调用前后触发回调。通过动态代理创建监听实例并注入到BinderProxystransactListener属性,可全局监控Java层Binder调用

    1

    • 实现步骤
      1. 绕过Hidden API限制(如通过Native线程JNI调用VMRuntime.setHiddenApiExemptions加白)。
      2. 动态代理ProxyTransactListener接口,记录调用耗时、线程堆栈等信息。
    • 局限性
      • 无法获取Binder调用的数据参数(如Parcel内容)

        1

      • 仅支持Android 10及以上版本。

​2. JNI Hook拦截transactNative方法(全版本兼容)​

  • Native函数替换
    Binder调用最终会进入BinderProxy.transactNative这一JNI方法。通过JNI Hook替换其底层Native函数,可捕获完整的调用参数和返回结果

    1

    3

    • 关键技术点
      1. 定位原Native函数:通过反射获取BinderProxy.transactNative对应的ArtMethod指针。
      2. 函数替换:使用RegisterNatives覆盖原JNI实现,插入自定义监控逻辑(如记录codedataflags)。
    • 优势
      • 支持全Android版本,稳定性高

        1

      • 可解析Parcel数据中的服务名、参数等详细信息

        3

​3. 内核层eBPF监控(系统级追踪)​

  • 基于binder_transaction的eBPF探针
    在Linux内核中,Binder调用通过binder_ioctlbinder_transaction实现。通过eBPF挂载kprobe到这些函数,可捕获跨进程通信的原始数据

    2

    • 数据解析
      • 提取binder_transaction结构体中的target(目标服务)、code(操作码)、data(参数缓冲区指针)。

      • 结合进程上下文过滤目标应用包名

        2

    • 适用场景
      • 分析恶意应用的系统服务滥用行为(如短信/定位窃取)。
      • 需Root权限或自定义内核模块支持。

​4. 日志增强与系统源码修改(需系统权限)​

  • 激活BinderProxy的ALOGV日志
    在AOSP源码中,BinderProxy.transactNative内部包含ALOGV日志输出,但默认在User版本中关闭。通过以下修改可启用日志

    3

    1. log.h中强制定义LOG_NDEBUG=0以开启VERBOSE日志。
    2. 添加PID过滤逻辑,仅打印目标进程的Binder调用。
    3. 重新编译系统或使用Xposed模块动态注入。

​方案选型建议

需求场景

推荐方案

优缺点

线上卡顿监控(无Root)

Java动态代理 + JNI Hook

低侵入性,但需处理Hidden API限制

1

安全分析(需参数级监控)

eBPF + 内核探针

数据全面,依赖Root或定制ROM

2

系统调试(有源码权限)

日志增强 + 编译优化

直接获取调用链,需重新部署系统

3

​注意事项

  1. 性能影响:高频Binder调用场景(如频繁跨进程通信)需限制监控粒度,避免引发性能劣化。
  2. 兼容性风险:JNI Hook和内核探针可能因Android版本升级失效,需持续适配。
  3. 隐私合规:监控涉及跨进程数据时需符合隐私法规(如GDPR、个人信息保护法)。

通过以上方案,开发者可根据具体需求实现从应用层到内核层的Binder调用监控能力。

Android Hook

具体来说,Hook 技术主要包括以下几个特点: 动态修改: Hook 技术是在程序运行时进行修改,而不是在编译时。这使得它可以灵活地应用于各种场景,而不需要修改程序源码。 透明性: 使用 Hook 技术进行修改是透明的,对于程序的其他部分来说是不可见的。这有利于保持程序的整体一致性和稳定性。 可扩展性: Hook 技术可以用于各种程序功能的扩展和增强,例如系统监控、性能分析、安全检测等。 多样性: Hook 技术可以应用于不同的编程语言和平台,包括 Windows、Linux、macOS 等。它通常利用操作系统或运行时环境提供的钩子机制来实现。 

在 Java 中,常见的 Hook 技术包括:

  1. 使用反射修改现有类的方法实现

  2. 利用动态代理创建代理对象

  3. 通过 Java Instrumentation 接口修改类的字节码

  4. 利用 Java 的 SecurityManager 进行权限控制

在Android系统中,Hook技术通常用于以下几个方面:

1、拦截系统事件:如按键事件、触摸事件等
2、修改系统行为:如改变系统设置、拦截系统调用等

使用 Java 的反射机制可以修改现有类的方法实现。

import java.io.FileInputStream;
import java.lang.reflect.Method;

public class HookFileInputStream {
    public static void main(String[] args) throws Exception {
        // 获取 FileInputStream 的 read() 方法
        Method readMethod = FileInputStream.class.getDeclaredMethod("read");

        // 创建一个代理方法,实现自定义逻辑
        readMethod.invoke(new FileInputStream("example.txt"), new Object[0]);
    }

    private static Object proxyRead(Object instance, Method method, Object[] args) throws Throwable {
        System.out.println("Before reading file");
        Object result = method.invoke(instance, args);
        System.out.println("After reading file");
        return result;
    }
}
3、增强应用功能:如实现应用插件化、动态加载等
三、Hook技术的工作原理

Android Hook技术的核心在于方法拦截

它通过以下几个步骤实现:

  1. 获取目标方法或对象: 首先需要确定需要拦截的目标方法或对象。这可以通过反射或动态代理等技术来实现。
  2. 创建代理类或方法: 创建一个代理类或方法,用于在目标方法或对象被调用时执行自定义的逻辑。
  3. 替换或修改目标: 将原有的目标方法或对象替换为代理类或方法,使得后续的调用都会指向代理。
  4. 执行自定义逻辑: 在代理类或方法中执行自定义的逻辑,例如记录日志、修改参数、改变返回值等。
  5. 可选:恢复原状: 在某些情况下,可能需要在使用完 Hook 技术后将目标方法或对象恢复到原来的状态。

blog.csdn.net/lizhong2008…

www.cnblogs.com/linghu-java…

www.cnblogs.com/linghu-java…

zhuanlan.zhihu.com/p/109157321

Android插件化

Android插件化技术深度解析

​1. 插件化的核心价值与实现基础

插件化技术通过将应用功能模块解耦为独立插件,实现按需加载、动态更新体积优化,尤其适用于大型应用(如电商、社交平台)的复杂需求

1

2

。其核心价值包括:

  • 模块化开发:各功能插件可独立开发、测试和更新,降低团队协作成本

    2

    6

  • 动态更新能力:无需重新安装APK即可修复BUG或上线新功能,提升用户体验

    7

  • 资源与性能优化:通过插件按需加载,减少主包体积和内存占用,加速启动速度

    3

    6

​2. 关键技术组件
​2.1 类加载与资源隔离
  • 自定义ClassLoader:通过DexClassLoader动态加载插件中的类,实现宿主与插件的类隔离

    1

    7

    。例如,wxdynamicplugin框架通过零反射技术直接操作ArtMethod,避免传统反射的性能损耗

    3

  • 资源管理:需解决资源ID冲突问题,常见方案包括重写AAPT(资源打包工具)或动态分配资源ID

    6

    7

​2.2 组件生命周期管理
  • 代理机制:对于四大组件(如Activity),传统框架(如DynamicAPK)通过代理类管理生命周期,但需插件继承特定基类

    6

    。现代框架(如Shadow)则通过全动态化实现,无需修改插件代码

    3

  • 跨进程通信:AIDL或广播机制用于宿主与插件间的数据交互,确保功能解耦

    2

    4

​2.3 插件化框架的底层支持
  • Binder机制扩展:通过Hook系统服务(如ActivityManagerService)或动态修改Binder客户端逻辑,实现插件组件的系统级注册

    6

    7

  • 资源动态替换:利用AssetManager.addAssetPath加载插件资源,结合宿主资源形成统一资源池

    6

    7

​3. 主流框架对比与选型建议

框架

核心技术

优势

局限性

Shadow

零反射、全动态化

无需Hook系统,兼容性好

插件体积较大(3MB+)

3

wxdynamicplugin

零Hack、分段加载

插件体积极小(70KB/模块),启动速度秒级

需自定义编译流程

3

RePlugin

Hook系统服务、多进程支持

高稳定性,支持四大组件动态注册

对系统版本兼容性要求高

5

7

VirtualAPK

动态代理、资源合并

支持插件与宿主资源共享

需预注册插件权限

4

5

选型建议

  • 快速接入:选择RePlugin或VirtualAPK,适合需要快速实现插件化的传统项目

    4

    5

  • 极致性能:优先考虑wxdynamicplugin,适用于对启动速度和体积敏感的场景(如音乐类App)

    3

  • 系统兼容性:Shadow框架因零反射特性,更适配Android新版本限制非公开API访问的策略

    3

    7

​4. 开发挑战与应对策略
​4.1 兼容性与稳定性
  • 系统版本适配:不同Android版本对Binder和ClassLoader的实现差异需针对性处理

    7

    。例如,Android 10+限制Hidden API访问,需通过JNI动态豁免

    3

  • 资源冲突:采用资源ID动态分配或命名空间隔离技术,避免宿主与插件资源冲突

    6

    7

​4.2 性能优化
  • 冷启动加速:wxdynamicplugin通过插件分段加载(多APK按需加载),减少首次启动耗时

    3

  • 内存管理:及时卸载未使用插件,防止内存泄漏

    1

    6

​4.3 动态化与安全
  • 热修复集成:结合Tinker或Sophix,实现插件代码与资源的动态替换

    7

  • 代码混淆:对插件核心逻辑加固,防止反编译导致的业务逻辑泄露

    6

​5. 未来趋势
  • 轻量化与动态化:进一步压缩插件体积(如wxdynamicplugin的70KB模块),支持插件框架自身动态更新

    3

  • 跨平台融合:结合Flutter或WebAssembly,实现插件多端复用

    2

  • 系统级支持:随着Android对模块化设计的重视,未来可能原生支持插件化标准(如Google Play Dynamic Delivery)

    1

    7

​总结

Android插件化技术通过动态加载与资源隔离机制,为大型应用提供了模块化开发与灵活部署的解决方案。开发者需根据业务需求权衡框架特性,例如追求极致性能可选wxdynamicplugin,注重兼容性则采用Shadow或RePlugin。未来,随着动态化技术的演进,插件化将更深度融入系统生态,成为构建高可维护性应用的核心架构

对App的so进行Hook的一种思路

我们知道现在JNI在Android开发中是特别重要的,使用JNI有什么好处呢?

  • Preference,C/C++在运行性能上面甩Java几条GAI
  • Security,更多的加密解密还是放在Native上。

优点不止这两点,比如在Native里面开辟空间并不受JVM管理,JVM怎么使用native memory。这里不再赘述。

本文提供一种对Android上so库进行Hook的一种思路,不涉及ELF的查看修改,不改动对方的调用方式。 思路就是一招偷梁换柱,用自己的so替换App的so,让对象调用自己的so的时候调用我们自己写的so,我们再调用原来的so,这样就可以获得对方so方法的输入输出。

可以应用在想获取对方App的数据传递格式或者无法破解对方的加解密,但是可以通过hook获取对方的数据格式再调用对方的加解密方法得到自己想要的结果。

juejin.cn/post/744635…

blog.csdn.net/weixin\_391…

Android PLT Hook 技术详解

​1. 原理与核心机制

PLT(Procedure Linkage Table)Hook 是一种基于 ELF 文件动态链接机制的 Hook 技术,通过修改 ​GOT(Global Offset Table)​ 中的函数地址实现对外部函数调用的拦截

3

4

。其核心逻辑如下:

  • 动态链接过程:当 Android 应用调用动态库(如 libc.so)中的函数时,首次调用会通过 PLT 表跳转到动态链接器(Linker)进行符号解析,并将解析后的真实地址写入 GOT 表。后续调用则直接通过 GOT 表跳转至目标函数

    5

    6

  • Hook 切入点:PLT Hook 通过替换 GOT 表中的目标函数地址为自定义函数地址,使得所有对该函数的调用均重定向到 Hook 逻辑

    1

    4

​2. 实现步骤
  1. ELF 文件解析
    加载目标 SO 文件,解析其 ELF 结构,定位 .dynamic.rel.plt(重定位表)、.dynsym(动态符号表)等关键节区,获取目标函数在 GOT 表中的偏移地址

    5

    7

    c// 示例:通过 readelf 工具解析重定位信息
    readelf -r libtarget.so | grep 'pthread_create'
    
  2. 内存基址计算
    运行时通过 /proc/self/maps 获取目标 SO 文件的内存基址,结合 GOT 偏移计算出目标函数在内存中的实际地址

    6

  3. 函数地址替换
    修改目标地址的读写权限(mprotect),将 GOT 表中的原函数地址替换为自定义 Hook 函数地址

    1

    6

    c// 修改内存权限并替换地址
    mprotect((void*)got_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
    *((void**)got_entry) = custom_pthread_create;
    
​3. 优缺点分析

优点

局限性

稳定性高:仅修改数据段(GOT 表),不破坏代码段,避免触发内存保护机制

3

4

仅支持动态链接函数:无法 Hook 静态链接或模块内函数

3

6

兼容性好:支持 Android 4.1–12 及主流 CPU 架构(armeabi-v7a, arm64-v8a 等)

7

依赖符号表:需目标函数在 ELF 中存在符号信息

3

性能损耗低:首次调用后直接跳转,无额外解析开销

3

无法 Hook 未加载的 SO:需目标库已加载到内存

3

​4. 应用场景
  1. 性能监控
    拦截系统调用(如 openpthread_create),统计文件 I/O 耗时、线程创建频率等指标

    1

    6

  2. 安全检测
    监控敏感 API(如 SSL_write)调用,捕获网络数据明文或加密漏洞

    4

    7

  3. 功能扩展
    修改系统行为(如重定向文件读写路径),实现沙箱或虚拟化功能

    7

​5. 开源方案对比

框架

特点

适用场景

xHook

轻量级,支持按需 Hook 特定 SO,兼容性良好(IQIYI 开源)

3

线上 APM 监控、轻量化改造

bhook

字节跳动开源,支持 Android 4.1–12,提供线程创建/文件操作等通用 Hook 模板

7

大规模生产环境性能优化

PLT Hook

底层实现灵活,需开发者自行处理 ELF 解析和重定位逻辑

5

定制化需求或学术研究

​6. 实践建议
  • 选择 Hook 点:优先选择高频、低风险函数(如 malloc/free),避免 Hook 关键路径函数导致性能劣化

    3

    6

  • 线程安全:Hook 过程中需加锁或暂停目标线程,防止并发修改 GOT 表引发崩溃

    1

  • 兼容性测试:针对不同 Android 版本和厂商 ROM 验证 Hook 稳定性(如华为 EMUI 对 SO 加载的特殊处理)

    7

​总结

PLT Hook 凭借其稳定性和低侵入性,成为 Android Native 层监控与优化的主流方案。开发者可根据需求选择成熟的开源框架(如 bhook)或定制底层实现,结合性能、安全、兼容性等多维度评估,构建高效的动态化能力

3

7

juejin.cn/post/723139…