




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、Andfix學(xué)習(xí)記錄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還有一點(diǎn)就是混淆需要注意-keep class * extends java.lang.annotation.Annotation -keepclasseswithmembernames class * native <methods&g
3、t; -keep class com.alipay.euler.andfix.* *; 命令輸入有點(diǎn)麻煩,可以自己寫(xiě)一個(gè)win的腳本apkpatch -f 2.apk -t 1.apk -o . -k finance.keystore -p -a -e 這樣基本可以照貓畫(huà)虎折騰熱更新了,當(dāng)然不要忘記添加讀寫(xiě)權(quán)限<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permi
4、ssion.READ_EXTERNAL_STORAGE"/>原理篇andfix 深入補(bǔ)丁深入andfix 原理andfix的核心原理就是方法替換 在通過(guò)其apath工具給需要替換的方法加上注解repleaceMethod,這樣在執(zhí)行時(shí)把有bug的方法替換成補(bǔ)丁文件中執(zhí)行的方法。(在Native 層使用指針替換的方式替換bug的方法,從而達(dá)到修復(fù)bug的目的),具體過(guò)程如下圖:使用虛擬機(jī)的JarFile加載的補(bǔ)丁文件,讀取PATCH.MF文件得到補(bǔ)丁類(lèi)名稱獲取補(bǔ)丁方法使用DexFile讀取patch文件的dex文件,獲取后根據(jù)注解獲取補(bǔ)丁方法獲取bug所在的方法根據(jù)注解中獲取到的
5、類(lèi)名和方法,使用ClassLaoder獲取到class,然后根據(jù)反射得到bug Method,并將其訪問(wèn)屬性修改為public Java 層-Native 層替換方法使用JNI來(lái)替換bug所在方法對(duì)象的屬性來(lái)修復(fù)bug簡(jiǎn)要類(lèi)之間關(guān)系圖修復(fù)的具體過(guò)程為:1)我們及時(shí)修復(fù)好bug之后,我們可以apkpatch工具將兩個(gè)apk做一次對(duì)比,然后找出不同的部分。生成的apatch了文件。若果這個(gè)時(shí)候,我們把后綴改成zip再解壓開(kāi),里面有一個(gè)dex文件。反編譯之后查看一下源碼,里面就是被修復(fù)的代碼所在的類(lèi)文件,這些更改過(guò)的類(lèi)都加上了一個(gè)_CF的后綴,并且變動(dòng)的方法都被加上了一個(gè)叫MethodReplace
6、的annotation,通過(guò)clazz和method指定了需要替換的方法。(后面補(bǔ)丁原理會(huì)說(shuō)到)2)客戶端得到補(bǔ)丁文件后就會(huì)根據(jù)annotation來(lái)尋找需要替換的方法。從AndFixManager.fix方法開(kāi)始,客戶端找到對(duì)應(yīng)的需要替換的方法,然后在fix方法的具體實(shí)現(xiàn)中調(diào)用fixClass方法進(jìn)行方法替換過(guò)程。3)由JNI層完成方法的替換。fixClass方法遍歷補(bǔ)丁class里的方法,在jni層對(duì)所需要替換的方法進(jìn)行一一替換。(AndfixManager#replaceMethod)源碼解析遵循使用時(shí)四步走:Step1:初始化PatchMangerPatchManager patchM
7、anager = new PatchManager();參閱 patchManager類(lèi)源碼>AndfixManager 其中包含了Compat兼容性檢測(cè)類(lèi)、SecurityChecker安全性檢查類(lèi)public AndFixManager(Context context) mContext = context; /判斷機(jī)型是否支持Andfix 阿里的YunOs不支持 mSupport = Compat.isSupport(); if (mSupport) /初始化簽名判斷類(lèi) 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 構(gòu)造函數(shù)初始化 init 主要是版本比對(duì),記錄版本號(hào);根據(jù)版本號(hào)將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);/緩存版本號(hào) String ver = sp.getString(SP_VERSION, null); if (ver = null | !ver.equalsIgnoreCase(appVersion) /根據(jù)傳入版本號(hào)作對(duì)比,若不同,則刪除本地的補(bǔ)丁文件 cleanPatch(); sp.edit().
11、putString(SP_VERSION, appVersion).commit();/傳入新的版本號(hào) else initPatchs();/初始化patch列表,把本地的patch加載到內(nèi)存中 private void initPatchs() File files = mPatchDir.listFiles(); for (File file : files) addPatch(file); Patch文件的加載 使用JarFile讀取Patch文件,讀取一些屬性如patchname,createtime,其中如果本地保存了多個(gè)補(bǔ)丁,那么AndFix會(huì)按照補(bǔ)丁生成的時(shí)間順序加載補(bǔ)丁。具體是
12、根據(jù).apatch文件中的PATCH.MF的字段Created-Time。step3:loadPatchpatchManager.loadPatch();參閱patchManager#loadPatch提供了3個(gè)重載方法public void loadPatch()/andfix 初始化之后調(diào)用 private void loadPatch(Patch patch)/下載補(bǔ)丁完成后調(diào)用,addPatch(path) public void loadPatch(String patchName, ClassLoader classLoader)/提供了自定義類(lèi)加載器的實(shí)現(xiàn) 這三個(gè)核心都是調(diào)用了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; /判斷補(bǔ)丁的簽名 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 / /如果本地已經(jīng)存在補(bǔ)丁文件,則校驗(yàn)指紋
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) /重寫(xiě)了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的類(lèi)文件 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的類(lèi)中的方法都是有注解的 if (methodReplace = null) continue; clz = methodReplace.clazz(); /獲取注解中的clazz的值 meth = methodReplace.method(); /獲取注解中method的值 if (!isEmpty(clz) && !isEmpty(meth) /找到需
21、要替換的方法后調(diào)用replaceMethod替換方法 replaceMethod(classLoader, clz, meth, method); private void replaceMethod(ClassLoader classLoader, String clz, String meth, Method method) try String key = clz + "" + classLoader.toString(); /根據(jù)key查找緩存中的數(shù)據(jù),該緩存記錄了已經(jīng)被修復(fù)過(guò)得class Class<?> clazz = mFixedClass.get
22、(key); if (clazz = null) / class not load /找不到說(shuō)明該class沒(méi)有被修復(fù)過(guò),則通過(guò)類(lèi)加載器去加載 Class<?> clzz = classLoader.loadClass(clz); / initialize target class /通過(guò)C層改寫(xiě)accessFlag,把需要替換的類(lèi)的所有方法(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(); /根據(jù)反射拿到有bug的類(lèi)的方法 /這里是調(diào)用了jni,art和dalvik分別執(zhí)行不同的替換邏輯,在cpp進(jìn)行實(shí)現(xiàn) AndFix.addReplaceMethod(src, method);/替換方法 src是有bug的方法,method是補(bǔ)丁方法 catch (Exception e) Log.e(TAG, "replaceMethod", e); Natvie方法的分析見(jiàn)下面前三步都是一開(kāi)始
24、初始化時(shí)候要做的,而最后一步第四步則是補(bǔ)丁下載好之后再做的step4: 添加PatchpatchManager.addPatch(path)參閱PatchManager#addPatch,最終還是執(zhí)行l(wèi)oadpatchappPatch>copy到andfix默認(rèn)的文件夾下>執(zhí)行l(wèi)oadPatch(補(bǔ)丁立即生效) 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; /這一步很重要,通過(guò)這一步將你所下載保存的patch文件,copy到andfix自己默認(rèn)的文件夾內(nèi)存的data/data/apatch FileUtil.copyFile(src, dest);/ copy to patch's directory Patch patch = addPatch(dest);
26、if (patch != null) /加載patch 補(bǔ)丁立即生效 loadPatch(patch); 小結(jié)一下: 可以看出andfix的核心就是兩大步 - java層 實(shí)現(xiàn)加載補(bǔ)丁文件,安全驗(yàn)證等操作,然后根據(jù)補(bǔ)丁匯總的注解找到將要替換的方法,交給Native層去處理替換方法 - native層:利用Java hook的技術(shù)來(lái)替換要修復(fù)的方法附 Native 分析在JNI目錄下 art和darvik文件中andfix.cpp#replaceMethod>art_method_replace.cpp(根據(jù)版本)art_method_replace_5_0.cppDalvikDalvik
27、是Google公司自己設(shè)計(jì)用于Android平臺(tái)的Java虛擬機(jī)。Dalvik虛擬機(jī)是Google等廠商合作開(kāi)發(fā)的Android移動(dòng)設(shè)備平臺(tái)的核心組成部分之一。它可以支持已轉(zhuǎn)換為 .dex(即Dalvik Executable)格式的Java應(yīng)用程序的運(yùn)行,.dex格式是專(zhuān)為Dalvik設(shè)計(jì)的一種壓縮格式,適合內(nèi)存和處理器速度有限的系統(tǒng)。Dalvik 經(jīng)過(guò)優(yōu)化,允許在有限的內(nèi)存中同時(shí)運(yùn)行多個(gè)虛擬機(jī)的實(shí)例,并且每一個(gè)Dalvik 應(yīng)用作為一個(gè)獨(dú)立的Linux 進(jìn)程執(zhí)行。獨(dú)立的進(jìn)程可以防止在虛擬機(jī)崩潰的時(shí)候所有程序都被關(guān)閉。ARTAndroid操作系統(tǒng)已經(jīng)成熟,Google的Android團(tuán)隊(duì)開(kāi)始
28、將注意力轉(zhuǎn)向一些底層組件,其中之一是負(fù)責(zé)應(yīng)用程序運(yùn)行的Dalvik運(yùn)行時(shí)。Google開(kāi)發(fā)者已經(jīng)花了兩年時(shí)間開(kāi)發(fā)更快執(zhí)行效率更高更省電的替代ART運(yùn)行時(shí)。 ART代表Android Runtime,其處理應(yīng)用程序執(zhí)行的方式完全不同于Dalvik,Dalvik是依靠一個(gè)Just-In-Time (JIT)編譯器去解釋字節(jié)碼。開(kāi)發(fā)者編譯后的應(yīng)用代碼需要通過(guò)一個(gè)解釋器在用戶的設(shè)備上運(yùn)行,這一機(jī)制并不高效,但讓?xiě)?yīng)用能更容易在不同硬件和架構(gòu)上運(yùn) 行。ART則完全改變了這套做法,在應(yīng)用安裝時(shí)就預(yù)編譯字節(jié)碼到機(jī)器語(yǔ)言,這一機(jī)制叫Ahead-Of-Time (AOT)編譯。在移除解釋代碼這一過(guò)程后,應(yīng)用程序執(zhí)
29、行將更有效率,啟動(dòng)更快。-優(yōu)缺點(diǎn)ART優(yōu)點(diǎn):1、系統(tǒng)性能的顯著提升。2、應(yīng)用啟動(dòng)更快、運(yùn)行更快、體驗(yàn)更流暢、觸感反饋更及時(shí)。3、更長(zhǎng)的電池續(xù)航能力。4、支持更低的硬件。ART缺點(diǎn):1、更大的存儲(chǔ)空間占用,可能會(huì)增加10%-20%。2、更長(zhǎng)的應(yīng)用安裝時(shí)間??偟膩?lái)說(shuō)ART的功效就是“空間換時(shí)間”。其他重要函數(shù)PatchManage#removeAllPatch()這個(gè)函數(shù)是在PatchManage#init(viersin) verision不同時(shí)調(diào)用的方法一樣,清空補(bǔ)丁目錄文件,這在做保護(hù)的時(shí)候十分重要。 public void removeAllPatch() cleanPatch(); Sh
30、aredPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); sp.edit().clear().commit();比如在laodPatch,包括初始化的時(shí)候patchManager.loadPatch()和patchManager.addPatch(其實(shí)也是調(diào)用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對(duì)用的class類(lèi)集合 mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(), classes);/核心-修復(fù)bug方法 因此需要在以下兩處做好保護(hù)publi
32、c void starAndfix() try mPatchManager = new PatchManager(context); mPatchManager.init(BuildConfig.VERSION_NAME);/更換版本號(hào),補(bǔ)丁會(huì)被清除 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"); 補(bǔ)丁原理apkPatch工具解析apkpatch是一個(gè)jar包,并沒(méi)有開(kāi)源出來(lái),我們可以使用JD-GUI來(lái)
34、查看其源碼。首先找到Main.class 位于com.euler.patch包下,找到main方法 Main#97public static void main(final String args) . /根據(jù)上面命令輸入拿到參數(shù) 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."); /第一步:拿到兩個(gè)apk文件對(duì)比,對(duì)比信息寫(xiě)入DiffInfo
37、 DiffInfo info = new DexDiffer().diff(this.from, this.to); /第二步:將對(duì)比結(jié)果info寫(xiě)入.smail文件中,然后打包成dex文件 this.classes = buildCode(smaliDir, dexFile, info); /第三步:將生成的dex文件寫(xiě)入jar包,并根據(jù)輸入的簽名信息進(jìn)行簽名生成diff.apatch文件 build(outFile, dexFile); /第四步:將diff.apatch文件重命名 release(this.out, dexFile, outFile); catch (Exception
38、e) e.printStackTrace();代碼翻譯一下:對(duì)比apk文件,得到所需信息將結(jié)果打包為apatch文件主要的就是對(duì)比文件信息的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) /對(duì)比相同的方法,存儲(chǔ)為修改
40、的方法 if (newClazz.equals(oldClazz) /對(duì)比class文件的變量 compareField(newClazz, oldClazz, info); /對(duì)比class的方法,如果同一個(gè)類(lèi)中沒(méi)有相同的方法,則判斷為新增方法(后面方法) compareMethod(newClazz, oldClazz, info); contains = true; break; if (!contains) info.addAddedClasses(newClazz); return info;從這段代碼可以看出dex diff得到兩個(gè)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. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025至2030年中國(guó)鐵蓋圓桶市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 2025至2030年中國(guó)輕型龍門(mén)刨床市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 2025至2030年中國(guó)線圈活頁(yè)本冊(cè)市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 2025至2030年中國(guó)瞬態(tài)電壓抑制二極管市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 2025至2030年中國(guó)瓷質(zhì)外墻磚市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 2025至2030年中國(guó)游泳館管理軟件市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 2025至2030年中國(guó)水晶大樓模型市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 2025至2030年中國(guó)木制穿線繞珠玩具市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 2025至2030年中國(guó)挖斗上料機(jī)市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 2025至2030年中國(guó)平面研磨開(kāi)閥市場(chǎng)分析及競(jìng)爭(zhēng)策略研究報(bào)告
- 拳擊入門(mén)-北京理工大學(xué)中國(guó)大學(xué)mooc課后章節(jié)答案期末考試題庫(kù)2023年
- 中石油職稱英語(yǔ)通用教材
- ICD-10疾病編碼完整版
- 智能客房控制器設(shè)計(jì)
- 滁州瑞芬生物科技有限公司年產(chǎn)1.5萬(wàn)噸赤蘚糖醇項(xiàng)目環(huán)境影響報(bào)告書(shū)
- THMDSXH 003-2023 電商產(chǎn)業(yè)園區(qū)數(shù)字化建設(shè)與管理指南
- 新建ICU鎮(zhèn)痛、鎮(zhèn)靜藥物應(yīng)用幻燈片
- 橡膠和基材的粘接
- GB/T 10610-2009產(chǎn)品幾何技術(shù)規(guī)范(GPS)表面結(jié)構(gòu)輪廓法評(píng)定表面結(jié)構(gòu)的規(guī)則和方法
- GA/T 935-2011法庭科學(xué)槍彈痕跡檢驗(yàn)鑒定文書(shū)編寫(xiě)規(guī)范
- 湖北省黃石市基層診所醫(yī)療機(jī)構(gòu)衛(wèi)生院社區(qū)衛(wèi)生服務(wù)中心村衛(wèi)生室信息
評(píng)論
0/150
提交評(píng)論