![AndroidART運(yùn)行時無縫替換Dalvik虛擬機(jī)的過程分析資料_第1頁](http://file4.renrendoc.com/view/4a912597daaa388b33bfd4eba3bfac2a/4a912597daaa388b33bfd4eba3bfac2a1.gif)
![AndroidART運(yùn)行時無縫替換Dalvik虛擬機(jī)的過程分析資料_第2頁](http://file4.renrendoc.com/view/4a912597daaa388b33bfd4eba3bfac2a/4a912597daaa388b33bfd4eba3bfac2a2.gif)
![AndroidART運(yùn)行時無縫替換Dalvik虛擬機(jī)的過程分析資料_第3頁](http://file4.renrendoc.com/view/4a912597daaa388b33bfd4eba3bfac2a/4a912597daaa388b33bfd4eba3bfac2a3.gif)
![AndroidART運(yùn)行時無縫替換Dalvik虛擬機(jī)的過程分析資料_第4頁](http://file4.renrendoc.com/view/4a912597daaa388b33bfd4eba3bfac2a/4a912597daaa388b33bfd4eba3bfac2a4.gif)
![AndroidART運(yùn)行時無縫替換Dalvik虛擬機(jī)的過程分析資料_第5頁](http://file4.renrendoc.com/view/4a912597daaa388b33bfd4eba3bfac2a/4a912597daaa388b33bfd4eba3bfac2a5.gif)
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
AndroidART運(yùn)行時無縫替換Dalvik虛擬機(jī)的過程分析Android4.4發(fā)布了一個ART運(yùn)行時,準(zhǔn)備用來替換掉之前一直使用的Dalvik虛擬機(jī),希望籍此解決飽受詬病的性能問題。老羅不打算分析ART的實(shí)現(xiàn)原理,只是很有興趣知道ART是如何無縫替換掉原來的Dalvik虛擬機(jī)的。畢竟在原來的系統(tǒng)中,大量的代碼都是運(yùn)行在Dalvik虛擬機(jī)里面的。開始覺得這個替換工作是挺復(fù)雜的,但是分析了相關(guān)代碼之后,發(fā)現(xiàn)思路是很清晰的。本文就詳細(xì)分析這個無縫的替換過程。我們知道,Dalvik虛擬機(jī)實(shí)則也算是一個Java虛擬機(jī),只不過它執(zhí)行的不是class文件,而是dex文件。因此,ART運(yùn)行時最理想的方式也是實(shí)現(xiàn)為一個Java虛擬機(jī)的形式,這樣就可以很容易地將Dalvik虛擬機(jī)替換掉。注意,我們這里說實(shí)現(xiàn)為Java虛擬機(jī)的形式,實(shí)際上是指提供一套完全與Java虛擬機(jī)兼容的接口。例如,Dalvik虛擬機(jī)在接口上與Java虛擬機(jī)是一致的,但是它的內(nèi)部可以是完全不一樣的東西。實(shí)際上,ART運(yùn)行時就是真的和 Dalvik 虛擬機(jī)一樣,實(shí)現(xiàn)了一套完全兼容 Java虛擬機(jī)的接口。為了方便描述,接下來我們就將 ART運(yùn)行時稱為 ART虛擬機(jī),它和 Dalvik虛擬機(jī)、Java虛擬機(jī)的關(guān)系如圖 1所示:從圖1可以知道,Dalvik虛擬機(jī)和 ART虛擬機(jī)都實(shí)現(xiàn)了三個用來抽象 Java虛擬機(jī)的接口:JNI_GetDefaultJavaVMInitArgs--獲取虛擬機(jī)的默認(rèn)初始化參數(shù)JNI_CreateJavaVM--在進(jìn)程中創(chuàng)建虛擬機(jī)實(shí)例3.JNI_GetCreatedJavaVMs-- 獲取進(jìn)程中創(chuàng)建的虛擬機(jī)實(shí)例在Android系統(tǒng)中,Davik虛擬機(jī)實(shí)現(xiàn)在libdvm.so中,ART虛擬機(jī)實(shí)現(xiàn)在libart.so中。也就是說,libdvm.so和libart.so導(dǎo)出了JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM和JNI_GetCreatedJavaVMs這三個接口,供外界調(diào)用。此外,Android 系統(tǒng)還提供了一個系統(tǒng)屬性 ,它的值要么等于libdvm.so,要么等于libart.so。當(dāng)?shù)扔趌ibdvm.so時,就表示當(dāng)前用的是Dalvik虛擬機(jī),而當(dāng)?shù)扔趌ibart.so時,就表示當(dāng)前用的是ART虛擬機(jī)。以上描述的Dalvik虛擬機(jī)和ART虛擬機(jī)的共同之處,當(dāng)然它們之間最顯著還是不同之處。不同的地方就在于,Dalvik虛擬機(jī)執(zhí)行的是dex字節(jié)碼,ART虛擬機(jī)執(zhí)行的是本地機(jī)器碼。這意味著Dalvik虛擬機(jī)包含有一個解釋器,用來執(zhí)行dex字節(jié)碼,具體可以參考這個系列的文章。當(dāng)然,Android從2.2開始,也包含有JIT(Just-In-Time),用來在運(yùn)行時動態(tài)地將執(zhí)行頻率很高的dex字節(jié)碼翻譯成本地機(jī)器碼,然后再執(zhí)行。通過JIT,就可以有效地提高Dalvik虛擬機(jī)的執(zhí)行效率。但是,將dex字節(jié)碼翻譯成本地機(jī)器碼是發(fā)生在應(yīng)用程序的運(yùn)行過程中的,并且應(yīng)用程序每一次重新運(yùn)行的時候,都要做重做這個翻譯工作的。因此,即使用采用了JIT,Dalvik虛擬機(jī)的總體性能還是不能與直接執(zhí)行本地機(jī)器碼的ART虛擬機(jī)相比。那么,ART虛擬機(jī)執(zhí)行的本地機(jī)器碼是從哪里來的呢? Android的運(yùn)行時從 Dalvik虛擬機(jī)替換成 ART虛擬機(jī),并不要求開發(fā)者要將重新將自己的應(yīng)用直接編譯成目標(biāo)機(jī)器碼。也就是說,開發(fā)者開發(fā)出的應(yīng)用程序經(jīng)過編譯和打包之后,仍然是一個包含 dex字節(jié)碼的APK文件。既然應(yīng)用程序包含的仍然是 dex字節(jié)碼,而 ART虛擬機(jī)需要的是本地機(jī)器碼,這就必然要有一個翻譯的過程。 這個翻譯的過程當(dāng)然不能發(fā)生應(yīng)用程序運(yùn)行的時候, 否則的話就和 Dalvik 虛擬機(jī)的 JIT一樣了。在計算機(jī)的世界里,與 JIT相對的是 AOT。AOT進(jìn)Ahead-Of-Time的簡稱,它發(fā)生在程序運(yùn)行之前。我們用靜態(tài)語言(例如 C/C++)來開發(fā)應(yīng)用程序的時候,編譯器直接就把它們翻譯成目標(biāo)機(jī)器碼。這種靜態(tài)語言的編譯方式也是AOT的一種。但是前面我們提到,ART虛擬機(jī)并不要求開發(fā)者將自己的應(yīng)用直接編譯成目標(biāo)機(jī)器碼。這樣,將應(yīng)用的 dex字節(jié)碼翻譯成本地機(jī)器碼的最恰當(dāng) AOT時機(jī)就發(fā)生在應(yīng)用安裝的時候。我們知道,沒有 ART虛擬機(jī)之前,應(yīng)用在安裝的過程,其實(shí)也會執(zhí)行一次“翻譯”的過程。只不過這個“翻譯”的過程是將dex字節(jié)碼進(jìn)行優(yōu)化,也就是由dex文件生成odex文件。這個過程由安裝服務(wù)PackageManagerService請求守護(hù)進(jìn)程installd來執(zhí)行的。從這個角度來說,在應(yīng)用安裝的過程中將dex字節(jié)碼翻譯成本地機(jī)器碼對原來的應(yīng)用安裝流程基本上就不會產(chǎn)生什么影響。有了以上的背景知識之后,我們接下來就從兩個角度來了解 ART虛擬機(jī)是如何做到無縫替換 Dalvik虛擬機(jī)的:ART虛擬機(jī)的啟動過程;Dex字節(jié)碼翻譯成本地機(jī)器碼的過程。我們知道,Android系統(tǒng)在啟動的時候, 會創(chuàng)建一個 Zygote進(jìn)程,充當(dāng)應(yīng)用程序進(jìn)程孵化器。Zygote進(jìn)程在啟動的過程中,又會創(chuàng)建一個 Dalvik虛擬機(jī)。Zygote進(jìn)程是通過復(fù)制自己來創(chuàng)建新的應(yīng)用程序進(jìn)程的。這意味著 Zygote進(jìn)程會將自己的 Dalvik虛擬機(jī)復(fù)制給應(yīng)用程序進(jìn)程。通過這種方式就可以大大地提高應(yīng)用程序的啟動速度, 因為這種方式避免了每一個應(yīng)用程序進(jìn)程在啟動的時候都要去創(chuàng)建一個 Dalvik。事實(shí)上,Zygote進(jìn)程通過自我復(fù)制的方式來創(chuàng)建應(yīng)用程序進(jìn)程,省去的不僅僅是應(yīng)用程序進(jìn)程創(chuàng)建 Dalvik 虛擬機(jī)的時間,還能省去應(yīng)用程序進(jìn)程加載各種系統(tǒng)庫和系統(tǒng)資源的時間,因為它們在 Zygote進(jìn)程中已經(jīng)加載過了,并且也會連同 Dalvik虛擬機(jī)一起復(fù)制到應(yīng)用程序進(jìn)程中去。關(guān)于 Zygote進(jìn)程和應(yīng)用程序進(jìn)程啟動的更多知識,可以參考和這兩篇文章。即然應(yīng)用程序進(jìn)程里面的 Dalvik虛擬機(jī)都是從 Zygote進(jìn)程中復(fù)制過來的,那么接下來我們就繼續(xù) Zygote進(jìn)程是如何創(chuàng)建 Dalvik虛擬機(jī)的。從這篇文章可以知道, Zygote進(jìn)程中的Dalvik虛擬機(jī)是從 AndroidRuntime::start 這個函數(shù)開始創(chuàng)建的。因此, 接下來我們就看看這個函數(shù)的實(shí)現(xiàn):[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片voidAndroidRuntime::start(constchar*className,constchar*options){....../*startthevirtualmachine*/JniInvocationjni_invocation;jni_invocation.Init(NULL);JNIEnv*env;if(startVm(&mJavaVM,&env)!=0){return;}....../**StartVM. ThisthreadbecomesthemainthreadoftheVM,andwillnotreturnuntiltheVMexits.*/char*slashClassName=toSlashClassName(className);jclassstartClass=env->FindClass(slashClassName);if(startClass==NULL){ALOGE("JavaVMunabletolocateclass'%s'\n",slashClassName);/*keepgoing*/}else{jmethodIDstartMeth=env->GetStaticMethodID(startClass,"main","([Ljava/lang/String;)V");if(startMeth==NULL){ALOGE("JavaVMunabletofindmain()in'%s'\n",className);/*keepgoing*/}else{env->CallStaticVoidMethod(startClass,startMeth,strArray);#if0if(env->ExceptionCheck())threadExitUncaughtException(env);#endif}}......}這個函數(shù)定義在文件 frameworks/base/core/jni/AndroidRuntime.cpp 中。AndroidRuntime 類的成員函數(shù) start最主要是做了以下三件事情:1.創(chuàng)建一個JniInvocation實(shí)例,并且調(diào)用它的成員函數(shù)init來初始化JNI環(huán)境;2.調(diào)用AndroidRuntime類的成員函數(shù)startVm來創(chuàng)建一個虛擬機(jī)及其對應(yīng)的JNI接口,即創(chuàng)建一個 JavaVM接口和一個 JNIEnv接口;有了上述的JavaVM接口和JNIEnv接口之后,就可以在Zygote進(jìn)程中加載指定的class了。其中,第1件事情和第2件事情又是最關(guān)鍵的。因此,接下來我們繼續(xù)分析它們所對應(yīng)的函數(shù)的實(shí)現(xiàn)。JniInvocation類的成員函數(shù) init的實(shí)現(xiàn)如下所示:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片#ifdefHAVE_ANDROID_OSstaticconstchar*kLibraryFallback="libdvm.so";boolJniInvocation::Init(constchar*library){#ifdefHAVE_ANDROID_OSchardefault_library[PROPERTY_V ALUE_MAX];property_get(kLibrarySystemProperty,default_library,kLibraryFallback);#elseconstchar*default_library=kLibraryFallback;#endifif(library==NULL){library=default_library;}handle_=dlopen(library,RTLD_NOW);if(handle_==NULL){if(strcmp(library,kLibraryFallback)==0){//Nothingelsetotry.ALOGE("Failedtodlopen%s:%s",library,dlerror());returnfalse;}Notethatthisisenoughtogetsomethinglikethezygoterunning,wecan'tproperty_setheretofixthisforthefuturebecausewearerootandnotthesystemuser.SeeRuntimeImonInitforwherewefixupthepropertytoavoidfuturefallbacks.http://b/11463182ALOGW("Fallingbackfrom%sto%safterdlopenerror:%s",library,kLibraryFallback,dlerror());library=kLibraryFallback;handle_=dlopen(library,RTLD_NOW);if(handle_==NULL){ALOGE("Failedtodlopen%s:%s",library,dlerror());returnfalse;}}if(!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),"JNI_GetDefaultJavaVMInitArgs")){returnfalse;}if(!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),"JNI_CreateJavaVM")){returnfalse;}if(!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),"JNI_GetCreatedJavaVMs")){returnfalse;}returntrue;}這個函數(shù)定義在文件 libnativehelper/JniInvocation.cpp 中。JniInvocation 類的成員函數(shù) init 所做的事情很簡單。它首先是讀取系統(tǒng)屬性 的值。前面提到,系統(tǒng)屬性 的值要么等于libdvm.so,要么等于libart.so。因此,接下來通過函數(shù) dlopen加載到進(jìn)程來的要么是 libdvm.so,要么是 libart.so。無論加載的是哪一個 so,都要求它導(dǎo)出 JNI_GetDefaultJavaVMInitArgs 、JNI_CreateJavaVM和JNI_GetCreatedJavaVMs這三個接口,并且分別保存在 JniInvocation類的 三 個 成 員 變 量 JNI_GetDefaultJavaVMInitArgs_ 、 JNI_CreateJavaVM_ 和JNI_GetCreatedJavaVMs_中。這三個接口也就是前面我們提到的用來抽象
Java虛擬機(jī)的三個接口。從這里就可以看出,JniInvocation類的成員函數(shù)init實(shí)際上就是根據(jù)系統(tǒng)屬性來初始化Dalvik虛擬機(jī)或者ART虛擬機(jī)環(huán)境。接下來我們繼續(xù)看 AndroidRuntime類的成員函數(shù) startVm的實(shí)現(xiàn):[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片intAndroidRuntime::startVm(JavaVM**pJavaVM,JNIEnv**pEnv){....../*InitializetheVM.TheJavaVM*isessentiallyper-process,andtheJNIEnv*isper-thread.Ifthiscallsucceeds,theVMisready,andwecanstartissuingJNIcalls.*/if(JNI_CreateJavaVM(pJavaVM,pEnv,&initArgs)<0){ALOGE("JNI_CreateJavaVMfailed\n");gotobail;}......}這個函數(shù)定義在文件frameworks/base/core/jni/AndroidRuntime.cpp中。AndroidRuntime類的成員函數(shù)startVm最主要就是調(diào)用函數(shù)JNI_CreateJavaVM來創(chuàng)建一個JavaVM接口及其對應(yīng)的 JNIEnv接口:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片extern"C"jintJNI_CreateJavaVM(JavaVM**p_vm,JNIEnv**p_env,void*vm_args){returnJniInvocation::GetJniInvocation().JNI_CreateJavaVM(p_vm,p_env,vm_args);}這個函數(shù)定義在文件 libnativehelper/JniInvocation.cpp 中。JniInvocation 類的靜態(tài)成員函數(shù) GetJniInvocation 返回的便是前面所創(chuàng)建的JniInvocation 實(shí)例。有了這個 JniInvocation 實(shí)例之后,就繼續(xù)調(diào)用它的成員函數(shù)JNI_CreateJavaVM來創(chuàng)建一個 JavaVM接口及其對應(yīng)的 JNIEnv接口:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片jintJniInvocation::JNI_CreateJavaVM(JavaVM**p_vm,JNIEnv**p_env,void*vm_args){returnJNI_CreateJavaVM_(p_vm,p_env,vm_args);}這個函數(shù)定義在文件 libnativehelper/JniInvocation.cpp 中。JniInvocation類的成員變量 JNI_CreateJavaVM_指向的就是前面所加載的 libdvm.so或者 libart.so 所導(dǎo)出的函數(shù) JNI_CreateJavaVM,因此,JniInvocation 類的成員函數(shù)JNI_CreateJavaVM返回的JavaVM接口指向的要么是 Dalvik虛擬機(jī),要么是 ART虛擬機(jī)。通過上面的分析,我們就很容易知道, Android系統(tǒng)通過將 ART運(yùn)行時抽象成一個Java虛擬機(jī),以及通過系統(tǒng)屬性 和一個適配層 JniInvocation,就可以無縫地將Dalvik虛擬機(jī)替換為 ART運(yùn)行時。這個替換過程設(shè)計非常巧妙,因為涉及到的代碼修改是非常少的。以上就是ART虛擬機(jī)的啟動過程,接下來我們再分析應(yīng)用程序在安裝過程中將dex字節(jié)碼翻譯為本地機(jī)器碼的過程。Android應(yīng)用程序的安裝過程可以參考這篇文章。 簡單來說,就是 Android系統(tǒng)通過PackageManagerService來安裝APK,在安裝的過程,PackageManagerService會通過另外一個類Installer的成員函數(shù)dexopt來對APK里面的dex字節(jié)碼進(jìn)行優(yōu)化:[java]viewplaincopy在CODE上查看代碼片派生到我的代碼片publicfinalclassInstaller{......publicintdexopt(StringapkPath,intuid,booleanisPublic){StringBuilderbuilder=newStringBuilder("dexopt");builder.append(apkPath);builder.append('');builder.append(uid);builder.append(isPublic?"1":"0");returnexecute(builder.toString());}......}這個函數(shù)定義在文件frameworks/base/services/java/com/android/server/pm/Installer.java中。Installer通過socket向守護(hù)進(jìn)程installd發(fā)送一個dexopt請求,這個請求是由installd里面的函數(shù)dexopt來處理的:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片intdexopt(constchar*apk_path,uid_tuid,intis_public){structutimbufut;structstatapk_stat,dex_stat;charout_path[PKG_PATH_MAX];chardexopt_flags[PROPERTY_VALUE_MAX];charpersist_sys_dalvik_vm_lib[PROPERTY_VALUE_MAX];char*end;intres,zip_fd=-1,out_fd=-1;....../*Beforeanythingelse:istherea.odexfile? Ifso,wehaveprecompiledtheapkandthereisnothingtodohere.*/sprintf(out_path,"%s%s",apk_path,".odex");if(stat(out_path,&dex_stat)==0){return0;}if(create_cache_path(out_path,apk_path)){return-1;}......out_fd=open(out_path,O_RDWR|O_CREAT|O_EXCL,0644);......pid_tpid;pid=fork();if(pid==0){......if(strncmp(persist_sys_dalvik_vm_lib,"libdvm",6)==0){run_dexopt(zip_fd,out_fd,apk_path,out_path,dexopt_flags);}elseif(strncmp(persist_sys_dalvik_vm_lib,"libart",6)==0){run_dex2oat(zip_fd,out_fd,apk_path,out_path,dexopt_flags);}else{exit(69);
}exit(68);
/*onlygethereonexecfailure*/}......}的實(shí)現(xiàn)如下所示:oat文件中。這個函數(shù)定義在文件
frameworks/native/cmds/installd/commands.c
中。函數(shù)
dexopt
首先是讀取系統(tǒng)屬性
的值,接著在/data/dalvik-cache目錄中創(chuàng)建一個 odex出文件。再接下來,函數(shù) dexopt
文件。這個通過 fork
odex文件就是作為dex文件優(yōu)化后的輸來創(chuàng)建一個子進(jìn)程。如果系統(tǒng)屬性 的值等于 libdvm.so,那么該子進(jìn)程就會調(diào)用函數(shù) run_dexopt來將dex文件優(yōu)化成 odex文件。另一方面,如果系統(tǒng)屬性 的值等于 libart.so,那么該子進(jìn)程就會調(diào)用函數(shù) run_dex2oat來將dex文件翻譯成 oat文件,實(shí)際上就是將 dex字節(jié)碼翻譯成本地機(jī)器碼,并且保存在一個函數(shù)run_dexopt和run_dex2oat[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片staticvoidrun_dexopt(intzip_fd,intodex_fd,constchar*input_file_name,constchar*output_file_name,constchar*dexopt_flags){staticconstchar*DEX_OPT_BIN="/system/bin/dexopt";staticconstintMAX_INT_LEN=12;//'-'+10dig+'\0'-OR-0x+8digcharzip_num[MAX_INT_LEN];charodex_num[MAX_INT_LEN];sprintf(zip_num,"%d",zip_fd);sprintf(odex_num,"%d",odex_fd);ALOGV("Running%sin=%sout=%s\n",DEX_OPT_BIN,input_file_name,output_file_name);execl(DEX_OPT_BIN,DEX_OPT_BIN,"--zip",zip_num,odex_num,input_file_name,dexopt_flags,(char*)NULL);ALOGE("execl(%s)failed:%s\n",DEX_OPT_BIN,strerror(errno));}staticvoidrun_dex2oat(intzip_fd,intoat_fd,constchar*input_file_name,constchar*output_file_name,constchar*dexopt_flags){staticconstchar*DEX2OAT_BIN="/system/bin/dex2oat";staticconstintMAX_INT_LEN=12; //'-'+10dig+'\0'-OR-0x+8digcharzip_fd_arg[strlen("--zip-fd=")+MAX_INT_LEN];charzip_location_arg[strlen("--zip-location=")+PKG_PATH_MAX];charoat_fd_arg[strlen("--oat-fd=")+MAX_INT_LEN];charoat_location_arg[strlen("--oat-name=")+PKG_PATH_MAX];sprintf(zip_fd_arg,"--zip-fd=%d",zip_fd);sprintf(zip_location_arg,"--zip-location=%s",input_file_name);sprintf(oat_fd_arg,"--o
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 網(wǎng)絡(luò)布線及設(shè)備采購合同范本
- 安全協(xié)議書范本及員工責(zé)任書
- 滬科版數(shù)學(xué)九年級上冊22.3《相似三角形的性質(zhì)》聽評課記錄1
- 二零二五年度校園消毒防疫應(yīng)急預(yù)案合同
- 北師大版歷史七年級上冊第19課《北方的民族匯聚》聽課評課記錄
- 2025年子女撫養(yǎng)權(quán)變更法律援助與協(xié)議書模板
- 2025年度醫(yī)療事故快速調(diào)解專項協(xié)議
- 二零二五年度倉儲物流租賃合同電子版模板即點(diǎn)即用
- 二零二五年度解除終止勞動合同后員工社會保險接續(xù)協(xié)議
- 2025年度鐵礦石加工與國際貿(mào)易金融服務(wù)合同
- 無人機(jī)航拍技術(shù)理論考核試題題庫及答案
- T∕CMATB 9002-2021 兒童肉類制品通用要求
- 工序勞務(wù)分包管理課件
- 暖通空調(diào)(陸亞俊編)課件
- 工藝評審報告
- 中國滑雪運(yùn)動安全規(guī)范
- 畢業(yè)論文-基于51單片機(jī)的智能LED照明燈的設(shè)計
- 酒廠食品召回制度
- 中職數(shù)學(xué)基礎(chǔ)模塊上冊第一章《集合》單元檢測試習(xí)題及參考答案
- 化學(xué)魯科版必修一期末復(fù)習(xí)98頁P(yáng)PT課件
- 《農(nóng)產(chǎn)品質(zhì)量安全檢測》PPT課件
評論
0/150
提交評論