1. 程式人生 > >擁抱Android:編譯nodejs搭建移動平臺

擁抱Android:編譯nodejs搭建移動平臺

Android編譯系列篇:

2 - NodeJS

3 - Nginx

4 - MariaDB

可用的編譯指令碼支援 v8.12.0 和 v10.10.0

優化:如何使用編譯的node

原來一直使用terminal執行node,然後想用node執行app的人越來越多,那就開發一個android app吧。

Github的連結上,nodeBase有兩個部分,platform就是apk的部分,是android app的node平臺,把編譯好的node檔案放到指定位置,然後編譯成apk就能在android上跑了。

另一個部分是modules,就是各種各樣的node apps。現在暫時只放上去了檔案交換的app,就是用platform執行express app,這個app可以支援指定檔案的下載,還使用multer支援其他裝置上傳檔案到android,用瀏覽器訪問就好了。

那麼再寫個檔案編輯器吧,這樣在哪都可以coding了。

前進:nodeJS各項擴充套件

這幾天在想怎麼把SQLite 19萬行的程式碼不用編譯轉成純javascript,google一下,已經有人做好了!

而且這個project將很多native的專案轉化成純jiavascript,比如unity遊戲引擎,qemu虛擬機器模擬器等!

這樣nodejs的使用又可以得心應手很多。繼續將javascript部署在手機上當server吧!

J.Y.Liu

2016.11.20

優化:NodeJS和機器學習

Wow! TensorflowJS 來啦!把你的Android變成移動智慧平臺吧!可是最近發現TensorflowJS在手機上執行不正常(手機訪問 

https://storage.googleapis.com/tfjs-examples/mobilenet/dist/index.html 分類結果和Desktop版的Chrome相差太多),於是轉mxnet.js。我們一起讓Android變成機器學習的平臺吧。mxnet.js是LLVM的IR直接轉過來的,所以執行的時候速度很慢,但是至少正確輸出,我是800塊的白菜價Android,處理一張圖片要30s左右(squeezenet-model.json)。

只要把圖片檔案放在images資料夾,然後執行mxnet的nodejs app,我們訪問 http://127.0.0.1:9090/test/<圖片名> 就可以分類這個圖片了。比如將

這張貓的圖片下載到images/cat.jpg,然後 http://127.0.0.1:9090/test/cat.jpg 就可以輸出為:

[1]: n02124075 Egyptian cat, PROB=78.84891629219055%
[2]: n02123045 tabby, tabby cat, PROB=12.727376818656921%
[3]: n02123597 Siamese cat, Siamese, PROB=7.1867793798446655%
[4]: n02119022 red fox, Vulpes vulpes, PROB=0.2718033967539668%
[5]: n04074963 remote control, remote, PROB=0.2662230748683214%
[6]: n02123159 tiger cat, PROB=0.11871062451973557%
[7]: n02127052 lynx, catamount, PROB=0.08755343733355403%
[8]: n02104365 schipperke, PROB=0.059607491130009294%
[9]: n02109961 Eskimo dog, husky, PROB=0.04351081443019211%
[10]: n02971356 carton, PROB=0.035816358285956085%

剩下的就是mark一下微軟的模型轉換工具:https://github.com/Microsoft/MMdnn 將Tensorflow的模型轉化成mxnet的,然後用mxnet.js提供的json化工具打包模型引數,在手機上執行神奇的機器學習演算法吧。

J.Y.Liu

2018.05.18

陪伴:node-v0.12之後

node到0.12,可以說在編譯上改進了很多,尤其是加了arch的選擇,可以告訴configure要在android使用node。一些煩人的問題都沒有了,也不需要自己再去寫程式碼讓npm能夠連上網路。node自從分家到再合併,版本號瞬間就上去了,v4和v6依舊在編譯上大同小異;

因為node的依賴v8在cross-copmile上出現了和python相似的情形:python要在host上編譯pgen去生成一個code檔案,v8要用host版本mkpeephole生成一個table。

my solution is:
1. compile node-v7.x on your host machine with `./configure && make | grep mkpeephole | grep table.cc`
2. copy out `cp out/Release/obj.target/v8_base/geni/bytecode-peephole-table.cc /tmp/table.cc`
3. modify out/deps/v8/src/v8_base.target.mk to skip run mkpeephole: `sed -i 's|"$(builddir)/mkpeephole"|echo|' $MEDIR/../$ME/out/deps/v8/src/v8_base.target.mk`
4. compile node-v7.x on your host machine with cross-compile environment; `mkdir -p out/Release/obj.target/v8_base/geni && cp /tmp/table.cc out/Release/obj.target/v8_base/geni/bytecode-peephole-table.cc`
 

J.Y.Liu

2016.11.20

繼V8的mkpeephole要編譯為host版然後生成一個應該是平臺無關的程式碼檔案,這樣會讓交叉編譯break;後來V8改進了這塊,移除了編譯時需要mkpeephole;一時間還是在node交叉編譯上還是很開心。而到了node version 10,引用的V8引擎裡,使用了torque;它也是要生成一個host機器上執行的程式去生成程式碼,而node Makefile裡卻編譯成target上執行的binary,扎心了。沒法子,從頭到尾把node的編譯在host上執行一遍生成host可以執行的node,這樣那些檔案也被生成好了,複製出來。接著把Makefile裡呼叫torque的那行echo掉,node version 10於是就可以正常編譯了。

J.Y.Liu

2018.09.23

初遇:node-v0.10

有了python,可是感覺庫不全,於是就想想,要不編譯下nodejs?

下面就和大家一起進入nodejs的Android編譯,本文target是node-v0.10。

Android 執行nodejs NPM安裝Grunt

nodejs的關聯三方庫很到位,下載nodejs的source,c-ares,v8,openssl和zlib都包含了,很開心。

這裡先列出注意事項:

  • 第一個要說的是./configure的時候一定要加--without-snapshot,不然之後snapshot生成還是很麻煩的。我的教訓是開始沒有加這個引數,直接把Makefile裡的snapshot部分註釋掉,結果編譯出來的nodejs在Android上segment fault了。
  • 下面就是libuv,裡面的uv_barrier_t比較亂,一直報pthread_barrier_t找不到,看看Android libc.so和libc.a,確實pthread沒有把pthread barrier一簇函式型別編進去。當時第一反應就是自己寫一個,寫完了發現nodejs竟然自己已經實現了,但是包裹在另一個次元(就是如果定義了__APPLE__巨集才會包含)。所以這裡要選擇性的去掉#if,把uv_barrier_t以及相關的init, wait, destroy全部編譯出來(修改deps/uv/src/unix/thread.c, deps/uv/include/uv-unix.h)。
  • 後面就是uv__recvmmsg和uv__utimesat在deps/uv/src/unix/linux-syscalls.c和.h裡重複定義了,把.h裡的幹掉就好了。
  • 接著把src/node.cc裡和uid與gid相關的函式全部幹掉,比如getpwuid,直接返回NULL之類就好了。之前還有些小break,比如IOV_MAX直接改1024啦什麼的,編譯的時候一點一點改,都不是問題。
  • 最後就是比較變態的獲得網路interface,一開始看看,覺得直接返回ENOENT,不實現好了,結果編譯出來nodejs不能用npm下載包。好吧android裡又沒有ifaddrs.h,怎麼辦呢?突然想到了busybox,裡面的ifconfig是工作的,在Android上也是,就把busybox-1.21.1/networking/interface.c拿出來改改,好了npm最後也工作了。
uv_err_t uv_interface_addresses(uv_interface_address_t** addresses,
  int* count) {
  int numreqs = 30;
  struct ifconf ifc;
  struct ifreq *ifr;
  int n, err = -1;
  int skfd;
  uv_interface_address_t *address;

  ifc.ifc_buf = NULL;

  skfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (skfd < 0) {
        return uv__new_sys_error(-1);
  }

  for (;;) {
    ifc.ifc_len = sizeof(struct ifreq) * numreqs;
    ifc.ifc_buf = realloc(ifc.ifc_buf, ifc.ifc_len);

    if (ioctl(skfd, 0x8912 /*SIOCGIFCONF*/, &ifc) < 0) {
      goto out;
    }
    if (ifc.ifc_len == (int)(sizeof(struct ifreq) * numreqs)) {
      /* assume it overflowed and try again */
      numreqs += 10;
      continue;
    }
    break;
  }

  *count = ifc.ifc_len/sizeof(struct ifreq);
  *addresses = (uv_interface_address_t*)
    malloc(*count * sizeof(uv_interface_address_t));
  if (!(*addresses)) {
    return uv__new_artificial_error(UV_ENOMEM);
  }

  address = *addresses;
  ifr = ifc.ifc_req;
  for (n = 0; n < ifc.ifc_len; n += sizeof(struct ifreq)) {
    // XXX: add exception handler
    ioctl(skfd, 0x8915 /*SIOCGIFADDR*/, &ifr);
    address->name = strdup(ifr->ifr_name);
    if (ifr->ifr_addr.sa_family == AF_INET6) {
      address->address.address6 = *((struct sockaddr_in6 *)&ifr->ifr_addr);
    } else {
      address->address.address4 = *((struct sockaddr_in *)&ifr->ifr_addr);
    }
    ifr++;
    address++;
  }
  err = 0;

out:
  close(skfd);
  free(ifc.ifc_buf);
  if (err == 0) return uv_ok_;
  return uv__new_sys_error(err);
}

J.Y.Liu

2014.12.08