代码改变世界

PhoneGap非编译式打包

2012-12-05 18:35 onm 阅读(...) 评论(...) 编辑 收藏
写过Android或者iOS应用的都知道,一个应用的诞生都需要经过编译这一步,PhoneGap是基于Android和iOS等之上的,常规的方式也是需要将代码编译,才能产生对应的安装包。大多数情况,编译过程会消耗相对较多的时间,对于Android平台为了代码安全性的考虑,有的时候会进行代码混淆,这更加增加了编译代码的时间。并且对于iOS,编译必须在Mac平台上进行。基于上面一些原因,我们需要寻找一种不需要编译过程的打包方案。 对于Phonegap应用,Android和iOS平台的源码文件都在www目录下面,从某种角度来说他们是静态的资源文件,我们基本上对应用的更改都是改动www目录下面的文件,他们是不需要编译的,对于文件的更改,只需要简单替换掉相应文件就行。这样就很容易想到,对于Phonegap应用,其实我们可以将原来的包解包,然后替换www目录下的文件,再重新打包。这就引发了两个问题,原来的包如何获得,如何解包和重新打包。有的时候我们还需要替换应用的icon,名字等,这些东西如何替换? 首先,我们先来看看通常的一个打包的流程是怎样的。对于各个平台大体流程应该都是一样的,编译代码,资源文件处理,压缩成一个文件,这个文件也就是最后的安装包了。我分两部分来做说明,依次介绍Android和iOS包结构和打包流程。 对于Android打包的流程还是很复杂,经过若干步骤,有兴趣的可以参看一下我以前写的有关Android打包流程的内容:http://t.cn/zjMXqkx。简单说来,还是刚才的话“编译代码,资源文件处理,压缩成一个文件”。 再来看看这最后生成的一个文件具体是什么样的,Android生成的安装包是apk包,其实这是一个zip压缩包,解压缩后可以看到里面的文件,里面有AndroidManifest.xml,这个是整个包的描述信息,包含包名,应用名,版本,icon等信息。还有assets目录,这个目录下面就是www目录,也就是PhoneGap用到的最重要的代码的目录。还有res目录,里面是一些资源文件,如icon等。还有classes.dex文件,这个就是Android编译后代码生成的文件。对于我们的需求,替换PhoneGap的www目录文件,很容易实现了,而对于替换应用名、icon等,就替换AndroidManifest.xml文件和res下的对应的资源文件就好了。 于是思路来了,先用unzip把包解开,然后替换相应文件和文件里的变量,最后再用zip把包压缩。这样可以吗,实验表明不可以,这里面有很多问题。首先AndroidManifest.xml是被压缩成二进制的,无法替换变量。再有,使用zip工具再压缩为apk文件后是无法在Android手机上运行的。还有压缩的包可能需要重新进行包的签名。另外直接替换像icon这样的资源文件,再打包也是不行的,apk里面的资源文件也是都必须经过压缩处理的,否则即使后面打包签名正确,也是无法在Android上面运行的。 正确的思路有两种:一种是采用原来正常的打包流程,在正常流程编译好classes.dex文件后,暂停。保留当前这个状态的所有文件为模板,然后对www文件和AndroidManifest.xml,res等相关文件替换,然后继续之前未完成的正常打包流程,资源文件处理,压缩成一个文件。这样每次就跳过了编译的过程。 第二种是使用apktool,apktool是一个反编译apk的工具,我们可以利用apktool将apk解包,然后替换文件,再用apktool打包,然后使用jarsign进行签名。使用这种方式对于不了解apk打包机制可能更容易理解一些。这种方法的好处是,你不需要清楚Android打包流程,逻辑上是“解包-更改-打包”的思路,更容易理解。 后来,我又想到可能更好的方法,最早我们的做法是把一个Android的Project作为模板,然后每次收到打包任务的时候,就把这个模板拷贝到一个工作目录中,然后把外来的PhoneGap用到的www目录放到这个模板中,然后打包。其实Android打包的过程是有类似于cache的机制的,或者说是增量编译,对于没有更改的地方它不会重新进行操作,比如编译代码。如果你的代码没有变更的话,它是不会重新编译的,由于之前每次都把这个模板拷贝出去,所以每次打包都是一个全新的包。这个新的想法是,工作目录存放这个模板,而不是每次打包的时候拷贝一份新的,这样的话就会节省一些步骤,从而减少了打包时间。 方法其实还是很多的,每个方法都可以work,没有对错之分。我自己更喜欢后来想到的和第一种方式。但是当时采用了第二种方式,这种更接近于hack的方式,不是很美好的方式。这有点像最近热映的电影少年派一样,第一个更加美好,但现实可能是第二个。 对于第一个和第三种方式写过一些测试,后来是懒得写出完整的代码了。于是尝试在第二个的基础上稍作更改,做了一个更适合个人使用的版本。最后会说道。前面跳过了很多细节,写的也不详细,只提供了一些思路,具体实现可以参考这个开源项目的代码。 再来说,对于iOS的ipa包的打包流程。其实我对iOS并不十分了解,大概了解了一下ipa包的结构。其实ipa包就是一个zip压缩包,解包的话就unzip,打包的话就zip就可以了(一个限制是生成的是未签名的ipa包,只能越狱安装),非常简单。对于压缩包里面,是一些层级目录。包里面有个Info.plist文件,描述了包的信息,如包名、应用名、版本、icon路径等(对应Android里面的AndroidManifest.xml文件),还有一些资源文件,如icon等,还有代码编译后产生的文件。www目录也在里面。替换PhoneGap的代码就把www目录下的文件替换掉就好。对于应用名称,版本号的替换,就是替换刚提到的Info.plist文件里面的内容。这里有个小细节,就是用xcode生成的ipa包里面的资源文件都做了压缩处理,Info.plist等文件处理成非文本文件,这样进行变量替换的时候就麻烦了一些,其实使用压缩前的纯文本Info.plist也是可以的,这个包是可以正常运行在iOS设备上的。 最后说一下这种方式的缺点,最大的缺点就是缺乏扩展性,不支持动态增删PhoneGap的插件,模板底包一旦生成,插件就固定了。 新浪移动云的打包平台大体上基于上面的思路,然后提供了封装了web的api接口,对内提供了打包服务。现在基于上面的思路开源了一个更加轻量级别的个人PhoneGap打包工具,里面的模板是基于新浪移动云平台的,开源在了github上面,有兴趣的可以去看看。https://github.com/qhm123/PhoneGap-Packager