




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、Andfix學習記錄How to Use(官方)Initialize PatchManager,patchManager = new PatchManager(context); patchManager.init(appversion);/current versionLoad patch,patchManager.loadPatch();You should load patch as early as possible, generally, in the initialization phase of your application(such as Application.onCre
2、ate().Add patch,patchManager.addPatch(path);/path of the patch file that was downloadedWhen a new patch file has been downloaded, it will become effective immediately by addPatch還有一點就是混淆需要注意-keep class * extends java.lang.annotation.Annotation -keepclasseswithmembernames class * native <methods&g
3、t; -keep class com.alipay.euler.andfix.* *; 命令輸入有點麻煩,可以自己寫一個win的腳本apkpatch -f 2.apk -t 1.apk -o . -k finance.keystore -p -a -e 這樣基本可以照貓畫虎折騰熱更新了,當然不要忘記添加讀寫權限<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permi
4、ssion.READ_EXTERNAL_STORAGE"/>原理篇andfix 深入補丁深入andfix 原理andfix的核心原理就是方法替換 在通過其apath工具給需要替換的方法加上注解repleaceMethod,這樣在執(zhí)行時把有bug的方法替換成補丁文件中執(zhí)行的方法。(在Native 層使用指針替換的方式替換bug的方法,從而達到修復bug的目的),具體過程如下圖:使用虛擬機的JarFile加載的補丁文件,讀取PATCH.MF文件得到補丁類名稱獲取補丁方法使用DexFile讀取patch文件的dex文件,獲取后根據注解獲取補丁方法獲取bug所在的方法根據注解中獲取到的
5、類名和方法,使用ClassLaoder獲取到class,然后根據反射得到bug Method,并將其訪問屬性修改為public Java 層-Native 層替換方法使用JNI來替換bug所在方法對象的屬性來修復bug簡要類之間關系圖修復的具體過程為:1)我們及時修復好bug之后,我們可以apkpatch工具將兩個apk做一次對比,然后找出不同的部分。生成的apatch了文件。若果這個時候,我們把后綴改成zip再解壓開,里面有一個dex文件。反編譯之后查看一下源碼,里面就是被修復的代碼所在的類文件,這些更改過的類都加上了一個_CF的后綴,并且變動的方法都被加上了一個叫MethodReplace
6、的annotation,通過clazz和method指定了需要替換的方法。(后面補丁原理會說到)2)客戶端得到補丁文件后就會根據annotation來尋找需要替換的方法。從AndFixManager.fix方法開始,客戶端找到對應的需要替換的方法,然后在fix方法的具體實現中調用fixClass方法進行方法替換過程。3)由JNI層完成方法的替換。fixClass方法遍歷補丁class里的方法,在jni層對所需要替換的方法進行一一替換。(AndfixManager#replaceMethod)源碼解析遵循使用時四步走:Step1:初始化PatchMangerPatchManager patchM
7、anager = new PatchManager();參閱 patchManager類源碼>AndfixManager 其中包含了Compat兼容性檢測類、SecurityChecker安全性檢查類public AndFixManager(Context context) mContext = context; /判斷機型是否支持Andfix 阿里的YunOs不支持 mSupport = Compat.isSupport(); if (mSupport) /初始化簽名判斷類 mSecurityChecker = new SecurityChecker(mContext); mOptDi
8、r = new File(mContext.getFilesDir(), DIR); / make directory fail if (!mOptDir.exists() && !mOptDir.mkdirs() mSupport = false; Log.e(TAG, "opt dir create error."); else if (!mOptDir.isDirectory() / not directory /如果不是目錄則刪除 mOptDir.delete(); mSupport = false; Step2:使用PatchManger檢查版本p
9、atchManager.init(apk版本)參閱patchManager#init >Patch 構造函數初始化 init 主要是版本比對,記錄版本號;根據版本號將patch清除或者加載到緩存中參閱Patch#init public void init(String appVersion) if (!mPatchDir.exists() && !mPatchDir.mkdirs() / make directory fail Log.e(TAG, "patch dir create error."); return; else if (!mPatch
10、Dir.isDirectory() / not directory mPatchDir.delete(); return; SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);/緩存版本號 String ver = sp.getString(SP_VERSION, null); if (ver = null | !ver.equalsIgnoreCase(appVersion) /根據傳入版本號作對比,若不同,則刪除本地的補丁文件 cleanPatch(); sp.edit().
11、putString(SP_VERSION, appVersion).commit();/傳入新的版本號 else initPatchs();/初始化patch列表,把本地的patch加載到內存中 private void initPatchs() File files = mPatchDir.listFiles(); for (File file : files) addPatch(file); Patch文件的加載 使用JarFile讀取Patch文件,讀取一些屬性如patchname,createtime,其中如果本地保存了多個補丁,那么AndFix會按照補丁生成的時間順序加載補丁。具體是
12、根據.apatch文件中的PATCH.MF的字段Created-Time。step3:loadPatchpatchManager.loadPatch();參閱patchManager#loadPatch提供了3個重載方法public void loadPatch()/andfix 初始化之后調用 private void loadPatch(Patch patch)/下載補丁完成后調用,addPatch(path) public void loadPatch(String patchName, ClassLoader classLoader)/提供了自定義類加載器的實現 這三個核心都是調用了p
13、ublic synchronized void fix(File file, ClassLoader classLoader, List classes)參看AndfixManager#fixpublic synchronized void fix(File file, ClassLoader classLoader, List<String> classes) if (!mSupport) return; /判斷補丁的簽名 if (!mSecurityChecker.verifyApk(file) / security check fail return; try File op
14、tfile = new File(mOptDir, file.getName(); boolean saveFingerprint = true; if (optfile.exists() / need to verify fingerprint when the optimize file exist, / prevent someone attack on jailbreak device with / Vulnerability-Parasyte. / btw:exaggerated android Vulnerability-Parasyte / /如果本地已經存在補丁文件,則校驗指紋
15、信息 if (mSecurityChecker.verifyOpt(optfile) saveFingerprint = false; else if (!optfile.delete() return; /加載patch文件中的dex final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(), optfile.getAbsolutePath(), Context.MODE_PRIVATE); if (saveFingerprint) mSecurityChecker.saveOptSig(optfile); ClassLoa
16、der patchClassLoader = new ClassLoader(classLoader) /重寫了ClassLoader的findClass方法 Override protected Class<?> findClass(String className) throws ClassNotFoundException Class<?> clazz = dexFile.loadClass(className, this); if (clazz = null && className.startsWith("com.alipay.eul
17、er.andfix") return Class.forName(className);/ annotation注解class / not found if (clazz = null) throw new ClassNotFoundException(className); return clazz; ; Enumeration<String> entrys = dexFile.entries(); Class<?> clazz = null; while (entrys.hasMoreElements() String entry = entrys.nex
18、tElement(); if (classes != null && !classes.contains(entry) continue;/ skip, not need fix clazz = dexFile.loadClass(entry, patchClassLoader);/獲取有bug的類文件 if (clazz != null) fixClass(clazz, classLoader);/核心- catch (IOException e) Log.e(TAG, "pacth", e); fix>fixclass>replaceMeth
19、od>Andfix#replaceMethod(Method dest,Method src) Native方法private void fixClass(Class<?> clazz, ClassLoader classLoader) /反射找到clazz中的所有方法 Method methods = clazz.getDeclaredMethods(); /MethodReplace的注解 MethodReplace methodReplace; String clz; String meth; for (Method method : methods) /遍歷所有方法,
20、找到有MethodReplace注解的方法,即需要替換的方法 methodReplace = method.getAnnotation(MethodReplace.class);/獲取此方法的注解,有bug的方法生成patch的類中的方法都是有注解的 if (methodReplace = null) continue; clz = methodReplace.clazz(); /獲取注解中的clazz的值 meth = methodReplace.method(); /獲取注解中method的值 if (!isEmpty(clz) && !isEmpty(meth) /找到需
21、要替換的方法后調用replaceMethod替換方法 replaceMethod(classLoader, clz, meth, method); private void replaceMethod(ClassLoader classLoader, String clz, String meth, Method method) try String key = clz + "" + classLoader.toString(); /根據key查找緩存中的數據,該緩存記錄了已經被修復過得class Class<?> clazz = mFixedClass.get
22、(key); if (clazz = null) / class not load /找不到說明該class沒有被修復過,則通過類加載器去加載 Class<?> clzz = classLoader.loadClass(clz); / initialize target class /通過C層改寫accessFlag,把需要替換的類的所有方法(Field)改成了public clazz = AndFix.initTargetClass(clzz);/初始化target class if (clazz != null) / initialize class OK mFixedClas
23、s.put(key, clazz); Method src = clazz.getDeclaredMethod(meth, method.getParameterTypes(); /根據反射拿到有bug的類的方法 /這里是調用了jni,art和dalvik分別執(zhí)行不同的替換邏輯,在cpp進行實現 AndFix.addReplaceMethod(src, method);/替換方法 src是有bug的方法,method是補丁方法 catch (Exception e) Log.e(TAG, "replaceMethod", e); Natvie方法的分析見下面前三步都是一開始
24、初始化時候要做的,而最后一步第四步則是補丁下載好之后再做的step4: 添加PatchpatchManager.addPatch(path)參閱PatchManager#addPatch,最終還是執(zhí)行l(wèi)oadpatchappPatch>copy到andfix默認的文件夾下>執(zhí)行l(wèi)oadPatch(補丁立即生效) public void addPatch(String path) throws IOException File src = new File(path); File dest = new File(mPatchDir, src.getName(); if(!src.ex
25、ists() throw new FileNotFoundException(path); if (dest.exists() Log.d(TAG, "patch " + path + " has be loaded."); return; /這一步很重要,通過這一步將你所下載保存的patch文件,copy到andfix自己默認的文件夾內存的data/data/apatch FileUtil.copyFile(src, dest);/ copy to patch's directory Patch patch = addPatch(dest);
26、if (patch != null) /加載patch 補丁立即生效 loadPatch(patch); 小結一下: 可以看出andfix的核心就是兩大步 - java層 實現加載補丁文件,安全驗證等操作,然后根據補丁匯總的注解找到將要替換的方法,交給Native層去處理替換方法 - native層:利用Java hook的技術來替換要修復的方法附 Native 分析在JNI目錄下 art和darvik文件中andfix.cpp#replaceMethod>art_method_replace.cpp(根據版本)art_method_replace_5_0.cppDalvikDalvik
27、是Google公司自己設計用于Android平臺的Java虛擬機。Dalvik虛擬機是Google等廠商合作開發(fā)的Android移動設備平臺的核心組成部分之一。它可以支持已轉換為 .dex(即Dalvik Executable)格式的Java應用程序的運行,.dex格式是專為Dalvik設計的一種壓縮格式,適合內存和處理器速度有限的系統(tǒng)。Dalvik 經過優(yōu)化,允許在有限的內存中同時運行多個虛擬機的實例,并且每一個Dalvik 應用作為一個獨立的Linux 進程執(zhí)行。獨立的進程可以防止在虛擬機崩潰的時候所有程序都被關閉。ARTAndroid操作系統(tǒng)已經成熟,Google的Android團隊開始
28、將注意力轉向一些底層組件,其中之一是負責應用程序運行的Dalvik運行時。Google開發(fā)者已經花了兩年時間開發(fā)更快執(zhí)行效率更高更省電的替代ART運行時。 ART代表Android Runtime,其處理應用程序執(zhí)行的方式完全不同于Dalvik,Dalvik是依靠一個Just-In-Time (JIT)編譯器去解釋字節(jié)碼。開發(fā)者編譯后的應用代碼需要通過一個解釋器在用戶的設備上運行,這一機制并不高效,但讓應用能更容易在不同硬件和架構上運 行。ART則完全改變了這套做法,在應用安裝時就預編譯字節(jié)碼到機器語言,這一機制叫Ahead-Of-Time (AOT)編譯。在移除解釋代碼這一過程后,應用程序執(zhí)
29、行將更有效率,啟動更快。-優(yōu)缺點ART優(yōu)點:1、系統(tǒng)性能的顯著提升。2、應用啟動更快、運行更快、體驗更流暢、觸感反饋更及時。3、更長的電池續(xù)航能力。4、支持更低的硬件。ART缺點:1、更大的存儲空間占用,可能會增加10%-20%。2、更長的應用安裝時間??偟膩碚fART的功效就是“空間換時間”。其他重要函數PatchManage#removeAllPatch()這個函數是在PatchManage#init(viersin) verision不同時調用的方法一樣,清空補丁目錄文件,這在做保護的時候十分重要。 public void removeAllPatch() cleanPatch(); Sh
30、aredPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); sp.edit().clear().commit();比如在laodPatch,包括初始化的時候patchManager.loadPatch()和patchManager.addPatch(其實也是調用loadpath)public void loadPatch() mLoaders.put("*", mContext.getClassLoader();/ wildcard Set<String>
31、patchNames; List<String> classes; for (Patch patch : mPatchs) patchNames = patch.getPatchNames(); for (String patchName : patchNames) classes = patch.getClasses(patchName);/獲取patch對用的class類集合 mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(), classes);/核心-修復bug方法 因此需要在以下兩處做好保護publi
32、c void starAndfix() try mPatchManager = new PatchManager(context); mPatchManager.init(BuildConfig.VERSION_NAME);/更換版本號,補丁會被清除 AppLog.d(TAG, "inited."); mPatchManager.loadPatch(); requestHotFixServer(lastSign); catch (Throwable throwable) mPatchManager.removeAllPatch(); AppLog.d(TAG, "
33、outer catch error remove apatch"); try mPatchManager.addPatch(context.getFilesDir() + "/" + DIR + APATCH_PATH); catch (Throwable throwable) mPatchManager.removeAllPatch(); AppLog.d(TAG, "inner catch error remove apatch"); 補丁原理apkPatch工具解析apkpatch是一個jar包,并沒有開源出來,我們可以使用JD-GUI來
34、查看其源碼。首先找到Main.class 位于com.euler.patch包下,找到main方法 Main#97public static void main(final String args) . /根據上面命令輸入拿到參數 ApkPatch apkPatch = new ApkPatch(from, to, name, out, keystore, password, alias, entry); apkPatch.doPatch();>ApkPatch#doPatch public void doPatch() try /生成smail文件夾 File smaliDir = n
35、ew File(this.out, "smali"); if (!smaliDir.exists() smaliDir.mkdir(); try FileUtils.cleanDirectory(smaliDir); catch (IOException e) throw new RuntimeException(e); /新建diff.dex文件 File dexFile = new File(this.out, "diff.dex"); if (dexFile.exists() && (!dexFile.delete() throw
36、new RuntimeException("diff.dex can't be removed."); /新建diff.apatch文件 File outFile = new File(this.out, "diff.apatch"); if (outFile.exists() && (!outFile.delete() throw new RuntimeException("diff.apatch can't be removed."); /第一步:拿到兩個apk文件對比,對比信息寫入DiffInfo
37、 DiffInfo info = new DexDiffer().diff(this.from, this.to); /第二步:將對比結果info寫入.smail文件中,然后打包成dex文件 this.classes = buildCode(smaliDir, dexFile, info); /第三步:將生成的dex文件寫入jar包,并根據輸入的簽名信息進行簽名生成diff.apatch文件 build(outFile, dexFile); /第四步:將diff.apatch文件重命名 release(this.out, dexFile, outFile); catch (Exception
38、e) e.printStackTrace();代碼翻譯一下:對比apk文件,得到所需信息將結果打包為apatch文件主要的就是對比文件信息的DexDiffer().diff(this.from, this.to)方法>diff#DexDiffer#diffpublic DiffInfo diff(File newFile, File oldFile)throws IOException/提取新apk的dex文件DexBackedDexFile newDexFile = DexFileFactory.loadDexFile(newFile, 19, true);/提取舊apk的dex文件D
39、exBackedDexFile oldDexFile = DexFileFactory.loadDexFile(oldFile, 19, true);DiffInfo info = DiffInfo.getInstance();boolean contains = false;for (DexBackedClassDef newClazz : newDexFile.getClasses() Set oldclasses = oldDexFile .getClasses(); for (DexBackedClassDef oldClazz : oldclasses) /對比相同的方法,存儲為修改
40、的方法 if (newClazz.equals(oldClazz) /對比class文件的變量 compareField(newClazz, oldClazz, info); /對比class的方法,如果同一個類中沒有相同的方法,則判斷為新增方法(后面方法) compareMethod(newClazz, oldClazz, info); contains = true; break; if (!contains) info.addAddedClasses(newClazz); return info;從這段代碼可以看出dex diff得到兩個apk文件的差別信息,變量和方法變量public
41、void addAddedFields(DexBackedField field) this.addedFields.add(ield);throw new RuntimeException("can,t add new Field:" + field.getName() + "(" + field.getType() + "), " + "in class :" + field.getDefiningClass(); public void addModifiedFields(DexBackedField fie
42、ld) this.modifiedFields.add(field); throw new RuntimeException("can,t modified Field:" + field.getName() + "(" + field.getType() + "), " + "in class :" + field.getDefiningClass();可以看出不支持增加成員變量,也不支持修改成員變量。方法public void addAddedMethods(DexBackedMethod method) Sy
43、stem.out.println("add new Method:" + method.getReturnType() + " " + method.getName() + "(" + Formater.formatStringList(method.getParameterTypes() + ") in Class:" + method.getDefiningClass();this.addedMethods.add(method);if (!this.modifiedClasses.contains(metho
44、d.classDef) this.modifiedClasses.add(method.classDef);public void addModifiedMethods(DexBackedMethod method) System.out.println("add odified Method:" + method.getReturnType() + " " + method.getName() + "(" + Formater.formatStringList(method.getParameterTypes() + ") in Class:" + method.getDefiningClass();this.modifiedMethods.add(method);if (!this.modifiedClasses.contains(method.classDef) th
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 農村耕地抵押合同范例
- 體育合作合同范例
- 2024-2025學年河南省駐馬店市新蔡縣高一上冊10月月考數學檢測試卷
- 買賣終止合同范本
- 倉儲長期保管合同范例
- 冷庫燈具采購合同范例
- 不固定單價合同范例
- 養(yǎng)殖銷售合同范例
- 第六章 第3節(jié) 測量液體和固體的密度2024-2025學年新教材八年級上冊物理新教學設計(人教版2024)
- 加工雪地靴合同范本
- 廣東省佛山市2024年中考英語模擬試卷(含答案)
- ISO14644國際標準(中文版)
- DB22T 1189.2-2011 無公害農產品 天麻 第2部分:種子與種麻生產技術規(guī)程
- DL-T5024-2020電力工程地基處理技術規(guī)程
- 2024社工(初)《社會工作實務》考試題庫附答案
- 2024年蘭州市高三診斷考試(一診)數學試卷(含答案)
- 辦公耗材采購服務方案(技術方案)
- (高清版)JTGT 5532-2023 公路橋梁支座和伸縮裝置養(yǎng)護與更換技術規(guī)范
- 廣東省廣州市越秀區(qū)2022-2023學年六年級下學期期末數學試卷
- 《成功之路+進步篇+1》第1課課件
- (2024年)特種設備安全法律法規(guī)培訓課件
評論
0/150
提交評論