【Android OTA】用nodejs搭建服務器

timg.jpeg

(原文鏈接)[https://moshuqi.github.io/2017/04/21/Android-OTA-用nodejs搭建服務器/]

OTA,Over-the-Air的簡寫,OTA升級就是通過GPRS、3G、無線網絡下載升級補丁升級,不用通過有線連接來升級。Android的應用或者是整個系統(tǒng),都可以通過OTA的方式進行版本的更新升級。

OTA具體原理自行google,或者參考這篇文章。本文和接下來的兩篇文章主要介紹的是具體的實現(xiàn)過程。

OTA升級大致過程

  1. 設備向服務器進行版本更新檢測請求
  2. 服務器將更新信息返回到設備端
  3. 設備通過返回信息下載指定的更新文件
  4. 下載完成后設備安裝升級

Android單個應用和整個系統(tǒng)的升級方式存在差異,接下來兩篇文章會分別介紹實現(xiàn)。

本文重點實現(xiàn)升級過程中的第二步,搭建一個服務器Demo,以方便后續(xù)的測試工作。服務器用的nodejs,使用較為簡單,沒接觸過的也可以跟著下面的步驟將服務器搭建在本地運行起來。

OTA服務器搭建

安裝nodejs

用的是Mac系統(tǒng),安裝命令

brew install node

查看是否安裝完成

$ node -v
v6.2.0

npm 是專門管理nodejs包的工具,用來方便地安裝第三方模塊,安裝nodejs時應該也默認同時安裝了npm,可以命令查看

$ npm -v
3.8.9

運行

安裝完成后,先實現(xiàn)個簡單的Demo,只需簡單幾行代碼便可在本地運行起一個服務器。

先創(chuàng)建一個文件夾 Server,在文件夾充創(chuàng)建文件 SimpleServer.js

用文本編輯工具打開SimpleServer.js,代碼實現(xiàn):

var http = require('http');

var server = http.createServer(function(req, res) {
    res.end('Hello!');
}).listen(3001);
console.log('Server listening at port: 3001');

然后打開命令行工具,cdServer 目錄下,敲入命令運行服務器:

node SimpleServer.js

命令行打印輸出:

Server listening at port: 3001

此時服務器開始監(jiān)聽來自本地端口3001的請求。

打開瀏覽器,地址欄輸入 http://localhost:3001 ,可以看到瀏覽器界面返回文本 Hello!。即一個最簡單的服務器。

Express框架

上邊實現(xiàn)的服務器對任何請求都返回Hello!文本,現(xiàn)實中服務器當然沒那么簡單。OTA服務器在接到請求時需要返回對應信息,并提供更新文件的下載接口。

Express 是一種保持最低程度規(guī)模的靈活 Node.js Web 應用程序框架,為 Web 和移動應用程序提供一組強大的功能。Express 提供的各種 HTTP 實用程序方法和中間件能幫助我們方便的處理網絡請求。具體可參考官方文檔

使用Express應用生成器快速搭建框架

使用以下命令安裝 express:

npm install express-generator -g

輸入如下命令,在當前目錄下創(chuàng)建名為 ota 的 Express 應用

express --view=pug ota

看結果打印,在ota目錄下自動創(chuàng)建了對應文件

1.jpg

cdota 目錄下,安裝需要的依賴

cd ota/
npm install

完成后,在node_modules目錄下會下載好項目需要的第三方依賴模塊

2.jpg

項目的運行文件為 bin/www,打開查看源文件,可看到服務器運行端口默認情況下為 3000

3.jpg

命令行運行服務器

node bin/www 

瀏覽器打開網址 http://localhost:3000

Express框架默認生成的界面

4.jpg

同時可以看到訪問服務器時命令行的打印信息

5.jpg

Ps. 關閉服務器時,在命令行敲 Ctrl + C,如果通過 Ctrl + Z 或者直接關閉命令行窗口界面的方式來退出服務器,會導致服務器監(jiān)聽的端口一直被占用,再次重啟服務器時會失敗。此時要么修改服務器監(jiān)聽的端口,要么將原端口所對應的進程殺掉。

殺指定端口進程方法,通過 lsof -i:端口號 查看端口對應PID, 然后 kill -9 PID

殺掉node相關的進程

6.jpg

OTA服務器實現(xiàn)

ota更新文件

在項目根目錄下創(chuàng)建文件夾 ota_file,將更新文件命名為 update.zip 放在該目錄下

7.jpg

路由實現(xiàn)

獲取更新信息

通過 /update_info 請求更新信息,將設備當前的版本信息作為參數(shù),例如 http://localhost:3000/update_info?versionName=v01

直接打開文件 routes/index.js,看到代碼

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

大概可以理解為,對于 / 請求,服務器會通過代碼 res.render('index', { title: 'Express' });,將指定頁面返回到瀏覽器,即我們看到的頁面。

對于 /update_info 請求,我們實現(xiàn)個類似的處理方式

具體代碼:

router.get('/update_info', function(req, res, next) {
    var name = req.query.versionName;
    console.log('current version:' + name);

    var info = {
        'url': '/ota_file/update.zip',
        'updateMessage': 'Fix bugs.',
        'versionName': 'v2',
        'md5': ''                           
    };

    var dir = process.cwd() + '/ota_file'
    var filePath = path.join(dir, 'update.zip');
    var md5 = getFileMD5(filePath);
    info.md5 = md5;

    res.writeHead(200, {'Content-Type': 'application/json;charset=utf-8'});
    res.end(JSON.stringify(info));
});

當我們在瀏覽器輸入 http://localhost:3000/update_info?versionName=v01 便會運行到該處代碼

var name = req.query.versionName;

獲取請求中 versionName 參數(shù)的值。

檢測是否有更新時,設備會將當前版本做為參數(shù)發(fā)送到服務器,通過與服務器最新的版本進行對比以決定是否需要升級。

var info = {
    'url': '/ota_file/update.zip',
    'updateMessage': 'Fix bugs.',
    'versionName': 'v2',
    'md5': ''                           
};
  • url:更新文件的下載地址
  • updateMessage:用來描述新版本的更新信息
  • versionName:新版本的版本名稱
  • md5:更新文件的md5值,用來和設備下載完成的更新包后md5值對比,保證下載完成的文件和服務器的文件一致

獲取更新文件的md5值,filePath 為更新文件的目錄

var dir = process.cwd() + '/ota_file'
var filePath = path.join(dir, 'update.zip');
var md5 = getFileMD5(filePath);
info.md5 = md5;

最后將 info 轉成 json 格式返回

res.writeHead(200, {'Content-Type': 'application/json;charset=utf-8'});
res.end(JSON.stringify(info));

Android設備最終獲取到json格式的數(shù)據(jù),通過特定字段將對應的值解析出來。

getFileMD5 函數(shù)用來獲取文件md5值,實現(xiàn)代碼:

function getFileMD5(filePath) {
    var buffer = fs.readFileSync(filePath);
    var fsHash = crypto.createHash('md5');

    fsHash.update(buffer);
    var md5 = fsHash.digest('hex');
    console.log("文件的MD5是:%s", md5);

    return md5;
}

一些處理操作用到了系統(tǒng)特定的模塊,使用前需要引入

var express = require('express');
var router = express.Router();

// add module
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');

加上代碼后,重新運行服務器,進行更新信息的請求,可以看到瀏覽器返回了對應的json數(shù)據(jù)

8.jpg
提供文件下載

返回的json數(shù)據(jù)中可看到其中

'url': '/ota_file/update.zip'

/ota_file/update.zip 即為我們下載文件的路徑

文件的下載路由實現(xiàn)代碼

router.get('/ota_file/:filename', function(req, res, next) {
    var filename = req.params.filename;
    var dir = process.cwd() + '/ota_file'
    var filePath = path.join(dir, filename);

    fs.exists(filePath, function(exist) {
        if (exist) {
            console.log('downloading:' + filename);
            res.download(filePath); 
        }
        else {
            res.set('Content-type', 'text/html');
            res.end('File not exist.');
        }
    });
});

/ota_file/update.zip 中的 update.zip 對應 /ota_file/:filename 中的 filename,所以下載請求的文件名可通過 filename 參數(shù)獲取

var filename = req.params.filename;

通過文件名合成文件路徑

var dir = process.cwd() + '/ota_file'
var filePath = path.join(dir, filename);

fs.exists 檢驗指定文件路徑是否存在,若是則進行文件下載

服務器的文件下載通過內置的 download 方法實現(xiàn)。

res.download(filePath); 

download 方法實現(xiàn)了http的斷點續(xù)傳協(xié)議,用瀏覽器下載時可嘗試暫停下載,再繼續(xù)下載時會延續(xù)之前的下載進度。

Ps. 下載也可通過打開文件將數(shù)據(jù)流寫到客戶端的方式來實現(xiàn),不過過程略麻煩,若要實現(xiàn)斷點續(xù)傳還得根據(jù)http協(xié)議range屬性自行處理。

文件下載實現(xiàn)完成后,重啟服務器生效,瀏覽器輸入 localhost:3000/ota_file/update.zip,回車后可看到文件下載到了本地

9.jpg

以上

服務器大概實現(xiàn)完成。實際生產環(huán)境中的處理流程會做更多判斷和容錯,這里只做了簡單處理,用來方便給后續(xù)Android OTA升級提供測試。

源碼地址

關注 ota/routes/index.js 文件即可

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容