個人在Ubuntu上使用交叉編譯工具鏈 arm-none-linux-gnueabi 按照 DroidPHP 的教程
構建了適用於Android(ARM架構)和樹莓派Raspbian(ARM架構基於Debian的Linux發行版)的PHP解譯器(cli,cli-server).
下載地址: http://pan.baidu.com/s/1gfJcXbX
內容包括:
main : 這個目錄裡面是項目原始碼,主要就是MainActivity.java和assets資料.
phpdroid.apk: 應用樣本,大小約7MB,包含PHP和BusyBox.
這裡需要說明的是,BusyBox並不是PHP必備的東西,
打包它只是為了方便PHP能夠調用裡面常用的GNU/Linux命令,
去掉BusyBox後APK包還能縮小600KB.
需要強調的是,包裡的PHP是路徑無關的,運行也不需要root許可權,
只要維持assets/php/的目錄結構,放到你的應用裡也能正常運行.
PHP的版本是5.5.9.
PHPDroid基本工作原理:
Java啟動PHP內建的HTTP伺服器,然後開一個WebView訪問這個PHP驅動的HTTP服務.
其中,WebView用於實現人機互動,可以用傳統的HTML/CSS/jQuery技術進行圖形介面編程.
PHP則負責跟本地檔案系統,SQLite資料庫,網路進行互動.
需要強調的是,PHPDroid追求的不是像Java App那樣能夠訪問Android系統提供的API.
PHPDroid的優勢在於用傳統的Web開發技術HTML/CSS/JS/PHP/SQL就能開發基於WebView的本地WebApp.
PHPDroid內建的本地PHP不能訪問Android提供給Java的API,但可以操作本地檔案系統(應用目錄)和SQLite以及進行網路互動.
比如擷取一個新聞列表,WebView通過AJAX訪問本地PHP,PHP再通過cURL等訪問遠程伺服器.
遠程伺服器返回JSON,裡麵包含新聞的標題,摘要,縮圖網址,本地PHP轉成數組後迴圈輸出到WebView.
可見這個本地PHP既是WebView的伺服器端,又是遠程伺服器的用戶端,是WebView和遠程伺服器資料互動的中轉站.
把WebView和本地PHP看做一個整體,那它就是一個不能調用Android API的本地WebApp.
畢竟Android是Linux核心,一切皆檔案的思想還是在那裡的,
只要有許可權,PHP讀取一些系統資料(比如/proc/cpuinfo)並沒有問題.
PHPDroid詳細工作原理:
phpdroid/app/src/main/java/net/php/phpdroid/MainActivity.java
MainActivity在onCreate初次開機時複製:
/data/app/net.php.phpdroid.apk/assets/php/
到:
/data/data/net.php.phpdroid/php/
然後Runtime.getRuntime().exec執行PHP服務啟動指令碼:
/data/data/net.php.phpdroid/php/bin/start.sh
#!/system/bin/sh
chmod 700 $1/php/bin/busybox
chmod 700 $1/php/bin/php
$1/php/bin/php $1/php/bin/ua.php #隨機產生UserAgent
$1/php/bin/php $1/php/bin/port.php #擷取可用連接埠
$1/php/bin/php \
-c $1/php/bin/php.ini \
-d app_dir="$1" \
-d upload_tmp_dir="$1/php/tmp" \
-d session.save_path="$1/php/tmp" \
-S 127.0.0.2:`cat $1/php/bin/port` \
-t $1/php/www \
$1/php/bin/auth.php \
>/dev/null 2>&1 &
echo $! > $1/php/bin/pid #記錄PHP的PID
return 0
這個指令碼的作用就是,隨機產生用於標記WebView的UserAgent,擷取127.0.0.2上的可用連接埠,
然後啟動PHP伺服器,記錄其PID,用於在kill關閉.
關於PHP內建HTTP伺服器的介紹,請看:
https://wiki.php.net/rfc/builtinwebserver
其中:
/data/data/net.php.phpdroid/php/bin/ua.php
file_put_contents(dirname(__FILE__).'/ua', sha1(uniqid(mt_rand(), true)));
/data/data/net.php.phpdroid/php/bin/port.php
//PHP用 fsockopen 檢測連接埠是否被佔用,返回可用連接埠.
$port = 8181;
while ( $fp = @fsockopen('127.0.0.2', $port, $errno, $errstr, 1) ) {
fclose($fp);
$port++;
}
file_put_contents(dirname(__FILE__).'/port', $port);
/data/data/net.php.phpdroid/php/bin/auth.php
//觸發PHP服務進程開啟resolv_php.conf,要求resolv_php.conf跟auth.php同在一個目錄
gethostbyname('localhost');
$ua = file_get_contents(dirname(__FILE__).'/ua');
if(isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT']===$ua) {
return false;
} else {
exit('Auth Failed');
}
PHP服務在處理每個請求之前,都會執行auth.php檔案,
如果ua(UserAgent)不匹配,程式就會exit退出.
Android上一個應用對應一個使用者,每個應用目錄只允許應用所屬使用者進行訪問,
所以除非手機被root,否則其他應用是沒法讀取PHPDroid應用目錄裡的資料的.
應用MainActivity.java裡讀取ua檔案並設定為WebView的UserAgent,所以能夠訪問PHP服務.
手機上的其他應用,比如瀏覽器,因為沒有讀取其他應用目錄比如 /data/data/net.php.phpdroid 的許可權,
也就無法讀取PHPDroid產生的ua,自然也就無法訪問PHP服務.
MainActivity.java
webview.getSettings().setUserAgentString(ua);
webview.loadUrl("http://127.0.0.2:" + port);
關於DNS解析,glibc預設訪問的是/etc/resolv.conf
#define _PATH_RESCONF "/etc/resolv.conf"
在編譯glibc時,我改成了相對路徑:
#define _PATH_RESCONF "./resolv_php.conf"
/data/data/net.php.phpdroid/php/bin/resolv_php.conf
# 百度公用DNS http://dudns.baidu.com/
nameserver 180.76.76.76
# CNNIC公用DNS http://www.sdns.cn/
nameserver 1.2.4.8
nameserver 210.2.4.8
靜態連結了glibc庫的PHP,在執行auth.php裡的gethostbyname('localhost')操作時,
會觸發訪問auth.php所在目錄下的resolv_php.conf,從而進行DNS.
更好的方法應該是調用Android的getprop net.dns1擷取本地DNS,然後加入到resolv_php.conf裡.
但奇怪的是,在adb shell裡執行 getprop net.dns1 能正確輸出,
一套在PHP的 echo shell_exec('getprop net.dns1'); 就沒有輸出了.
執行 echo shell_exec('vmstat'); 調用其他命令是能正常輸出的.
這裡不知道是什麼問題,許可權問題麼?請高人指點.
關於glibc的編譯,我還把調用命令的/bin/sh改成了Android的/system/bin/sh,
這樣PHP的shell_exec等函數才能正常運行.
sed -i "s{/bin/sh{/system/bin/sh{" ./libio/oldiopopen.c
sed -i "s{/bin/sh{/system/bin/sh{" ./libio/iopopen.c
sed -i "s{/bin/sh{/system/bin/sh{" ./posix/tst-vfork3.c
sed -i "s{/bin/sh{/system/bin/sh{" ./posix/bug-regex9.c
sed -i "s{/bin/sh{/system/bin/sh{" ./sysdeps/posix/system.c
sed -i "s{/bin/sh{/system/bin/sh{" ./sysdeps/generic/paths.h
sed -i "s{/bin/sh{/system/bin/sh{" ./sysdeps/unix/sysv/linux/paths.h
位於PHP裡的proc_open函數也要進行類似修改:
sed -i "s{/bin/sh{/system/bin/sh{" ext/standard/proc_open.c
這樣PHP就可以愉快地調用Android和BusyBox裡提供的GNU/Linux常用命令了.
MainActivity在onKeyDown按下返回鍵KEYCODE_BACK退出應用時:
會調用stop.sh關閉PHP服務,stop.sh內容如下:
#!/system/bin/sh
ua=$1/php/bin/ua
if [ -r $ua ]; then
rm $ua
fi
port=$1/php/bin/port
if [ -r $port ]; then
rm $port
fi
pid=$1/php/bin/pid
if [ -r $pid ]; then
kill `cat $pid`
rm $pid
fi
return 0
就是把ua,port這兩個檔案刪掉,並且關閉PHP進程.
其實MainActivity在啟動時也會調用stop.sh清理上次應用可能意外退出遺留下來的東西.