基于Android上的PHP(好比我打包的PHPDroid),寥寥几行PHP代码,就能实现一个支持无线局域网用浏览器访问的Android手机的Shell,用于执行命令和PHP代码.javascript
你们之前可能都据说过PFA(PHP for Android)这么一个东西,能够下结论的是,这个项目其实已经再也不活跃失败了,由于其依赖的SL4A(Scripting Layer for Android)已经中止更新和维护了,并且它依赖SL4A的运行模式也跟我下面提到的PHPDroid的本地Browser/Server模式大相径庭.php
我的在Ubuntu上用交叉编译工具链musl-cross-compilers参考DroidPHP的教程构建了适用于Linux ARM架构的二进制PHP解释器.其中DroidPHP的教程用的是arm-none-linux-gnueabi那一套工具链,我发现很差使,后来我又看到了PocketMine,一个用PHP7构建的MineCraft Android版服务器端,它用的是musl-cross-compilers,因此我也改用这套工具链.我主要作了一些修改,好比修改libc库的shell路径,以及DNS相关的resolv.conf和hosts位置,主要是为了让PHP能在Android上跑.css
如今有了可以运行在Android上的PHP解释器,接下来就是看看能用什么方法构建App.由于PHP解释器从5.4开始内置了一个CLI HTTP Server,这是一个C实现的支持PHP编程的单进程Web服务器,很轻量,官方定位是开发测试.并且它还支持一个特性,就是能够启动时给它指定一个PHP路由脚本:php -S 127.0.0.2:8080 -t /www auth.php
好比我打包的PHPDroid里的auth.php是这样的:html
<?php //如下代码的意思就是,若是UserAgent不匹配,就输出Forbidden并退出 $ua = dirname(__FILE__).'/ua'; if( isset($_SERVER['HTTP_USER_AGENT']) && file_exists($ua) && $_SERVER['HTTP_USER_AGENT'] === trim(file_get_contents($ua)) ) { return false; } else { exit('Forbidden'); }
咱们还知道,PHP还内置了SQLite引擎,一个文件就是一个数据库,管理起来很是方便,可视化管理工具能够用免费开源跨平台的SQLiteStudio.如今有了HTTP服务器和SQLite数据库,我编译时还启用了一些经常使用的扩展,以下:java
bcmath calendar Core ctype curl date dom exif filter ftp gd hash json libxml mbstring mcrypt openssl pcre PDO pdo_sqlite Reflection session SimpleXML sockets SPL sqlite3 standard swoole xml xmlreader xmlwriter zip zlib
有了上面这些东西,咱们该怎么进行图形化交互呢?我想你们都想到了,那就是Android内置的WebView.我的认为,浏览器是有史以来最成功的跨平台GUI应用.Android从4.4开始,WebView底层由Chromium驱动,性能也是至关不错的.若是你要调用一些Java编写的本地功能,WebView一直都提供有一个addJavascriptInterface的特性,能够把Java对象注入到WebView中供JS调用,好比:jquery
Java代码: webview.addJavascriptInterface(new Object() { @JavascriptInterface public String getprop(String prop) { return java.lang.System.getProperties().getProperty(prop);; } }, "android"); WebView里的HTML代码: <a href="javascript:void(0)" onclick="alert(android.getprop('os.name'))">os.name</a>
也就是说,你用WebView也是能够进行一些本地API调用的,固然你得先用Java写好那些功能.linux
好了,如今PHPDroid的整个运行模式就比较清晰了,应用启动时,执行start.sh随机生成UserAgent并记录下来(ua.php),找到可用端口并记录下来(port.php),而后启动PHP内置HTTP服务器并记录PID(用于关闭),Java里启动WebView前读取随机生成UserAgent并setUserAgentString设置,PHP内置服务器的路由auth.php会对UserAgent进行比对,拒绝本机上其余应用(好比浏览器)发出的请求.
各个脚本以下:android
start.sh #!/system/bin/sh cd $1/php/bin chmod 700 busybox if [ ! -f php ]; then ./busybox xz -d file.tar.xz ./busybox tar xf file.tar && rm file.tar chmod 700 lib/ld php watcher qrencode ./busybox sed -i "s@/data/data/net.php.phpdroid@$1@g" php.ini ./busybox sed -i "s@/storage/self/primary@$2@g" php.ini cp resolv.conf $1/php/www cp hosts $1/php/www fi #随机生成UserAgent ./php -c php.ini ua.php #获取可用端口 ./php -c php.ini port.php #开发调试时把网站根目录复制到SD卡,方便修改 # -t $1/php/www 实际应用的网站根目录 # -t $2/phpdroid 开发调试的网站根目录 if [ ! -d $2/phpdroid ]; then cp -R $1/php/www $2/phpdroid; fi #启动PHP服务 $1/php/bin/php \ -c php.ini \ -S 127.0.0.2:`cat $1/php/bin/port` \ -t $2/phpdroid \ $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 stop.sh #!/system/bin/sh ua=$1/php/bin/ua if [ -e $ua ]; then rm $ua fi port=$1/php/bin/port if [ -e $port ]; then rm $port fi pid=$1/php/bin/pid if [ -e $pid ]; then kill -9 `cat $pid` rm $pid fi pid=$1/php/bin/pid_watcher if [ -e $pid ]; then kill -9 `cat $pid` rm $pid fi return 0 ua.php <?php $ua = 'Mozilla/5.0 (Linux; Android; PHPDroid) AppleWebKit (KHTML, like Gecko) Chrome Mobile Safari'; file_put_contents(dirname(__FILE__).'/ua', $ua.' '.sha1(uniqid(mt_rand(), true))); 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); MainActivity.java 在 onCreate 时启动 WebView 以下: webview = new WebView(this); webview.getSettings().setUserAgentString(ua); webview.getSettings().setJavaScriptEnabled(true); webview.setWebChromeClient(new WebChromeClient()); webview.setWebViewClient(new WebViewClient() { public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } }); webview.loadUrl("http://127.0.0.2:" + port); setContentView(webview);
好了,看到这里,你们基本已经明白我所说的用PHP开发Android应用的思路.
你们感兴趣能够下载我打包好的APK来体验一下:
phpdroid_20160703.apk(5.8M)
phpdroid_20160703.7z(4.7M)
apk里包含PHP最新的7.0.8和高性能网络编程扩展Swoole,
另外还有BusyBox和生成二维码的qrencode.
7z包是项目源代码,主要就是MainActivity.java和assets数据.
提示下,我打包的APK在start.sh里把网站根目录调整到了SD卡的phpdroid目录下,主要上方便你们体验时USB链接手机把本身写的PHP文件放到里面运行测试.git
附:开头题图(Shell)的PHP代码:github
<?php if(isset($_POST['cmd'])) { $pre = 'export PATH='.get_cfg_var('app_dir').'/php/bin:$PATH'."\n" .'alias php="'.get_cfg_var('app_dir').'/php/bin/lib/ld --library-path '.get_cfg_var('app_dir').'/php/bin/lib '.get_cfg_var('app_dir').'/php/bin/php"'."\n"; echo shell_exec($pre.trim($_POST['cmd']))."\n"; @var_export($GLOBALS['_SERVER']); exit(); } header('Content-Type: text/html; charset=utf-8'); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <title>PHPDroid Web Shell</title> <style> * { margin: 0; padding: 0; } body { background: #2D2D2D; } #con { width: 100%; margin: 0 auto; } #con .cmd { display: block; width: 96%; padding: 6px 2%; margin: 0 auto; height: 160px; border: none; border-top: 3px solid #E66000; border-radius: 0; resize: none; background: #F9F9F9; color: #2D2D2D; font-size: 14px; font-family: monospace; line-height: 1.5em; outline: none; word-break: break-all; } #con .run { display: block; width: 100%; margin: 0 auto; background: #E66000; border: none; box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.4); text-decoration: none; color: #FFF; font-size: 20px; text-align: center; line-height: 3em; letter-spacing: 0.2em; outline: none; } #con .run:active { box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.4); } .ret { width: 98%; padding: 6px 1%; margin: 0 auto; min-height: 160px; background: #2D2D2D; color: #FFF; font-size: 14px; font-family: monospace; line-height: 1.5em; overflow: auto; } </style> </head> <body> <div id="con"> <textarea class="cmd" spellcheck="false"> type php php -v php -r ' echo date("Y年m月d日 H时i分s秒")."\n"; ' ps|busybox head -n 1 ps|busybox grep php </textarea> <button class="run"">运行命令</button> </div> <pre class="ret"></pre> <script src="jquery.js"></script> <script> $('.ret').css('min-height',$(window).height()-247); $(window).resize(function(){ $('.ret').css('min-height',$(window).height()-247); }); $('#con .cmd').focus(); $('#con').on('click', '.run', function(){ $('.ret').text(''); $.ajax({ url: '<?php echo parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); ?>', type: 'POST', data: {cmd: $('#con .cmd').val()}, success: function(data){ $('.ret').text(data); } }); }); </script> </body> </html>