Android 面試之必問Java基礎
Android 面試之必問Android基礎知識javascript
在Android早期的版本中,應用程序的運行環境是須要依賴Dalvik虛擬機的。不過,在後來的版本(大概是4.x版本),Android的運行環境卻換到了 Android Runtime,其處理應用程序執行的方式徹底不一樣於 Dalvik,Dalvik 是依靠一個 Just-In-Time (JIT) 編譯器去解釋字節碼。前端
不過,Dalvik模式下,開發者編譯後的應用代碼須要經過一個解釋器在用戶的設備上運行,這一機制並不高效,但讓應用能更容易在不一樣硬件和架構上運 行。ART 則徹底改變了這套作法,在應用安裝時就預編譯字節碼到機器語言,這一機制叫 Ahead-Of-Time (AOT)編譯。在移除解釋代碼這一過程後,應用程序執行效率更高、啓動也更快。java
下面是AOT編譯方式的一些優勢:android
ART 引入了預先編譯機制,可提升應用的性能。ART 還具備比 Dalvik 更嚴格的安裝時驗證。在安裝時,ART 使用設備自帶的 dex2oat 工具來編譯應用。該實用工具接受 DEX 文件做爲輸入,併爲目標設備生成通過編譯的應用可執行文件,該工具可以順利編譯全部有效的 DEX 文件。git
垃圾回收 (GC) 可能有損於應用性能,從而致使顯示不穩定、界面響應速度緩慢以及其餘問題。ART模式從如下幾個方面優化了垃圾回收的策略:github
支持採樣分析器
一直以來,開發者都使用 Traceview 工具(用於跟蹤應用執行狀況)做爲分析器。雖然 Traceview 可提供有用的信息,但每次方法調用產生的開銷會致使 Dalvik 分析結果出現誤差,並且使用該工具明顯會影響運行時性能ART 添加了對沒有這些限制的專用採樣分析器的支持,於是可更準確地瞭解應用執行狀況,而不會明顯減慢速度。支持的版本從KitKat (4.4)版本開始,爲 Dalvik 的 Traceview 添加了採樣支持。web
支持更多調試功能
ART 支持許多新的調試選項,特別是與監控和垃圾回收相關的功能。例如,查看堆棧跟蹤中保留了哪些鎖,而後跳轉到持有鎖的線程;詢問指定類的當前活動的實例數、請求查看實例,以及查看使對象保持有效狀態的參考;過濾特定實例的事件(如斷點)等。面試
優化了異常和崩潰報告中的診斷詳細信息
當發生運行時異常時,ART 會爲您提供儘量多的上下文和詳細信息。ART 會提供 java.lang.ClassCastException、java.lang.ClassNotFoundException 和 java.lang.NullPointerException 的更多異常詳細信息(較高版本的 Dalvik 會提供 java.lang.ArrayIndexOutOfBoundsException 和 java.lang.ArrayStoreException 的更多異常詳細信息,這些信息如今包括數組大小和越界偏移量;ART 也提供這類信息)。算法
ART 提供了多個不一樣的 GC 方案,這些方案運行着不一樣垃圾回收器,默認的GC方案是 CMS(併發標記清除),主要使用粘性 CMS 和部分 CMS。粘性 CMS 是 ART 的不移動分代垃圾回收器。它僅掃描堆中自上次 GC 後修改的部分,而且只能回收自上次 GC 後分配的對象。除 CMS 方案外,當應用將進程狀態更改成察覺不到卡頓的進程狀態(例如,後臺或緩存)時,ART 將執行堆壓縮。bootstrap
除了新的垃圾回收器以外,ART 還引入了一種基於位圖的新內存分配程序,稱爲 RosAlloc(插槽運行分配器)。此新分配器具備分片鎖,當分配規模較小時可添加線程的本地緩衝區,於是性能優於 DlMalloc(內存分配器)。
內存分配器的相關知識能夠參考:內存分配器
同時,與 Dalvik 相比,ART的 CMS垃圾回收也帶來了其餘方面的改善,以下:
ART GC 與 Dalvik 的另外一個主要區別在於 ART GC 引入了移動垃圾回收器。使用移動 GC 的目的在於經過堆壓縮來減小後臺應用使用的內存。目前,觸發堆壓縮的事件是 ActivityManager 進程狀態的改變。當應用轉到後臺運行時,它會通知 ART 已進入再也不「感知」卡頓的進程狀態。此時 ART 會進行一些操做(例如,壓縮和監視器壓縮),從而致使應用線程長時間暫停。
目前,Android的ART正在使用的兩個移動 GC 是同構空間壓縮和半空間壓縮,它們的區別以下:
目前,Android的類加載器從下到上主要分爲BootstrapClassLoader(根類加載器)、 ExtensionClassLoader (擴展類加載器)和 AppClassLoader(應用類加載器)三種。
所謂雙親委託模式,指的是某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,才本身去加載。
由於這樣能夠避免重複加載,當父親已經加載了該類的時候,就沒有必要子 ClassLoader 再加載一次。若是不使用這種委託模式,那咱們就能夠隨時使用自定義的類來動態替代一些核心的類,存在很是大的安全隱患。
舉個例子,事實上,java.lang.String這個類並不會被咱們自定義的classloader加載,而是由bootstrap classloader進行加載,爲何會這樣?實際上這就是雙親委託模式的緣由,由於在任何一個自定義ClassLoader加載一個類以前,它都會先 委託它的父親ClassLoader進行加載,只有當父親ClassLoader沒法加載成功後,纔會由本身加載。
下面是Android類加載器的模型圖:
下面看一下DexClassLoader,DexClassLoader 重載了 findClass 方法,在加載類時會調用其內部的 DexPathList 去加載。DexPathList 是在構造 DexClassLoader 時生成的,其內部包含了 DexFile,涉及的源碼以下。
··· public Class findClass(String name) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext); if (clazz != null) { return clazz; } } } return null; } ···
類加載器更多的內容,能夠參考:android 類加載器雙親委託模式
所謂Hook,就是在程序執行的過程當中去截取其中的某段信息,示意圖以下。
Android的Hook大致的流程能夠分爲以下幾步:
一、根據需求肯定須要 hook 的對象
二、尋找要hook的對象的持有者,拿到須要 hook 的對象
三、定義「要 hook 的對象」的代理類,而且建立該類的對象
四、使用上一步建立出來的對象,替換掉要 hook 的對象
下面是一段簡單的Hook的示例代碼,用到了Java的反射機制。
@SuppressLint({"DiscouragedPrivateApi", "PrivateApi"}) public static void hook(Context context, final View view) {// try { // 反射執行View類的getListenerInfo()方法,拿到v的mListenerInfo對象,這個對象就是點擊事件的持有者 Method method = View.class.getDeclaredMethod("getListenerInfo"); method.setAccessible(true);//因爲getListenerInfo()方法並非public的,因此要加這個代碼來保證訪問權限 Object mListenerInfo = method.invoke(view);//這裏拿到的就是mListenerInfo對象,也就是點擊事件的持有者 // 要從這裏面拿到當前的點擊事件對象 Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 這是內部類的表示方法 Field field = listenerInfoClz.getDeclaredField("mOnClickListener"); final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真實的mOnClickListener對象 // 2. 建立咱們本身的點擊事件代理類 // 方式1:本身建立代理類 // ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance); // 方式2:因爲View.OnClickListener是一個接口,因此能夠直接用動態代理模式 // Proxy.newProxyInstance的3個參數依次分別是: // 本地的類加載器; // 代理類的對象所繼承的接口(用Class數組表示,支持多個接口) // 代理類的實際邏輯,封裝在new出來的InvocationHandler內 Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.d("HookSetOnClickListener", "點擊事件被hook到了");//加入本身的邏輯 return method.invoke(onClickListenerInstance, args);//執行被代理的對象的邏輯 } }); // 3. 用咱們本身的點擊事件代理類,設置到"持有者"中 field.set(mListenerInfo, proxyOnClickListener); } catch (Exception e) { e.printStackTrace(); } } // 自定義代理類 static class ProxyOnClickListener implements View.OnClickListener { View.OnClickListener oriLis; public ProxyOnClickListener(View.OnClickListener oriLis) { this.oriLis = oriLis; } @Override public void onClick(View v) { Log.d("HookSetOnClickListener", "點擊事件被hook到了"); if (oriLis != null) { oriLis.onClick(v); } } }
而在Android開發中,想要實現Hook,確定是沒有這麼簡單的,咱們須要藉助一些Hook框架,好比Xposed、Cydia Substrate、Legend等。
參考資料:Android Hook機制
衆所周知,Java代碼是很是容易反編譯的,爲了更好的保護Java源代碼,咱們每每會對編譯好的Class類文件進行混淆處理。而ProGuard就是一個混淆代碼的開源項目。它的主要做用就是混淆,固然它還能對字節碼進行縮減體積、優化等,但那些對於咱們來講都算是次要的功能。
具體來講,ProGuard具備以下功能:
在Android開發中,開啓混淆須要將app/build.gradle文件下的minifyEnabled屬性設置爲true,以下所示。
minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguard-android.txt是Android提供的默認混淆配置文件,咱們須要的混淆的規則都放在這個文件中。
混淆命令
混淆通配符
<field>
:匹配類中的全部字段<method>
:匹配類中全部的方法<init>
:匹配類中全部的構造函數*
: 匹配任意長度字符,不包含包名分隔符(.)**
: 匹配任意長度字符,包含包名分隔符(.)***
: 匹配任意參數類型keep的規則的格式以下:
[keep命令] [類] { [成員] }
ProGuard中有些公共的模版是能夠複用的,好比壓縮比、大小寫混合和一些系統提供的Activity、Service不能混淆等。
# 代碼混淆壓縮比,在 0~7 之間,默認爲 5,通常不作修改 -optimizationpasses 5 # 混合時不使用大小寫混合,混合後的類名爲小寫 -dontusemixedcaseclassnames # 指定不去忽略非公共庫的類 -dontskipnonpubliclibraryclasses # 這句話可以使咱們的項目混淆後產生映射文件 # 包含有類名->混淆後類名的映射關係 -verbose # 指定不去忽略非公共庫的類成員 -dontskipnonpubliclibraryclassmembers # 不作預校驗,preverify 是 proguard 的四個步驟之一,Android 不須要 preverify,去掉這一步可以加快混淆速度。 -dontpreverify # 保留 Annotation 不混淆 -keepattributes *Annotation*,InnerClasses # 避免混淆泛型 -keepattributes Signature # 拋出異常時保留代碼行號 -keepattributes SourceFile,LineNumberTable # 指定混淆是採用的算法,後面的參數是一個過濾器 # 這個過濾器是谷歌推薦的算法,通常不作更改 -optimizations !code/simplification/cast,!field/*,!class/merging/* ############################################# # # Android開發中一些須要保留的公共部分 # ############################################# # 保留咱們使用的四大組件,自定義的 Application 等等這些類不被混淆 # 由於這些子類都有可能被外部調用 -keep public class * extends android.app.Activity -keep public class * extends android.app.Appliction -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class * extends android.view.View -keep public class com.android.vending.licensing.ILicensingService # 保留 support 下的全部類及其內部類 -keep class android.support.** { *; } # 保留繼承的 -keep public class * extends android.support.v4.** -keep public class * extends android.support.v7.** -keep public class * extends android.support.annotation.** # 保留 R 下面的資源 -keep class **.R$* { *; } # 保留本地 native 方法不被混淆 -keepclasseswithmembernames class * { native <methods>; } # 保留在 Activity 中的方法參數是view的方法, # 這樣以來咱們在 layout 中寫的 onClick 就不會被影響 -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } # 保留枚舉類不被混淆 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # 保留咱們自定義控件(繼承自 View)不被混淆 -keep public class * extends android.view.View { *** get*(); void set*(***); public <init>(android.content.Context); public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); } # 保留 Parcelable 序列化類不被混淆 -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } # 保留 Serializable 序列化的類不被混淆 -keepnames class * implements java.io.Serializable -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient <fields>; !private <fields>; !private <methods>; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # 對於帶有回調函數的 onXXEvent、**On*Listener 的,不能被混淆 -keepclassmembers class * { void *(**On*Event); void *(**On*Listener); } # webView 處理,項目中沒有使用到 webView 忽略便可 -keepclassmembers class fqcn.of.javascript.interface.for.webview { public *; } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); public boolean *(android.webkit.WebView, java.lang.String); } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.webView, java.lang.String); } # js -keepattributes JavascriptInterface -keep class android.webkit.JavascriptInterface { *; } -keepclassmembers class * { @android.webkit.JavascriptInterface <methods>; } # @Keep -keep,allowobfuscation @interface android.support.annotation.Keep -keep @android.support.annotation.Keep class * -keepclassmembers class * { @android.support.annotation.Keep *; }
若是是aar這種插件,能夠在aar的build.gralde中添加以下混淆配置。
android { ··· defaultConfig { ··· consumerProguardFile 'proguard-rules.pro' } ··· }
若是要問Android的高級開發知識,那麼NDK確定是必問的。那麼什麼的NDK,NDK 全稱是 Native Development Kit,是一組可讓開發者在 Android 應用中使用C/C++ 的工具。一般,NDK能夠用在以下的場景中:
JNI即java native interface,是Java和Native代碼進行交互的接口。
假如,有以下一個Java類,代碼以下。
package com.xzh.jni; public class MyJob { public static String JOB_STRING = "my_job"; private int jobId; public MyJob(int jobId) { this.jobId = jobId; } public int getJobId() { return jobId; } }
而後,在cpp目錄下,新建native_lib.cpp,添加對應的native實現。
#include <jni.h> extern "C" JNIEXPORT jint JNICALL Java_com_xzh_jni_MainActivity_getJobId(JNIEnv *env, jobject thiz, jobject job) { // 根據實例獲取 class 對象 jclass jobClz = env->GetObjectClass(job); // 根據類名獲取 class 對象 jclass jobClz = env->FindClass("com/xzh/jni/MyJob"); // 獲取屬性 id jfieldID fieldId = env->GetFieldID(jobClz, "jobId", "I"); // 獲取靜態屬性 id jfieldID sFieldId = env->GetStaticFieldID(jobClz, "JOB_STRING", "Ljava/lang/String;"); // 獲取方法 id jmethodID methodId = env->GetMethodID(jobClz, "getJobId", "()I"); // 獲取構造方法 id jmethodID initMethodId = env->GetMethodID(jobClz, "<init>", "(I)V"); // 根據對象屬性 id 獲取該屬性值 jint id = env->GetIntField(job, fieldId); // 根據對象方法 id 調用該方法 jint id = env->CallIntMethod(job, methodId); // 建立新的對象 jobject newJob = env->NewObject(jobClz, initMethodId, 10); return id; }
首先,在 Java代碼中聲明 Native 方法,以下所示。
public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("MainActivity", stringFromJNI()); } private native String stringFromJNI(); }
而後,新建一個 cpp 目錄,而且新建一個名爲native-lib.cpp的cpp 文件,實現相關方法。
#include <jni.h> extern "C" JNIEXPORT jstring JNICALL Java_com_xzh_jni_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
cpp文件遵循以下的規則:
System.loadLibrary()的代碼位於java/lang/System.java文件中,源碼以下:
@CallerSensitive public static void load(String filename) { Runtime.getRuntime().load0(Reflection.getCallerClass(), filename); }
CMake 是一個開源的跨平臺工具系列,旨在構建、測試和打包軟件,從 Android Studio 2.2 開始,Android Sudio 默認地使用 CMake 與 Gradle 搭配使用來構建原生庫。具體來講,咱們可使用 Gradle 將 C \ C++ 代碼 編譯到原生庫中,而後將這些代碼打包到咱們的應用中, Java 代碼隨後能夠經過 Java 原生接口 ( JNI ) 調用 咱們原生庫中的函數。
使用CMake開發NDK項目須要下載以下一些套件:
咱們能夠打開Android Studio,依次選擇 【Tools】 > 【Android】> 【SDK Manager】> 【SDK Tools】選中LLDB、CMake 和 NDK便可。
啓用CMake還須要在 app/build.gradle 中添加以下代碼。
android { ··· defaultConfig { ··· externalNativeBuild { cmake { cppFlags "" } } ndk { abiFilters 'arm64-v8a', 'armeabi-v7a' } } ··· externalNativeBuild { cmake { path "CMakeLists.txt" } } }
而後,在對應目錄新建一個 CMakeLists.txt 文件,添加代碼。
# 定義了所需 CMake 的最低版本 cmake_minimum_required(VERSION 3.4.1) # add_library() 命令用來添加庫 # native-lib 對應着生成的庫的名字 # SHARED 表明爲分享庫 # src/main/cpp/native-lib.cpp 則是指明瞭源文件的路徑。 add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp) # find_library 命令添加到 CMake 構建腳本中以定位 NDK 庫,並將其路徑存儲爲一個變量。 # 可使用此變量在構建腳本的其餘部分引用 NDK 庫 find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log) # 預構建的 NDK 庫已經存在於 Android 平臺上,所以,無需再構建或將其打包到 APK 中。 # 因爲 NDK 庫已是 CMake 搜索路徑的一部分,只須要向 CMake 提供但願使用的庫的名稱,並將其關聯到本身的原生庫中 # 要將預構建庫關聯到本身的原生庫 target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib}) ···
動態加載技術在Web中很常見,對於Android項目來講,動態加載的目的是讓用戶不用從新安裝APK就能升級應用的功能,主要的應用場景是插件化和熱修復。
首先須要明確的一點,插件化和熱修復不是同一個概念,雖然站在技術實現的角度來講,他們都是從系統加載器的角度出發,不管是採用hook方式,亦或是代理方式或者是其餘底層實現,都是經過「欺騙」Android 系統的方式來讓宿主正常的加載和運行插件(補丁)中的內容;可是兩者的出發點是不一樣的。
插件化,本質上是把須要實現的模塊或功能當作一個獨立的功能提取出來,減小宿主的規模,當須要使用到相應的功能時再去加載相應的模塊。而熱修復則每每是從修復bug的角度出發,強調的是在不須要二次安裝應用的前提下修復已知的bug。
爲了方便說明,咱們先理清幾個概念:
下圖展現了Android動態化開發框架的總體的架構。
關於插件化技術,最先能夠追溯到2012年的 AndroidDynamicLoader ,其原理是動態加載不一樣的Fragment實現UI替換,不過隨着15,16年更好的方案,這個方案漸漸的被淘汰了。再後來有了任玉剛的dynamic-load-apk方案,開始有了插件化的標準方案。然後面的方案大多基於Hook和動態代理兩個方向進行。
目前,插件化的開發並無一個官方的插件化方案,它是國內提出的一種技術實現,利用虛擬機的類的加載機制實現的一種技術手段,每每須要hook一些系統api,而Google從Android9.0開始限制對系統私有api的使用,也就形成了插件化的兼容性問題,如今幾個流行的插件化技術框架,都是大廠根據本身的需求,開源出來的,如滴滴的VirtualAPK,360的RePlugin等,你們能夠根據須要自行了解技術的實現原理。
說到熱修復的原理,就不得不提到類的加載機制,和常規的JVM相似,在Android中類的加載也是經過ClassLoader來完成,具體來講就是PathClassLoader 和 DexClassLoader 這兩個Android專用的類加載器,這兩個類的區別以下。
這兩個類都是繼承自BaseDexClassLoader,BaseDexClassLoader的構造函數以下。
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); }
這個構造函數只作了一件事,就是經過傳遞進來的相關參數,初始化了一個DexPathList對象。DexPathList的構造函數,就是將參數中傳遞進來的程序文件(就是補丁文件)封裝成Element對象,並將這些對象添加到一個Element的數組集合dexElements中去。
前面說過類加載器的做用,就是將一個具體的類(class)加載到內存中,而這些操做是由虛擬機完成的,對於開發者來講,只須要關注如何去找到這個須要加載的類便可,這也是熱修復須要乾的事情。
在Android中,查找一個名爲name的class須要經歷以下兩步:
所以,基於上面的理論,咱們能夠想到一個最簡單的熱修復方案。假設如今代碼中的某一個類出現Bug,那麼咱們能夠在修復Bug以後,將這些個類打包成一個補丁文件,而後經過這個補丁文件封裝出一個Element對象,而且將這個Element對象插到原有dexElements數組的最前端。這樣,當DexClassLoader去加載類時,因爲雙親加載機制的特色,就會優先加載插入的這個Element,而有缺陷的Element則沒有機會再被加載。事實上,QQ早期的熱修復方案就是這樣的。
QQ 空間補丁方案就是使用javaassist 插樁的方式解決了CLASS_ISPREVERIFIED的難題。涉及的步驟以下:
若是一個類的static方法,private方法,override方法以及構造函數中引用了其餘類,並且這些類都屬於同一個dex文件,此時該類就會被打上CLASS_ISPREVERIFIED。
QQ空間超級補丁方案在遇到補丁文件很大的時候耗時是很是嚴重的,由於一個大文件夾加載到內存中構建一個Element對象時,插入到數組最前端是須要耗費時間的,而這很是影響應用的啓動速度。基於這些問題,微信提出了Tinker 方案。
Tinker的思路是,經過修復好的class.dex 和原有的class.dex比較差生差量包補丁文件patch.dex,在手機上這個patch.dex又會和原有的class.dex 合併生成新的文件fix_class.dex,用這個新的fix_class.dex 總體替換原有的dexPathList的中的內容,進而從根本上修復Bug,下圖是演示圖。
相比QQ空間超級補丁方案,Tinker 提供的思路能夠說效率更高。對Tinker熱修復方案感興趣的同窗能夠去看看Tinker 源碼分析之DexDiff / DexPatch
以上提到的兩種方式,雖然策略有所不一樣,但總的來講都是從上層ClassLoader的角度出發,因爲ClassLoader的特色,若是想要新的補丁文件再次生效,不管你是插樁仍是提早合併,都須要從新啓動應用來加載新的DexPathList,從而實現Bug的修復。
AndFix 提供了一種運行時在Native修改Filed指針的方式,實現方法的替換,達到即時生效無需重啓,對應用無性能消耗的目的。不過,因爲Android在國內變成了安卓,各大手機廠商定製了本身的ROM,因此不少底層實現的差別,致使AndFix的兼容性並非很好。
Sophix採用的是相似類修復反射注入方式,把補丁so庫的路徑插入到nativeLibraryDirectories數組的最前面, 這樣加載so庫的時候就是補丁so庫而不是原來的so庫。
在修復類代碼的缺陷時,Sophix對舊包與補丁包中classes.dex的順序進行了打破與重組,使得系統能夠天然地識別到這個順序,以實現類覆蓋的目的。
在修復資源的缺陷時,Sophix構造了一個package id 爲 0x66 的資源包,這個包裏只包含改變了的資源項,而後直接在原有AssetManager中addAssetPath這個包便可,無需變動AssetManager對象的引用。
除了這些方案外,熱修復方案還有美團的Robust、餓了嗎的Amigo等。不過,對於Android的熱修復來講,很難有一種十分完美的解決方案。好比,在Android開發中,四大組件使用前須要在AndroidManifest中提早聲明,而若是須要使用熱修復的方式,不管是提早佔坑亦或是動態修改,都會帶來很強的侵入性。同時,Android碎片化的問題,對熱修復方案的適配也是一大考驗。