標籤:
寫在前面:
首先當我們執行升級指令碼updater-script的時候,就表示我們已經進入了升級安裝狀態。那麼在我們就從實際的安裝作為入口開始分析。也就是說我們從install.cpp中的install_package函數開始一步步來分析。
這裡主要分析與指令碼相關的部分,其他的請參考這位朋友的博文http://blog.chinaunix.net/uid-22028566-id-3533856.html,我也很受啟發。這裡也借用一張圖來協助流程上的分析。
下面是調用的流程:install_package()-->really_install_package(),那麼在really_install_package()函數中真正開始安裝的邏輯如下:
/* Verify and install the contents of the package. */ ui->Print("Installing update...\n"); err = try_update_binary(path, &zip, wipe_cache); if(err != INSTALL_SUCCESS) return err; try_update_binary是真正實現讀取升級包中的指令檔並執行相應的函數。在此函數中,通過調用fork函數建立出一個子進程,在子進程中開始讀取並執行升級指令檔。在此需要注意的是函數fork的用法,fork被調用一次,將做兩次返回,在父進程中返回的是子進程的進程ID,為正數;而在子進程中,則返回0。子進程中所進行的操作,即execv(binary, args)。子進程建立成功後,開始執行升級代碼,並通過管道與父進程互動(建立管道,並將pipefd[1]作為參數傳遞給子進程,子進程則將相關資訊寫入到此管道描述符中);而父進程則通過讀取子進程傳遞過來的資訊更新UI。
那麼下面我們來具體來分析一下這個方法具體的邏輯。
static inttry_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) { //定義一個常量用來封裝尋找相zip關資訊的結果如“META-INF/com/google/android/update-binary”,便於後面進行解壓,因為之前mzOpenZipArchive函數並沒有對更新包進行解壓操作,*zip是之前mzOpenZipArchive方法返回的一個ZipArchive對象。mzOpenZipArchive是開啟升級包,並將相關的資訊拷貝到一個臨時的ZipArchinve變數中。//ASSUMED_UPDATE_BINARY_NAME="META-INF/com/google/android/update-binary" const ZipEntry* binary_entry = mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); if (binary_entry == NULL) { mzCloseZipArchive(zip); return INSTALL_CORRUPT; } const char* binary = "/tmp/update_binary"; unlink(binary);//建立“/tmp/update_binary”檔案 int fd = creat(binary, 0755); if (fd < 0) { mzCloseZipArchive(zip); LOGE("Can't make %s\n", binary); return INSTALL_ERROR; }//將META-INF/com/google/android/update-binary中的內容解壓縮到"/tmp/update_binary"下面。其實這個方法中主要是對update_binary操作的,如解壓和執行等。 bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); close(fd); mzCloseZipArchive(zip); if (!ok) { LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME); return INSTALL_ERROR; } int pipefd[2];//建立管道,用於下面的子進程和父進程之間的通訊 pipe(pipefd); // When executing the update binary contained in the package, the // arguments passed are: // // - the version number for this interface // // - an fd to which the program can write in order to update the // progress bar. The program can write single-line commands: // // progress <frac> <secs> // fill up the next <frac> part of of the progress bar // over <secs> seconds. If <secs> is zero, use // set_progress commands to manually control the // progress of this segment of the bar // // set_progress <frac> // <frac> should be between 0.0 and 1.0; sets the // progress bar within the segment defined by the most // recent progress command. // // firmware <"hboot"|"radio"> <filename> // arrange to install the contents of <filename> in the // given partition on reboot. // // (API v2: <filename> may start with "PACKAGE:" to // indicate taking a file from the OTA package.) // // (API v3: this command no longer exists.) // // ui_print <string> // display <string> on the screen. // // - the name of the package zip file. // const char** args = (const char**)malloc(sizeof(char*) * 5); args[0] = binary; args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk char* temp = (char*)malloc(10); sprintf(temp, "%d", pipefd[1]); args[2] = temp; args[3] = (char*)path; args[4] = NULL;//建立子進程。其中的子進程主要負責執行binary(execv(binary,args),即執行我們的安裝命令指令碼),父進程負責接受子進程發送的命令去更新ui顯示(顯示當前的進度)。子父處理序間通訊依靠管道。 pid_t pid = fork(); if (pid == 0) { close(pipefd[0]);//執行<span style="font-family: Arial, Helvetica, sans-serif;">update-binary</span><span style="font-family: Arial, Helvetica, sans-serif;">,這個程式的實質就是去解析update.zip包中的updater-script指令碼中的命令並執行。由此,Recovery服務就進入了實際安裝update.zip包的過程。</span><span style="font-family: Arial, Helvetica, sans-serif;">在執行execv函數時,execv會停止執行當前的進程,並且以progname應用進程替換被停止執行的進程,進程ID沒有改變。這裡的progname就是指</span><span style="font-family: Arial, Helvetica, sans-serif;">update-binary</span><span style="font-family: Arial, Helvetica, sans-serif;">,也就是被執行的應用程式。第二個參數argv,是指運行</span><span style="font-family: Arial, Helvetica, sans-serif;">update-binary</span><span style="font-family: Arial, Helvetica, sans-serif;">t時,傳遞給執行程式的參數列表, 注意,這個數組的第一個參數應該是應用程式名稱字本身,並且最後一個參數應該為NULL,不參將多個參數合并為一個參數放入數組。</span><span style="font-family: Arial, Helvetica, sans-serif;"></span>此外,如果應用程式正常執行完畢,那麼execv是永遠不會返回的;當execv在調用進程中返回時,那麼這個應用程式應該出錯了(可能是程式本身沒找到,許可權不夠...),此時它的傳回值應該是-1,具體的錯誤碼可以通過全域變數errno查看,還可以通過stderr得到具體的錯誤描述字串。 execv(binary, (char* const*)args);//其實我的理解就是<span style="font-family: Arial, Helvetica, sans-serif;">update-binary相當一個exe可執行程式,這裡就是一個雙擊可執行程式的動作</span> fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno)); _exit(-1); } close(pipefd[1]); *wipe_cache = 0; char buffer[1024]; FILE* from_child = fdopen(pipefd[0], "r"); while (fgets(buffer, sizeof(buffer), from_child) != NULL) { char* command = strtok(buffer, " \n"); if (command == NULL) { continue; } else if (strcmp(command, "progress") == 0) { char* fraction_s = strtok(NULL, " \n"); char* seconds_s = strtok(NULL, " \n"); float fraction = strtof(fraction_s, NULL); int seconds = strtol(seconds_s, NULL, 10); ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds); } else if (strcmp(command, "set_progress") == 0) { char* fraction_s = strtok(NULL, " \n"); float fraction = strtof(fraction_s, NULL); ui->SetProgress(fraction); } else if (strcmp(command, "ui_print") == 0) { char* str = strtok(NULL, "\n"); if (str) { ui->Print("%s", str); } else { ui->Print("\n"); } fflush(stdout); } else if (strcmp(command, "wipe_cache") == 0) { *wipe_cache = 1;#if 1 //wschen 2012-07-25 } else if (strcmp(command, "special_factory_reset") == 0) { *wipe_cache = 2;#endif } else if (strcmp(command, "clear_display") == 0) { ui->SetBackground(RecoveryUI::NONE); } else { LOGE("unknown command [%s]\n", command); } } fclose(from_child); int status; waitpid(pid, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); return INSTALL_ERROR; }#ifdef SUPPORT_DATA_BACKUP_RESTORE //wschen 2011-03-09 //Skip userdata restore if updating from /data with no /data layout changeif(!usrdata_changed && update_from_data){ ui->Print("/data offset remains the same no need to restore usrdata\n");}else{ if (part_size_changed) { if (ensure_path_mounted("/sdcard") != 0) { LOGE("Can't mount %s\n", path); return INSTALL_NO_SDCARD; } if (userdata_restore(backup_path, 1)) { return INSTALL_FILE_SYSTEM_ERROR; } }}#endif //SUPPORT_DATA_BACKUP_RESTORE /* ----------------------------- */ /* SECURE BOOT UPDATE */ /* ----------------------------- */ #ifdef SUPPORT_SBOOT_UPDATE sec_update(false);#endif return INSTALL_SUCCESS;}
Android OTA升級包製作指令碼詳解(五,升級指令碼updater-script的執行<1>)