文章开头我们就提到update.zip包来源有两种,一个是OTA在线下载(一般下载到/CACHE分区),一个是手动拷贝到SD卡中。不论是哪种方式获得update.zip包,在进入Recovery模式前,都未对这个zip包做处理。只是在重启之前将zip包的路径告诉了Recovery服务(通过将--update_package=CACHE:some_filename.zip或--update_package=SDCARD:update.zip命令写入到/cache/recovery/command中)。在这里我们假设update.zip包已经制作好并拷贝到了SD卡中,并以Settings-->About Phone-->System Update-->Installed From SDCARD方式升级。
我们的测试开发板是TCC8800,使用的Android源码是gingerbread0919,在这种方式下升级的源码位于gingerbread/device/telechips/common/apps/TelechipsSystemUpdater/src/com/telechips/android/systemupdater/下。 下面我们具体分析这种升级方式下,我们的update.zip是怎样从上层一步步进入到Recovery模式的。
一、从System Update到Reboot
当我们依次选择Settings-->About Phone-->System Update-->Installed From SDCARD后会弹出一个对话框,提示已有update.zip包是否现在更新,我们从这个地方跟踪。这个对话框的源码是SystemUpdateInstallDialog.java。
①在mNowButton按钮的监听事件里,会调用mService.rebootAndUpdate(new File(mFile))。这个mService就是SystemUpdateService的实例。 这 个类所在的源码文件是SystemUpdateService.java。这个函数的参数是一个文件。它肯定就是我们的update.zip包了。我们可以证实一下这个猜想。
②mFile的值:在SystemUpdateInstallDialog.java中的ServiceConnection中我们可以看到这个mFile的值有两个来源。
来源一:
mFile的一个来源是这个是否立即更新提示框接受的上一个Activity以“file”标记传来的值。这个Activity就是SystemUpdate.java。它是一个PreferenceActivity类型的。在其onPreferenceChange函数中定义了向下一个Activity传送的值,这个值是根据我们不同的选择而定的。如果我们在之前选择了从SD卡安装,则这个传下去的“file”值为“/sdcard/update.zip”。如果选择了从NAND安装,则对应的值为“/nand/update.zip”。
来源二:
另个一来源是从mService.getInstallFile()获得。我们进一步跟踪就可发现上面这个函数获得的值就是“/cache”+ mUpdateFileURL.getFile();这就是OTA在线下载后对应的文件路径。不论参数mFile的来源如何,我们可以发现在mNowButton按钮的监听事件里是将整个文件,也就是我们的update.zip包作为参数往rebootAndUpdate()中传递的。
③rebootAndUpdate:在这个函数中Main System做了重启前的准备。继续跟踪下去会发现,在SystemUpdateService.java中的rebootAndUpdate函数中新建了一个线程,在这个线程中最后调用的就是RecoverySystem.installPackage(mContext,mFile),我们的update.zip包也被传递进来了。
④RecoverySystem类:RecoverySystem类的源码所在文件路径为:gingerbread0919/frameworks/base/core/java/android/os/RecoverySystem.java。我们关心的是installPackage(Context context,FilepackageFile)函数。这个函数首先根据我们传过来的包文件,获取这个包文件的绝对路径filename。然后将其拼成arg=“--update_package=”+filename。它最终会被写入到BCB中。这个就是重启进入Recovery模式后,Recovery服务要进行的操作。它被传递到函数bootCommand(context,arg)。
⑤bootCommand():在这个函数中才是Main System在重启前真正做的准备。主要做了以下事情,首先创建/cache/recovery/目录,删除这个目录下的command和log(可能不存在)文件在sqlite数据库中的备份。然后将上面④步中的arg命令写入到/cache/recovery/command文件中。下一步就是真正重启了。接下来看一下在重启函数reboot中所做的事情。
⑥pm.reboot():重启之前先获得了PowerManager(电源管理)并进一步获得其系统服务。然后调用了pm.reboot(“recovery”)函数。他就是/gingerbread0919/bionic/libc/unistd/reboot.c中的reboot函数。这个函数实际上是一个系统调用,即__reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,mode,NULL);从这个函数我们可以看出前两个参数就代表了我们的组合键,mode就是我们传过来的“recovery”。再进一步跟踪就到了汇编代码了,我们无法直接查看它的具体实现细节。但可以肯定的是 这个函数只将“recovery”参数传递过去了,之后将“boot-recovery”写入到了MISC分区的BCB数据块的command域中。这样在重启之后Bootloader才知道要进入Recovery模式。
在这里我们无法肯定Main System在重启之前对BCB的recovery域是否进行了操作。其实在重启前是否更新BCB的recovery域是不重要的,因为进入Recovery服务后,Recovery会自动去/cache/recovery/command中读取要进行的操作然后写入到BCB的recovery域中。
至此,Main System就开始重启并进入Recovery模式。在这之前Main System做的最实质的就是两件事,一是将“boot-recovery”写入BCB的command域,二是将--update_package=/cache/update.zip”或则“--update_package=/sdcard/update.zip”写入/cache/recovery/command文件中。下面的部分就开始重启并进入Recovery服务了。
二、从reboot到Recovery服务
这个过程我们在上文(对照第一个图)已经讲过了。从Bootloader开始如果没有组合键按下,就从MISC分区读取BCB块的command域(在主系统时已经将“boot-recovery”写入)。然后就以Recovery模式开始启动。与正常启动不同的是Recovery模式下加载的镜像是recovery.img。这个镜像同boot.img类似,也包含了标准的内核和根文件系统。其后就与正常的启动系统类似,也是启动内核,然后启动文件系统。在进入文件系统后会执行/init,init的配置文件就是/init.rc。这个配置文件来自bootable/recovery/etc/init.rc。查看这个文件我们可以看到它做的事情很简单:
①设置环境变量。
②建立etc连接。
③新建目录,备用。
④挂载/tmp为内存文件系统tmpfs
⑤启动recovery(/sbin/recovery)服务。
⑥启动adbd服务(用于调试)。
这里最重要的就是当然就recovery服务了。在Recovery服务中将要完成我们的升级工作。
我们将在下一篇详细分析Recovery服务的流程细节。