版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
AndroidART運(yùn)行時(shí)無縫替換Dalvik虛擬機(jī)的過程分析Android4.4發(fā)布了一個(gè)ART運(yùn)行時(shí),準(zhǔn)備用來替換掉之前一直使用的Dalvik虛擬機(jī),希望籍此解決飽受詬病的性能問題。老羅不打算分析ART的實(shí)現(xiàn)原理,只是很有興趣知道ART是如何無縫替換掉原來的Dalvik虛擬機(jī)的。畢竟在原來的系統(tǒng)中,大量的代碼都是運(yùn)行在Dalvik虛擬機(jī)里面的。開始覺得這個(gè)替換工作是挺復(fù)雜的,但是分析了相關(guān)代碼之后,發(fā)現(xiàn)思路是很清晰的。本文就詳細(xì)分析這個(gè)無縫的替換過程。我們知道,Dalvik虛擬機(jī)實(shí)則也算是一個(gè)Java虛擬機(jī),只不過它執(zhí)行的不是class文件,而是dex文件。因此,ART運(yùn)行時(shí)最理想的方式也是實(shí)現(xiàn)為一個(gè)Java虛擬機(jī)的形式,這樣就可以很容易地將Dalvik虛擬機(jī)替換掉。注意,我們這里說實(shí)現(xiàn)為Java虛擬機(jī)的形式,實(shí)際上是指提供一套完全與Java虛擬機(jī)兼容的接口。例如,Dalvik虛擬機(jī)在接口上與Java虛擬機(jī)是一致的,但是它的內(nèi)部可以是完全不一樣的東西。實(shí)際上,ART運(yùn)行時(shí)就是真的和 Dalvik 虛擬機(jī)一樣,實(shí)現(xiàn)了一套完全兼容 Java虛擬機(jī)的接口。為了方便描述,接下來我們就將 ART運(yùn)行時(shí)稱為 ART虛擬機(jī),它和 Dalvik虛擬機(jī)、Java虛擬機(jī)的關(guān)系如圖 1所示:從圖1可以知道,Dalvik虛擬機(jī)和 ART虛擬機(jī)都實(shí)現(xiàn)了三個(gè)用來抽象 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這三個(gè)接口,供外界調(diào)用。此外,Android 系統(tǒng)還提供了一個(gè)系統(tǒng)屬性 ,它的值要么等于libdvm.so,要么等于libart.so。當(dāng)?shù)扔趌ibdvm.so時(shí),就表示當(dāng)前用的是Dalvik虛擬機(jī),而當(dāng)?shù)扔趌ibart.so時(shí),就表示當(dāng)前用的是ART虛擬機(jī)。以上描述的Dalvik虛擬機(jī)和ART虛擬機(jī)的共同之處,當(dāng)然它們之間最顯著還是不同之處。不同的地方就在于,Dalvik虛擬機(jī)執(zhí)行的是dex字節(jié)碼,ART虛擬機(jī)執(zhí)行的是本地機(jī)器碼。這意味著Dalvik虛擬機(jī)包含有一個(gè)解釋器,用來執(zhí)行dex字節(jié)碼,具體可以參考這個(gè)系列的文章。當(dāng)然,Android從2.2開始,也包含有JIT(Just-In-Time),用來在運(yùn)行時(shí)動(dòng)態(tài)地將執(zhí)行頻率很高的dex字節(jié)碼翻譯成本地機(jī)器碼,然后再執(zhí)行。通過JIT,就可以有效地提高Dalvik虛擬機(jī)的執(zhí)行效率。但是,將dex字節(jié)碼翻譯成本地機(jī)器碼是發(fā)生在應(yīng)用程序的運(yùn)行過程中的,并且應(yīng)用程序每一次重新運(yùn)行的時(shí)候,都要做重做這個(gè)翻譯工作的。因此,即使用采用了JIT,Dalvik虛擬機(jī)的總體性能還是不能與直接執(zhí)行本地機(jī)器碼的ART虛擬機(jī)相比。那么,ART虛擬機(jī)執(zhí)行的本地機(jī)器碼是從哪里來的呢? Android的運(yùn)行時(shí)從 Dalvik虛擬機(jī)替換成 ART虛擬機(jī),并不要求開發(fā)者要將重新將自己的應(yīng)用直接編譯成目標(biāo)機(jī)器碼。也就是說,開發(fā)者開發(fā)出的應(yīng)用程序經(jīng)過編譯和打包之后,仍然是一個(gè)包含 dex字節(jié)碼的APK文件。既然應(yīng)用程序包含的仍然是 dex字節(jié)碼,而 ART虛擬機(jī)需要的是本地機(jī)器碼,這就必然要有一個(gè)翻譯的過程。 這個(gè)翻譯的過程當(dāng)然不能發(fā)生應(yīng)用程序運(yùn)行的時(shí)候, 否則的話就和 Dalvik 虛擬機(jī)的 JIT一樣了。在計(jì)算機(jī)的世界里,與 JIT相對的是 AOT。AOT進(jìn)Ahead-Of-Time的簡稱,它發(fā)生在程序運(yùn)行之前。我們用靜態(tài)語言(例如 C/C++)來開發(fā)應(yīng)用程序的時(shí)候,編譯器直接就把它們翻譯成目標(biāo)機(jī)器碼。這種靜態(tài)語言的編譯方式也是AOT的一種。但是前面我們提到,ART虛擬機(jī)并不要求開發(fā)者將自己的應(yīng)用直接編譯成目標(biāo)機(jī)器碼。這樣,將應(yīng)用的 dex字節(jié)碼翻譯成本地機(jī)器碼的最恰當(dāng) AOT時(shí)機(jī)就發(fā)生在應(yīng)用安裝的時(shí)候。我們知道,沒有 ART虛擬機(jī)之前,應(yīng)用在安裝的過程,其實(shí)也會執(zhí)行一次“翻譯”的過程。只不過這個(gè)“翻譯”的過程是將dex字節(jié)碼進(jìn)行優(yōu)化,也就是由dex文件生成odex文件。這個(gè)過程由安裝服務(wù)PackageManagerService請求守護(hù)進(jìn)程installd來執(zhí)行的。從這個(gè)角度來說,在應(yīng)用安裝的過程中將dex字節(jié)碼翻譯成本地機(jī)器碼對原來的應(yīng)用安裝流程基本上就不會產(chǎn)生什么影響。有了以上的背景知識之后,我們接下來就從兩個(gè)角度來了解 ART虛擬機(jī)是如何做到無縫替換 Dalvik虛擬機(jī)的:ART虛擬機(jī)的啟動(dòng)過程;Dex字節(jié)碼翻譯成本地機(jī)器碼的過程。我們知道,Android系統(tǒng)在啟動(dòng)的時(shí)候, 會創(chuàng)建一個(gè) Zygote進(jìn)程,充當(dāng)應(yīng)用程序進(jìn)程孵化器。Zygote進(jìn)程在啟動(dòng)的過程中,又會創(chuàng)建一個(gè) Dalvik虛擬機(jī)。Zygote進(jìn)程是通過復(fù)制自己來創(chuàng)建新的應(yīng)用程序進(jìn)程的。這意味著 Zygote進(jìn)程會將自己的 Dalvik虛擬機(jī)復(fù)制給應(yīng)用程序進(jìn)程。通過這種方式就可以大大地提高應(yīng)用程序的啟動(dòng)速度, 因?yàn)檫@種方式避免了每一個(gè)應(yīng)用程序進(jìn)程在啟動(dòng)的時(shí)候都要去創(chuàng)建一個(gè) Dalvik。事實(shí)上,Zygote進(jìn)程通過自我復(fù)制的方式來創(chuàng)建應(yīng)用程序進(jìn)程,省去的不僅僅是應(yīng)用程序進(jìn)程創(chuàng)建 Dalvik 虛擬機(jī)的時(shí)間,還能省去應(yīng)用程序進(jìn)程加載各種系統(tǒng)庫和系統(tǒng)資源的時(shí)間,因?yàn)樗鼈冊?Zygote進(jìn)程中已經(jīng)加載過了,并且也會連同 Dalvik虛擬機(jī)一起復(fù)制到應(yīng)用程序進(jìn)程中去。關(guān)于 Zygote進(jìn)程和應(yīng)用程序進(jìn)程啟動(dòng)的更多知識,可以參考和這兩篇文章。即然應(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 這個(gè)函數(shù)開始創(chuàng)建的。因此, 接下來我們就看看這個(gè)函數(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}}......}這個(gè)函數(shù)定義在文件 frameworks/base/core/jni/AndroidRuntime.cpp 中。AndroidRuntime 類的成員函數(shù) start最主要是做了以下三件事情:1.創(chuàng)建一個(gè)JniInvocation實(shí)例,并且調(diào)用它的成員函數(shù)init來初始化JNI環(huán)境;2.調(diào)用AndroidRuntime類的成員函數(shù)startVm來創(chuàng)建一個(gè)虛擬機(jī)及其對應(yīng)的JNI接口,即創(chuàng)建一個(gè) JavaVM接口和一個(gè) 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;}這個(gè)函數(shù)定義在文件 libnativehelper/JniInvocation.cpp 中。JniInvocation 類的成員函數(shù) init 所做的事情很簡單。它首先是讀取系統(tǒng)屬性 的值。前面提到,系統(tǒng)屬性 的值要么等于libdvm.so,要么等于libart.so。因此,接下來通過函數(shù) dlopen加載到進(jìn)程來的要么是 libdvm.so,要么是 libart.so。無論加載的是哪一個(gè) so,都要求它導(dǎo)出 JNI_GetDefaultJavaVMInitArgs 、JNI_CreateJavaVM和JNI_GetCreatedJavaVMs這三個(gè)接口,并且分別保存在 JniInvocation類的 三 個(gè) 成 員 變 量 JNI_GetDefaultJavaVMInitArgs_ 、 JNI_CreateJavaVM_ 和JNI_GetCreatedJavaVMs_中。這三個(gè)接口也就是前面我們提到的用來抽象
Java虛擬機(jī)的三個(gè)接口。從這里就可以看出,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;}......}這個(gè)函數(shù)定義在文件frameworks/base/core/jni/AndroidRuntime.cpp中。AndroidRuntime類的成員函數(shù)startVm最主要就是調(diào)用函數(shù)JNI_CreateJavaVM來創(chuàng)建一個(gè)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);}這個(gè)函數(shù)定義在文件 libnativehelper/JniInvocation.cpp 中。JniInvocation 類的靜態(tài)成員函數(shù) GetJniInvocation 返回的便是前面所創(chuàng)建的JniInvocation 實(shí)例。有了這個(gè) JniInvocation 實(shí)例之后,就繼續(xù)調(diào)用它的成員函數(shù)JNI_CreateJavaVM來創(chuàng)建一個(gè) 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);}這個(gè)函數(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)行時(shí)抽象成一個(gè)Java虛擬機(jī),以及通過系統(tǒng)屬性 和一個(gè)適配層 JniInvocation,就可以無縫地將Dalvik虛擬機(jī)替換為 ART運(yùn)行時(shí)。這個(gè)替換過程設(shè)計(jì)非常巧妙,因?yàn)樯婕暗降拇a修改是非常少的。以上就是ART虛擬機(jī)的啟動(dòng)過程,接下來我們再分析應(yīng)用程序在安裝過程中將dex字節(jié)碼翻譯為本地機(jī)器碼的過程。Android應(yīng)用程序的安裝過程可以參考這篇文章。 簡單來說,就是 Android系統(tǒng)通過PackageManagerService來安裝APK,在安裝的過程,PackageManagerService會通過另外一個(gè)類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());}......}這個(gè)函數(shù)定義在文件frameworks/base/services/java/com/android/server/pm/Installer.java中。Installer通過socket向守護(hù)進(jìn)程installd發(fā)送一個(gè)dexopt請求,這個(gè)請求是由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文件中。這個(gè)函數(shù)定義在文件
frameworks/native/cmds/installd/commands.c
中。函數(shù)
dexopt
首先是讀取系統(tǒng)屬性
的值,接著在/data/dalvik-cache目錄中創(chuàng)建一個(gè) odex出文件。再接下來,函數(shù) dexopt
文件。這個(gè)通過 fork
odex文件就是作為dex文件優(yōu)化后的輸來創(chuàng)建一個(gè)子進(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ī)器碼,并且保存在一個(gè)函數(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)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- java掃雷游戲課程設(shè)計(jì)
- 2025年興業(yè)銀行天津分行校園招聘備考題庫含答案詳解
- 2025西藏昌都瀾滄江投資有限責(zé)任公司招聘1人考試核心題庫及答案解析
- 2025貴州六枝特區(qū)人力資源和社會保障局招聘城鎮(zhèn)公益性崗位2人備考核心題庫及答案解析
- 2025年西藏革吉縣財(cái)政局招聘財(cái)會監(jiān)督人員的備考題庫參考答案詳解
- 2025云南保山隆陽區(qū)紅十字會招聘公益性崗位人員1人筆試重點(diǎn)題庫及答案解析
- 2025年智能倉儲物流信息追溯系統(tǒng)在物流行業(yè)智能客服技術(shù)應(yīng)用可行性報(bào)告
- 2026廣西桂林市恭城瑤族自治縣兵役登記考試備考題庫及答案解析
- 2025年十堰市公安局武當(dāng)山旅游經(jīng)濟(jì)特區(qū)分局招聘輔警備考題庫參考答案詳解
- 2025恒豐銀行南京分行社會招聘29人考試重點(diǎn)題庫及答案解析
- 光谷融媒體中心公開招聘工作人員備考考試試題及答案解析
- 2025下半年貴州遵義市市直事業(yè)單位選調(diào)56人考試筆試備考試題及答案解析
- 門窗合同范本的模板
- 深度解析(2026)《DLT 2121-2020高壓直流輸電換流閥冷卻系統(tǒng)化學(xué)監(jiān)督導(dǎo)則》
- 2025北京日報(bào)社招聘10人參考筆試題庫及答案解析
- 國開機(jī)考 社會調(diào)查研究與方法2025
- MOOC 電子技術(shù)應(yīng)用實(shí)驗(yàn)2(數(shù)字電路綜合實(shí)驗(yàn))-電子科技大學(xué) 中國大學(xué)慕課答案
- 黑布林英語閱讀初一年級16《柳林風(fēng)聲》譯文和答案
- GB/T 216-2003煤中磷的測定方法
- GB/T 16880-1997光掩模缺陷分類和尺寸定義的準(zhǔn)則
- GA 1517-2018金銀珠寶營業(yè)場所安全防范要求
評論
0/150
提交評論