日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

JAndFix: 基于Java实现的Android实时热修复方案

發布時間:2025/3/15 Android 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAndFix: 基于Java实现的Android实时热修复方案 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡述

  • JAndFix是一種基于Java實現的Android實時熱修復方案,它并不需要重新啟動就能生效。JAndFix是在AndFix的基礎上改進實現,AndFix主要是通過jni實現對method(ArtMethod)結構題內容的替換。JAndFix是通過Unsafe對象直接操作Java虛擬機內存來實現替換。

原理

  • 為何JAndfix能夠做到即時生效呢? 原因是這樣的,在app運行到一半的時候,所有需要發生變更的Class已經被加載過了,在Android上是無法對一個Class進行卸載的。而騰訊系的方案,都是讓Classloader去加載新的類。如果不重啟,原來的類還在虛擬機中,就無法加載新類。因此,只有在下次重啟的時候,在還沒走到業務邏輯之前搶先加載補丁中的新類,這樣后續訪問這個類時,就會Resolve為新的類。從而達到熱修復的目的。JAndfix采用的方法是,在已經加載了的類中直接拿到Method(ArtMethod)在JVM的地址,通過Unsafe直接修改Method(ArtMethod)地址的內容,是在原來類的基礎上進行修改的。我們這就來看一下JAndfix的具體實現。

虛擬機調用方法的原理

為什么這樣替換完就可以實現熱修復呢?這需要從虛擬機調用方法的原理說起。在Android 6.0,art虛擬機中ArtMethod的結構是這個樣子的:

@art/runtime/art_method.hclass ArtMethod FINAL {... ...protected:// Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".// The class we are a part of.GcRoot<mirror::Class> declaring_class_;// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.GcRoot<mirror::PointerArray> dex_cache_resolved_methods_;// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.GcRoot<mirror::ObjectArray<mirror::Class>> dex_cache_resolved_types_;// Access flags; low 16 bits are defined by spec.uint32_t access_flags_;/* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */// Offset to the CodeItem.uint32_t dex_code_item_offset_;// Index into method_ids of the dex file associated with this method.uint32_t dex_method_index_;/* End of dex file fields. */// Entry within a dispatch table for this method. For static/direct methods the index is into// the declaringClass.directMethods, for virtual methods the vtable and for interface methods the// ifTable.uint32_t method_index_;// Fake padding field gets inserted here.// Must be the last fields in the method.// PACKED(4) is necessary for the correctness of// RoundUp(OFFSETOF_MEMBER(ArtMethod, ptr_sized_fields_), pointer_size).struct PACKED(4) PtrSizedFields {// Method dispatch from the interpreter invokes this pointer which may cause a bridge into// compiled code.void* entry_point_from_interpreter_;// Pointer to JNI function registered to this method, or a function to resolve the JNI function.void* entry_point_from_jni_;// Method dispatch from quick compiled code invokes this pointer which may cause bridging into// the interpreter.void* entry_point_from_quick_compiled_code_;} ptr_sized_fields_;... ... }

這其中最重要的字段就是entry_point_from_interprete_和entry_point_from_quick_compiled_code_了,從名字可以看出來,他們就是方法的執行入口。我們知道,Java代碼在Android中會被編譯為Dex Code。 art中可以采用解釋模式或者AOT機器碼模式執行。

解釋模式,就是取出Dex Code,逐條解釋執行就行了。如果方法的調用者是以解釋模式運行的,在調用這個方法時,就會取得這個方法的entry_point_from_interpreter_,然后跳轉過去執行。

而如果是AOT的方式,就會先預編譯好Dex Code對應的機器碼,然后運行期直接執行機器碼就行了,不需要一條條地解釋執行Dex Code。如果方法的調用者是以AOT機器碼方式執行的,在調用這個方法時,就是跳轉到entry_point_from_quick_compiled_code_執行。

AndFix的方法替換其本質是ArtMethod指針所指內容的替換。?

變成了這樣的整體替換?

由Unsafe來實現相當于:

//src means source ArtMethod Address,dest mean destinction ArtMethod Addressprivate void replaceReal(long src, long dest) throws Exception {int methodSize = MethodSizeUtils.methodSize();int methodIndexOffset = MethodSizeUtils.methodIndexOffset();//methodIndex need not replace,becase the process of finding method in vtable need methodIndexint methodIndexOffsetIndex = methodIndexOffset / 4;//why 1? index 0 is declaring_class, declaring_class need not replace.for (int i = 1, size = methodSize / 4; i < size; i++) {if (i != methodIndexOffsetIndex) {int value = UnsafeProxy.getIntVolatile(dest + i * 4);UnsafeProxy.putIntVolatile(src + i * 4, value);}}}

so easy,JAndFix就這樣完成了方法替換。值得一提的是,由于忽略了底層ArtMethod結構的差異,對于所有的Android版本都不再需要區分,而統一以Unsafe實現即可,代碼量大大減少。即使以后的Android版本不斷修改ArtMethod的成員,只要保證ArtMethod數組仍是以線性結構排列,就能直接適用于將來的Android 8.0、9.0等新版本,無需再針對新的系統版本進行適配了。事實也證明確實如此,當我們拿到Google剛發不久的Android O(8.0)開發者預覽版的系統時。

對比方案

名字公司實現語言及時生效方法替換方法的增加減少Android版本機型性能損耗補丁大小回滾易用性
JAndFix阿里天貓JAVA支持支持不支持4.0+ALL支持
AndFix阿里支付寶C支持支持不支持4.0+極少部分不支持支持
Tinker騰訊JAVA不支持支持支持ALLALL不支持
Robust美團JAVA支持不支持不支持ALLALL支持差(需要反射調用,需要打包插件支持)
Dexposed個人C支持支持不支持4.0+部分不支持支持差(需要反射調用)

如何使用

try {Method method1 = Test1.class.getDeclaredMethod("string");Method method2 = Test2.class.getDeclaredMethod("string");MethodReplaceProxy.instance().replace(method1, method2);} catch (Exception e) {e.printStackTrace();}

Running DEMO

  • 把整個項目放入你的IDE即可(Android Studio)

注意

Proguard

-keep class com.tmall.wireless.jandfix.MethodSizeCase { *;}

解釋實現

  • 我以Android Art 6.0的實現來解釋為什么這樣實現就可實現方法替換
package com.tmall.wireless.jandfix;import java.lang.reflect.Field; import java.lang.reflect.Method;/*** Created by jingchaoqinjc on 17/5/15.*/public class MethodReplace6_0 implements IMethodReplace {static Field artMethodField;static {try {Class absMethodClass = Class.forName("java.lang.reflect.AbstractMethod");artMethodField = absMethodClass.getDeclaredField("artMethod");artMethodField.setAccessible(true);} catch (Exception e) {e.printStackTrace();}}@Overridepublic void replace(Method src, Method dest) {try {long artMethodSrc = (long) artMethodField.get(src);long artMethodDest = (long) artMethodField.get(dest);replaceReal(artMethodSrc, artMethodDest);} catch (Exception e) {e.printStackTrace();}}private void replaceReal(long src, long dest) throws Exception {// ArtMethod struct sizeint methodSize = MethodSizeUtils.methodSize();int methodIndexOffset = MethodSizeUtils.methodIndexOffset();//methodIndex need not replace,becase the process of finding method in vtableint methodIndexOffsetIndex = methodIndexOffset / 4;//why 1? index 0 is declaring_class, declaring_class need not replace.for (int i = 1, size = methodSize / 4; i < size; i++) {if (i != methodIndexOffsetIndex) {int value = UnsafeProxy.getIntVolatile(dest + i * 4);UnsafeProxy.putIntVolatile(src + i * 4, value);}}} }

1.declaring_class不能替換,為什么不能替換,是因為JVM去調用方式時很多地方都要對declaring_class進行檢查。替換declaring_class會導致未知的錯誤。 2.methodIndex 不能替換,因為public proected等簡介尋址的訪問權限,本質在尋找方法的時候會查找virtual_methods_,而virtual_methods_是個ArtMethod數組對象,需要通過methodIndex來查找,如果你的methodIndex不對會導致方法尋址出錯。 3.為什么AbstractMethod類中對應的artMethod屬性的值可以作為c層ArtMethod的地址直接使用?看源碼:

@@art/mirror/abstract_method.cc ArtMethod* AbstractMethod::GetArtMethod() {return reinterpret_cast<ArtMethod*>(GetField64(ArtMethodOffset())); }@@art/mirror/abstract_method.h static MemberOffset ArtMethodOffset() {return MemberOffset(OFFSETOF_MEMBER(AbstractMethod, art_method_));}

從源碼可以看出C層在獲取ArtMethod的地址,實際上就是把AbstractMethod的artMethod強制轉換成了ArtMethod*指針,及我們在Java拿到的artMethod就是c層ArtMethod的實際地址。是不是很簡單。

參考

  • Unsafe
  • AndFix

原文地址: https://github.com/cmzy/JAndFix

總結

以上是生活随笔為你收集整理的JAndFix: 基于Java实现的Android实时热修复方案的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。