android系统进行升级的时候,有两种途径,一种是通过接口传递升级包路径自动升级(android系统sd卡升级),升级完之后系统自动重启;另一种是手动进入recovery模式下,选择升级包进行升级,升级完成之后停留在recovery界面,需要手动选择重启。前者多用于手机厂商的客户端在线升级,后者多用于开发和测试人员。但不管哪种,原理都是一样的,都要在recovery模式下进行升级。
1、获取升级包,可以从服务端下载,也可以直接拷贝到sd卡中
2、获取升级包路径,验证签名,通过installpackage接口升级
3、系统重启进入recovery模式
4、在install.cpp进行升级操作
5、try_update_binary执行升级脚本
6、finish_recovery,重启
一、获取升级包,可以从服务端下载,也可以直接拷贝到sd卡中
假设sd卡中已有升级包update.zip
二、获取升级包路径,验证签名,通过installpackage接口升级
1、调用recoverysystem类提供的verifypackage方法进行签名验证
publicstaticvoidverifypackage(filepackagefile,progresslistenerlistener,filedevicecertszipfile)throwsioexception,generalsecurityexception
签名验证函数,实现过程就不贴出来了,参数,
packagefile--升级文件
listener--进度监督器
devicecertszipfile--签名文件,如果为空,则使用系统默认的签名
只有当签名验证正确才返回,否则将抛出异常。
在recovery模式下进行升级时候也是会进行签名验证的,如果这里先不进行验证也不会有什么问题。但是我们建议在重启前,先验证,以便及早发现问题。
如果签名验证没有问题,就执行installpackage开始升级。
2、installpackage开始升级
如果签名验证没有问题,就进行重启升级,
publicstaticvoidinstallpackage(contextcontext,filepackagefile)throwsioexception{stringfilename=packagefile.getcanonicalpath();log.w(tag,"!!!rebootingtoinstall"+filename+"!!!");finalstringfilenamearg="--update_package="+filename;finalstringlocalearg="--locale="+locale.getdefault().tostring();bootcommand(context,filenamearg,localearg);}
这里定义了两个参数,我们接着看,
privatestaticvoidbootcommand(contextcontext,string...args)throwsioexception{recovery_dir.mkdirs();//incaseweneeditcommand_file.delete();//incaseit'snotwritablelog_file.delete();filewritercommand=newfilewriter(command_file);try{for(stringarg:args){if(!textutils.isempty(arg)){command.write(arg);command.write("\n");}}}finally{command.close();}//havingwrittenthecommandfile,goaheadandrebootpowermanagerpm=(powermanager)context.getsystemservice(context.power_service);pm.reboot(powermanager.reboot_recovery);thrownewioexception("rebootfailed(nopermissions?)");}
创建目录/cache/recovery/,command文件保存在该目录下;如果存在command文件,将其删除;然后将上面一步生成的两个参数写入到command文件。
最后重启设备,重启过程就不再详述了。
三、系统重启进入recovery模式
系统重启时会判断/cache/recovery目录下是否有command文件,如果存在就进入recovery模式,否则就正常启动。
进入到recovery模式下,将执行recovery.cpp的main函数,下面贴出关键代码片段,
intarg;while((arg=getopt_long(argc,argv,"",options,null))!=-1){switch(arg){case's':send_intent=optarg;break;case'u':update_package=optarg;break;case'w':wipe_data=wipe_cache=1;break;case'c':wipe_cache=1;break;case't':show_text=1;break;case'x':just_exit=true;break;case'l':locale=optarg;break;case'g':{if(stage==null||*stage=='\0'){charbuffer[20]="1/";strncat(buffer,optarg,sizeof(buffer)-3);stage=strdup(buffer);}break;}case'p':shutdown_after=true;break;case'r':reason=optarg;break;case'?':loge("invalidcommandargument\n");continue;}}
这是一个while循环,用来读取recovery的command参数,options的不同选项定义如下,
staticconststructoptionoptions[]={{"send_intent",required_argument,null,'s'},{"update_package",required_argument,null,'u'},{"wipe_data",no_argument,null,'w'},{"wipe_cache",no_argument,null,'c'},{"show_text",no_argument,null,'t'},{"just_exit",no_argument,null,'x'},{"locale",required_argument,null,'l'},{"stages",required_argument,null,'g'},{"shutdown_after",no_argument,null,'p'},{"reason",required_argument,null,'r'},{null,0,null,0},};
显然,根据第二步写入的命令文件内容,将为update_package赋值。
接着看,
if(update_package){//forbackwardscompatibilityonthecachepartitiononly,if//we'regivenanold'root'path"cache:foo",changeitto//"/cache/foo".if(strncmp(update_package,"cache:",6)==0){intlen=strlen(update_package)+10;char*modified_path=(char*)malloc(len);strlcpy(modified_path,"/cache/",len);strlcat(modified_path,update_package+6,len);printf("(replacingpath\"%s\"with\"%s\")\n",update_package,modified_path);update_package=modified_path;}}
兼容性处理。
intstatus=install_success;if(update_package!=null){status=install_package(update_package,&wipe_cache,temporary_install_file,true);if(status==install_success&&wipe_cache){if(erase_volume("/cache")){loge("cachewipe(requestedbypackage)failed.");}}if(status!=install_success){ui->print("installationaborted.\n");//ifthisisanengoruserdebugbuild,thenautomatically//turnthetextdisplayonifthescriptfailssotheerror//messageisvisible.charbuffer[property_value_max+1];property_get("ro.build.fingerprint",buffer,"");if(strstr(buffer,":userdebug/")||strstr(buffer,":eng/")){ui->showtext(true);}}}elseif(wipe_data){if(device->wipedata())status=install_error;if(erase_volume("/data"))status=install_error;if(wipe_cache&&erase_volume("/cache"))status=install_error;if(erase_persistent_partition()==-1)status=install_error;if(status!=install_success)ui->print("datawipefailed.\n");}elseif(wipe_cache){if(wipe_cache&&erase_volume("/cache"))status=install_error;if(status!=install_success)ui->print("cachewipefailed.\n");}elseif(!just_exit){status=install_none;//nocommandspecifiedui->setbackground(recoveryui::no_command);}
update_package不为空,执行install_package方法。
我们也可以看到擦除数据、缓存的实现也是在这个里执行的,这里就不展开了。
四、在install.cpp进行升级操作
具体的升级过程都是在install.cpp中执行的,先看install_package方法,
intinstall_package(constchar*path,int*wipe_cache,constchar*install_file,boolneeds_mount){file*install_log=fopen_path(install_file,"w");if(install_log){fputs(path,install_log);fputc('\n',install_log);}else{loge("failedtoopenlast_install:%s\n",strerror(errno));}intresult;if(setup_install_mounts()!=0){loge("failedtosetupexpectedmountsforinstall;aborting\n");result=install_error;}else{result=really_install_package(path,wipe_cache,needs_mount);}if(install_log){fputc(result==install_success?'1':'0',install_log);fputc('\n',install_log);fclose(install_log);}returnresult;}
这个方法中首先创建了log文件,升级过程包括出错的信息都会写到这个文件中,便于后续的分析工作。继续跟进,really_install_package,
staticintreally_install_package(constchar*path,int*wipe_cache,boolneeds_mount){ui->setbackground(recoveryui::installing_update);ui->print("findingupdatepackage...\n");//giveverificationhalftheprogressbar...ui->setprogresstype(recoveryui::determinate);ui->showprogress(verification_progress_fraction,verification_progress_time);logi("updatelocation:%s\n",path);//maptheupdatepackageintomemory.ui->print("openingupdatepackage...\n");if(path&&needs_mount){if(path[0]=='@'){ensure_path_mounted(path+1);}else{ensure_path_mounted(path);}}memmappingmap;if(sysmapfile(path,&map)!=0){loge("failedtomapfile\n");returninstall_corrupt;}//装入签名文件intnumkeys;certificate*loadedkeys=load_keys(public_keys_file,&numkeys);if(loadedkeys==null){loge("failedtoloadkeys\n");returninstall_corrupt;}logi("%dkey(s)loadedfrom%s\n",numkeys,public_keys_file);ui->print("verifyingupdatepackage...\n");//验证签名interr;err=verify_file(map.addr,map.length,loadedkeys,numkeys);free(loadedkeys);logi("verify_filereturned%d\n",err);//签名失败的处理if(err!=verify_success){loge("signatureverificationfailed\n");sysreleasemap(&map);returninstall_corrupt;}/*trytoopenthepackage.*///打开升级包ziparchivezip;err=mzopenziparchive(map.addr,map.length,&zip);if(err!=0){loge("can'topen%s\n(%s)\n",path,err!=-1?strerror(err):"bad");sysreleasemap(&map);returninstall_corrupt;}/*verifyandinstallthecontentsofthepackage.*/ui->print("installingupdate...\n");ui->setenablereboot(false);//执行升级脚本文件,开始升级intresult=try_update_binary(path,&zip,wipe_cache);ui->setenablereboot(true);ui->print("\n");sysreleasemap(&map);returnresult;}
该方法主要做了三件事
1、验证签名
intnumkeys;certificate*loadedkeys=load_keys(public_keys_file,&numkeys);if(loadedkeys==null){loge("failedtoloadkeys\n");returninstall_corrupt;}
装载签名文件,如果为空,终止升级;
interr;err=verify_file(map.addr,map.length,loadedkeys,numkeys);free(loadedkeys);logi("verify_filereturned%d\n",err);//签名失败的处理if(err!=verify_success){loge("signatureverificationfailed\n");sysreleasemap(&map);returninstall_corrupt;}
调用verify_file进行签名验证,这个方法定义在verifier.cpp文件中,此处不展开,如果验证失败立即终止升级。
2、读取升级包信息
ziparchivezip;err=mzopenziparchive(map.addr,map.length,&zip);if(err!=0){loge("can'topen%s\n(%s)\n",path,err!=-1?strerror(err):"bad");sysreleasemap(&map);returninstall_corrupt;}
执行mzopenziparchive方法,打开升级包并扫描,将包的内容拷贝到变量zip中,该变量将作为参数用来执行升级脚本。
3、执行升级脚本文件,开始升级
intresult=try_update_binary(path,&zip,wipe_cache);
try_update_binary方法用来处理升级包,执行制作升级包中的脚本文件update_binary,进行系统更新。
五、try_update_binary执行升级脚本
//ifthepackagecontainsanupdatebinary,extractitandrunit.staticinttry_update_binary(constchar*path,ziparchive*zip,int*wipe_cache){//检查update-binary是否存在constzipentry*binary_entry=mzfindzipentry(zip,assumed_update_binary_name);if(binary_entry==null){mzcloseziparchive(zip);returninstall_corrupt;}constchar*binary="/tmp/update_binary";unlink(binary);intfd=creat(binary,0755);if(fd<0){mzcloseziparchive(zip);loge("can'tmake%s\n",binary);returninstall_error;}//update-binary拷贝到"/tmp/update_binary"boolok=mzextractzipentrytofile(zip,binary_entry,fd);close(fd);mzcloseziparchive(zip);if(!ok){loge("can'tcopy%s\n",assumed_update_binary_name);returninstall_error;}//创建管道,用于下面的子进程和父进程之间的通信intpipefd[2];pipe(pipefd);//whenexecutingtheupdatebinarycontainedinthepackage,the//argumentspassedare:////-theversionnumberforthisinterface////-anfdtowhichtheprogramcanwriteinordertoupdatethe//progressbar.theprogramcanwritesingle-linecommands:////progress<frac><secs>//fillupthenext<frac>partofoftheprogressbar//over<secs>seconds.if<secs>iszero,use//set_progresscommandstomanuallycontrolthe//progressofthissegmentofthebar////set_progress<frac>//<frac>shouldbebetween0.0and1.0;setsthe//progressbarwithinthesegmentdefinedbythemost//recentprogresscommand.////firmware<"hboot"|"radio"><filename>//arrangetoinstallthecontentsof<filename>inthe//givenpartitiononreboot.////(apiv2:<filename>maystartwith"package:"to//indicatetakingafilefromtheotapackage.)////(apiv3:thiscommandnolongerexists.)////ui_print<string>//display<string>onthescreen.////-thenameofthepackagezipfile.//constchar**args=(constchar**)malloc(sizeof(char*)*5);args[0]=binary;args[1]=expand(recovery_api_version);//definedinandroid.mkchar*temp=(char*)malloc(10);sprintf(temp,"%d",pipefd[1]);args[2]=temp;args[3]=(char*)path;args[4]=null;//创建子进程。负责执行binary脚本pid_tpid=fork();if(pid==0){umask(022);close(pipefd[0]);execv(binary,(char*const*)args);//执行binary脚本fprintf(stdout,"e:can'trun%s(%s)\n",binary,strerror(errno));_exit(-1);}close(pipefd[1]);*wipe_cache=0;//父进程负责接受子进程发送的命令去更新ui显示charbuffer[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;}elseif(strcmp(command,"progress")==0){char*fraction_s=strtok(null,"\n");char*seconds_s=strtok(null,"\n");floatfraction=strtof(fraction_s,null);intseconds=strtol(seconds_s,null,10);ui->showprogress(fraction*(1-verification_progress_fraction),seconds);}elseif(strcmp(command,"set_progress")==0){char*fraction_s=strtok(null,"\n");floatfraction=strtof(fraction_s,null);ui->setprogress(fraction);}elseif(strcmp(command,"ui_print")==0){char*str=strtok(null,"\n");if(str){ui->print("%s",str);}else{ui->print("\n");}fflush(stdout);}elseif(strcmp(command,"wipe_cache")==0){*wipe_cache=1;}elseif(strcmp(command,"clear_display")==0){ui->setbackground(recoveryui::none);}elseif(strcmp(command,"enable_reboot")==0){//packagescanexplicitlyrequestthattheywanttheuser//tobeabletorebootduringinstallation(usefulfor//debuggingpackagesthatdon'texit).ui->setenablereboot(true);}else{loge("unknowncommand[%s]\n",command);}}fclose(from_child);intstatus;waitpid(pid,&status,0);if(!wifexited(status)||wexitstatus(status)!=0){loge("errorin%s\n(status%d)\n",path,wexitstatus(status));returninstall_error;}returninstall_success;}
try_update_binary函数,是真正实现读取升级包中的脚本文件并执行相应的函数的地方。在此函数中,通过调用fork函数创建出一个子进程,在子进程中开始读取并执行升级脚本文件。在此需要注意的是函数fork的用法,fork被调用一次,将做两次返回,在父进程中返回的是子进程的进程id,为正数;而在子进程中,则返回0。子进程创建成功后,开始执行升级代码,并通过管道与父进程交互,父进程则通过读取子进程传递过来的信息更新ui。
六、finish_recovery,重启
上一步完成之后,回到main函数,
//savelogsandcleanupbeforerebootingorshuttingdown.finish_recovery(send_intent);
保存升级过程中的log,清除临时文件,包括command文件(不清除的话,下次重启还会进入recovery模式),最后重启。
以上就是升级的一个流程。
补充:
手动升级的流程也基本差不多,通过powerkey+volume上键组合,进入recovery模式,进入prompt_and_wait函数等待用户按键事件。
recovery.cpp的main函数,
device::builtinactionafter=shutdown_after?device::shutdown:device::reboot;if(status!=install_success||ui->istextvisible()){device::builtinactiontemp=prompt_and_wait(device,status);if(temp!=device::no_action)after=temp;}
根据用户选择进入到相应的分支进行处理,如下图,
intchosen_item=get_menu_selection(headers,device->getmenuitems(),0,0,device);//device-specificcodemaytakesomeactionhere.itmay//returnoneofthecoreactionshandledintheswitch//statementbelow.device::builtinactionchosen_action=device->invokemenuitem(chosen_item);
当我们选择从外置sdcard升级,进入如下分支中,
casedevice::apply_ext:{ensure_path_mounted(sdcard_root);char*path=browse_directory(sdcard_root,device);if(path==null){ui->print("\n--nopackagefileselected.\n",path);break;}ui->print("\n--install%s...\n",path);set_sdcard_update_bootloader_message();void*token=start_sdcard_fuse(path);intstatus=install_package(fuse_sideload_host_pathname,&wipe_cache,temporary_install_file,false);finish_sdcard_fuse(token);ensure_path_unmounted(sdcard_root);if(status==install_success&&wipe_cache){ui->print("\n--wipingcache(atpackagerequest)...\n");if(erase_volume("/cache")){ui->print("cachewipefailed.\n");}else{ui->print("cachewipecomplete.\n");}}if(status>=0){if(status!=install_success){ui->setbackground(recoveryui::error);ui->print("installationaborted.\n");}elseif(!ui->istextvisible()){returndevice::no_action;//rebootiflogsaren'tvisible}else{ui->print("\ninstallfromsdcardcomplete.\n");}}break;}
char*path=browse_directory(sdcard_root,device);这个函数浏览sdcard下的文件,并把路径记录下来,然后根据名称排序,并处理用户按键。
·当用户选择第一个条目“../”,直接跳转到上级目录,并且继续浏览文件
·当用户选择的条目以"/"开头,直接进入子目录
·其它情况表明,该条目就是zip包.写入bcb,copy更新包至临时目录,直接转入install_package
选择zip包后,同样也是执行install_package函数,后面与自动升级的流程是一样的。
intstatus=install_package(fuse_sideload_host_pathname,&wipe_cache,temporary_install_file,false);
孙凯南个人博客
上海自考之家