PHP语言学习之下载PHPDroid: 基于WebView和PHP内置HTTP服务器开发Android应用
小标 2019-04-30 来源 : 阅读 1193 评论 0

摘要:本文主要向大家介绍了PHP语言学习之下载PHPDroid: 基于WebView和PHP内置HTTP服务器开发Android应用,通过具体的内容向大家展示,希望对大家学习php语言有所帮助。

本文主要向大家介绍了PHP语言学习之下载PHPDroid: 基于WebView和PHP内置HTTP服务器开发Android应用,通过具体的内容向大家展示,希望对大家学习php语言有所帮助。

PHP语言学习之下载PHPDroid: 基于WebView和PHP内置HTTP服务器开发Android应用

基于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("//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 //dudns.baidu.com/   
nameserver 180.76.76.76   
# CNNIC公共DNS //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清理上次应用可能意外退出遗留下来的东西.   


本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言PHP频道!


本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程