下載PHPDroid: 基於WebView和PHP內建HTTP伺服器開發Android應用

來源:互聯網
上載者:User

標籤:

基於Android上的PHP(比如我打包的PHPDroid),寥寥幾行PHP代碼,就能實現一個支援無線區域網路用瀏覽器訪問的Android手機的Shell,用於執行命令和PHP代碼.

 

 

 

個人在Ubuntu上使用交叉編譯工具鏈  arm-none-linux-gnueabi 或 musl-cross-compilers(推薦)  按照  DroidPHP 的教程
cross_compile_php.txt
這是我使用musl-cross-compilers交叉編譯Android版PHP7的詳細筆記.
構建了適用於Android(ARM架構)和樹莓派Raspbian(ARM架構基於Debian的Linux發行版)的PHP解譯器(cli,cli-server).

可以看到,PHP進程的記憶體(RSS)記憶體佔用不到5MB,WebView的記憶體佔用超過56MB.
照著Linux C man文檔inotify的常式給PHPDroid寫了個C程式(watcher),
在App卸載刪除檔案時,捕獲IN_DELETE_SELF事件,退出PHP進程.
:
phpdroid_20160703.apk(5.8M)
phpdroid_20160703.7z(4.7M)
apk裡包含PHP-7.0.8和高效能網路編程擴充Swoole,
另外還有BusyBox和產生二維碼的qrencode.
7z包是項目原始碼,主要就是MainActivity.java和assets資料.
這裡需要說明的是,BusyBox並不是PHP必備的東西,
打包它只是為了方便PHP能夠調用裡面常用的GNU/Linux命令,比如xz.
為了減少APK大小,用xz極限壓縮PHP,應用首次運行時再調用busybox的xz解壓,從而減少APK大小.
需要強調的是,包裡的PHP是路徑無關的,運行也不需要root許可權,
只要維持assets/php/的目錄結構,放到你的應用裡也能正常運行.
網站根目錄位於assets/php/www.
PHPer在PC上開發時,只需執行:
php -S 127.0.0.2:8181 -t /path/to/assets/php/www
然後開啟瀏覽器的手機模式訪問 127.0.0.2:8181 就可以了.
phpdroid_20160413.7z改動說明:
為了方便開發人員在電腦通過MTP連手機時就能修改PHP檔案,所以把網站根目錄調整到外部儲存.
網站根目錄:比如小米和華為執行
String www_dir = Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+getPackageName();
Log.d("PHPDroid", www_dir);
返回的是:
/storage/emulated/0/net.php.phpdroid
phpdroid.apk在啟動時會自動建立這個目錄,並寫入一個檔案index.php.
建立的這個目錄在手機的檔案管理工具能即時看到,但電腦檔案管理工具(MTP)裡卻不會立即顯示,需要重啟手機才能看到.

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,
但可以操作本地檔案系統(應用目錄/SD卡)和SQLite以及進行網路互動.
比如擷取一個新聞列表,WebView通過AJAX訪問本地PHP,PHP再通過cURL訪問遠程伺服器.
遠程伺服器返回JSON,裡麵包含新聞的標題,摘要,縮圖網址,本地PHP轉成數組後迴圈輸出到WebView.
可見這個本地PHP既是WebView的伺服器端,又是遠程伺服器的用戶端,是WebView和遠程伺服器資料互動的中轉站.
當然WebView也可以通過JSONP遠程擷取資料.
把WebView和本地PHP看做一個整體,那它就是一個不能調用Android API的本地WebApp.
畢竟Android是Linux核心,一切皆檔案的思想還是在那裡的.
只要有許可權,PHP讀取一些系統資料(比如/proc/cpuinfo)並沒有問題.
如果你要訪問Android Java API,可以addJavascriptInterface注入Java對象到WebView供JS調用:
webview.addJavascriptInterface(new MyClass(this), "myClass");
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
cd $1/php/bin
chmod 700 busybox
if [ ! -f php ]; then
    ./busybox xz -d php.xz
    ./busybox xz -d watcher.xz
    chmod 700 php
    chmod 700 watcher
fi
#隨機產生UserAgent
./php -c php.ini ua.php
#擷取可用連接埠
./php -c php.ini port.php
#建立檔案/storage/self/primary/net.php.phpdroid/index.php
./php -c php.ini -d www_dir="$2" www.php
#啟動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 $2 \
$1/php/bin/auth.php \
>/dev/null 2>&1 &
#記錄PHP的PID
echo $! > pid
#監聽,發現檔案auth.php被刪除,則關閉PHP進程
$1/php/bin/watcher $1/php/bin/auth.php >/dev/null 2>&1 &
#記錄watcher的PID
echo $! > pid_watcher
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
<?php
file_put_contents(dirname(__FILE__).‘/ua‘, sha1(uniqid(mt_rand(), true)));
/data/data/net.php.phpdroid/php/bin/port.php
<?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
$ua = dirname(__FILE__).‘/ua‘;
if( isset($_SERVER[‘HTTP_USER_AGENT‘]) 
    && file_exists($ua) 
    && $_SERVER[‘HTTP_USER_AGENT‘] === trim(file_get_contents($ua)) ) {
    //每次請求都執行getprop net.dns1擷取手機DNS並寫入resolv_php.conf供glibc庫使用.
    if( ($dns1 = filter_var(trim(shell_exec(‘getprop net.dns1‘)), FILTER_VALIDATE_IP)) !== false ) {
        $dns = file_get_contents(dirname(__FILE__).‘/resolv_php.conf.default‘);
        file_put_contents(dirname(__FILE__).‘/resolv_php.conf‘, ‘nameserver ‘.$dns1."\n".$dns);
    }
    gethostbyname(‘localhost‘); //觸發PHP進程開啟resolv_php.conf,要求resolv_php.conf跟auth.php在同一目錄
    return false;
} else {
    exit(‘Forbidden‘);
}
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‘); 調用其他命令是能正常輸出的.
這個問題發生在小米(Android 6)上,華為(Android 4)上是正常的.
關於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 -9 `cat $pid`
    rm $pid
fi
pid=$1/php/bin/pid_watcher
if [ -r $pid ]; then
    kill -9 `cat $pid`
    rm $pid
fi
return 0
就是把ua,port這兩個檔案刪掉,並且關閉PHP和watcher進程.
其實MainActivity在啟動時也會調用stop.sh清理上次應用可能意外退出遺留下來的東西.

 

 

來源於:http://my.oschina.net/eechen/blog/655689

下載PHPDroid: 基於WebView和PHP內建HTTP伺服器開發Android應用

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.