发表日期:2017-01 文章编辑:小灯 浏览次数:3472
App开发测试过程中,我们会把安装包传到各种第三方的内测分发平台方便下载。这些平台或多或少有这样那样的限制,比如下载量啊、付费啊、不能方便找到历史版本啊。还有一方面,我们经常会打Debug版本的包方便调试,又不希望Debug包流传到外部去,这样就很有必要自己搭一个下载平台,于是就有了这个项目(github地址)。
先说安卓,apk文件通过最简单的http/ftp下载就可以安装了,略过。
iOS稍微复杂一点,需要两步才能完成。
第一,下载链接必须是这样的格式
itms-services://?action=download-manifest&url=一个plist文件的地址 第二,plist内容如下
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>items</key> <array> <dict> <key>assets</key> <array> <dict> <key>kind</key> <string>software-package</string> <key>url</key> <string>ipa文件的地址</string> </dict> </array> <key>metadata</key> <dict> <key>bundle-identifier</key> <string>bundleID</string> <key>bundle-version</key> <string>1.0</string> <key>kind</key> <string>software</string> <key>title</key> <string>AppTitle</string> </dict> </dict> </array> </dict> </plist> 其中,最重要的就是ipa文件的地址,要求必须是https协议,那就需要SSL证书,幸运的是我们可以信任自签名的证书。下载的过程就是这样,当然我们希望这个链接和plist的生成是自动完成的。
参考如何创建一个自签名的SSL证书(X509)
单单只能下载还不够,我们希望看到更多的信息:App名字、版本号、build号、更新时间、图标等。这些信息虽然可以留给上传者在上传的时候一并带上,但是作为有追求的程序员,把方便留给别人的最基本的,因此我们要从ipa/apk中提取这些信息。
无论是ipa还是apk,本质都是zip压缩文件。
对于iOS的ipa,包信息都放在Info.plist中,主要有CFBundleVersion、CFBundleIdentifier、CFBundleShortVersionString、CFBundleName等。图标文件的名字也是固定的,只要解压就可以得到。不过,苹果对png图片进行了了自定义的pngcrush压缩,有压缩自然就有还原工具pngdefry。
对于Android的apk,解压后还能看到AndroidManifest.xml,但是里面的内容经过编码显示为乱码,不方便查看,需要借助开发工具aapt(Android Asset Packaging Tool),方法如下
aapt dump badging apkPath
输出的文本格式如下,不是标准的歌声,需要手动转换一下。
package: name='com.jianshu.haruki' versionCode='16070101' versionName='1.11.2' sdkVersion:'14' targetSdkVersion:'22' ... application: label=' ' icon='res/drawable-hdpi-v4/icon_jianshu_new.png' ... 程序员有一个习惯,需要某个东西的时候会先一番搜索,直接用别人写好的,用着用着发现别人写的东西有这样那样的不足,然后撸起袖子自己造一个。这次也不例外,我在github上找到了一个ios-ipa-server,它的特点是简单,ipa文件存储在一个目录下,没有数据库,包信息只有上传时间(其实就是文件更新时间),不能对app归类,只靠文件名区别,不支持上传,如下图:
既然ios-ipa-server是基于node-express写的,正好我没写过nodejs,那就在它的基础上继续写吧,借机学(zhuang)习(bi)一下。
整个项目的结构是这样的,提供四个API:包上传、获取所有App最新版本、获取某个App的所有版本、动态生成plist文件,数据存储使用sqlite3。
接口设计如下:
path: POST /uploadparam:package:安装包文件response: { id: 6, guid: "46269d71-9fda-76fc-3442-a118d6b08bf1", bundleID: "com.jianshu.Hugo", version: "2.11.4", build: "1608051045", icon: "https://10.20.30.233:1234/icon/46269d71-9fda-76fc-3442-a118d6b08bf1.png", name: "Hugo", uploadTime: "2016-12-01 20:50:05", platform: "ios", url: "itms-services://?action=download-manifest&url=https://10.20.30.233:1234/plist/46269d71-9fda-76fc-3442-a118d6b08bf1" } 后端需要拿到安装包,提取出包信息和png图标图片,然后插入到数据库中,最后存储安装包文件和png图片,这也是最关键、最复杂的一个API。
app.post('/upload', function(req, res) { var form = new multiparty.Form(); form.parse(req, function(err, fields, files) { var obj = files.package[0]; var tmp_path = obj.path; parseAppAndInsertToDb(tmp_path, info => { storeApp(tmp_path, info["guid"], error => { if (error) { errorHandler(error,res) } }) console.log(info) res.send(info) }, error => { errorHandler(error,res) }); }); }); 接收表单信息用到了multiparty模块,parseAppAndInsertToDb内部完成了包信息的提取和存储,storeApp存储包文件。
parseAppAndInsertToDb的实现如下,
function parseAppAndInsertToDb(filePath, callback, errorCallback) { var guid = Guid.create().toString(); var parse, extract if (path.extname(filePath) === ".ipa") { parse = parseIpa extract = extractIpaIcon } else if (path.extname(filePath) === ".apk") { parse = parseApk extract = extractApkIcon } Promise.all([parse(filePath),extract(filePath,guid)]).then(values => { var info = values[0] info["guid"] = guid excuteDB("INSERT INTO info (guid, platform, build, bundleID, version, name) VALUES (?, ?, ?, ?, ?, ?);", [info["guid"], info["platform"], info["build"], info["bundleID"], info["version"], info["name"]],function(error){ if (!error){ callback(info) } else { errorCallback(error) } }); }, reason => { errorCallback(reason) }) } 首先根据文件后缀名判断安装包类型,因为ipa和apk的处理逻辑不一样,所以分别对应两个方法,包信息的提取和icon提取可以同时进行,所以这里用了Promise.all。parseIpa和parseApk就是包信息的提取。extractApkIcon和extractIpaIcon则是icon的提取,extractIpaIcon多了一步还原png图片的处理。
parseIpa用到了ipa-extract-info模块,parseApk则使用了apk-parser3,代码都非常简单。详细可进入github地址。
其他三个API则比较简单了,无非就是根据参数取数据,不再赘述。
安装步骤非常简单,首先需要安装node,有了node之后只要一行命令
npm install -g ipapk-server 安装完成之后输入命令
ipapk-server 手机浏览器访问https://ip:port 即可打开下载页面


作为一个优质原创内容社区,拥有大量优质原创内容,提供了极佳的阅读和书写体验,吸引了大量文字爱好者和程序员。 技术团队在这里分享技术心得体会,是希望抛砖引玉,吸引更多的程序员大神来 记录、分享、交流自己的心得体会。这个专题以后会不定期更新 技术团队的文章,包括Android、iOS、前端、后端等等,欢迎大家关注。