文档章节

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

eechen
 eechen
发布于 2016/04/09 10:34
字数 2274
阅读 10398
收藏 69

基于Android上的PHP(比如我打包的PHPDroid),寥寥几行PHP代码,就能实现一个支持无线局域网用浏览器访问的Android手机的Shell,用于执行命令和PHP代码.


个人在Ubuntu上使用交叉编译工具链  arm-none-linux-gnueabimusl-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_20170117.apk 我打包的APK包(5MB),无Swoole
phpdroid_20170117.7z 项目源代码(Android Studio)
phpdroid_20160703.apk(5.8M)
phpdroid_20160703.7z(4.7M)
apk里包含PHP-7.0.8和高性能网络编程扩展Swoole,
另外还有BusyBox和生成二维码的qrencode.
7z包是项目源代码,主要就是MainActivity.java和assets数据.

我提供的APK包里PHP根目录默认是SD卡下的phpdroid,
你把你的PHP脚本放到里面打开应用就可以查看了.
如果你是自己下载Android项目构建,那你的PHP脚本就放在:
AndroidStudioProjects/phpdroid/app/src/main/assets/php/www
如果你不想PHP脚本放在SD卡以免被别人修改,那你就修改start.sh:
把 -t $2/phpdroid 改为 -t $1/php/www 即可.
这样APK安装后PHP脚本就是放在:
/data/data/net.php.phpdroid/php/www/index.php

这里需要说明的是,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清理上次应用可能意外退出遗留下来的东西.

 

 

 

© 著作权归作者所有

共有 人打赏支持
eechen

eechen

粉丝 1004
博文 107
码字总数 55962
作品 1
深圳
私信 提问
加载中

评论(47)

卧龙大熊猫
卧龙大熊猫
牛B 思路不错 折腾折腾还是可以的
Darkest
Darkest
最好是再弄一套前端的UI框架上去,统一一下界面样式,开发起来也更省事,比如像微信的WeUi那种,然后PHP上的框架,就不上那些太重的,Yaf实现简单路由和配置项,再加个爬虫框架,哦哦,把Mysql那些太笨重的去掉,有Sqlite这种简单的就够了。就快要无敌了。
桃源人
桃源人
赞赞赞
hosser
hosser
我记得至少在两年前,PHP 官方 zendstudio 就有开发 APP 的功能了。
eechen
eechen
@乌龟壳 本地B/S架构没什么不完美的,我觉得很完美,完全发挥了PHP的固有优势,而且开发者不需要改变任何思维.
乌龟壳
乌龟壳

引用来自“乌龟壳”的评论

我有朋友想做android手机上的PHP开发,他给我看了下这个

http://www.54ok.cn/6759.html

帮忙看看PHPDroid和这个哪个更好一些?

引用来自“eechen”的评论

KSWeb是收费的应用,不开源,定位是在Android上搭建LAMP服务.我打包的PHPDroid则只有PHP7和Swoole,数据库SQLite和HTTP服务器都是PHP内置,界面用WebView,很简单的本地Browser/Server模式.GLIBC DNS相关的配置文件resolv.conf和hosts我使用的是相对路径,PHP解释器也是路径无关的,Android项目代码和PHP编译步骤也都是公开的,完全可以自由拿去组装自己的应用.有空我再打包新版PHP7,同时把找到的代码补上让WebView支持上传.现在不足之处就是有些扩展无法交叉编译,比如可以导出脚本opcode实现代码保护的opcache扩展,但我看到有人是可以编译ARM版opcache的:
http://repozytorium.mati75.eu/raspbian/pool/main/p/php7.0/
要不你来看看mati75是怎么编译的树莓派版本PHP7和opcache扩展.
最近我也较忙,晚点看看,用php开发android这个思路蛮有意思的,要是能直接从webview打通php更好了,监听端口什么的总觉得不够完美。
eechen
eechen

引用来自“乌龟壳”的评论

我有朋友想做android手机上的PHP开发,他给我看了下这个

http://www.54ok.cn/6759.html

帮忙看看PHPDroid和这个哪个更好一些?
KSWeb是收费的应用,不开源,定位是在Android上搭建LAMP服务.我打包的PHPDroid则只有PHP7和Swoole,数据库SQLite和HTTP服务器都是PHP内置,界面用WebView,很简单的本地Browser/Server模式.GLIBC DNS相关的配置文件resolv.conf和hosts我使用的是相对路径,PHP解释器也是路径无关的,Android项目代码和PHP编译步骤也都是公开的,完全可以自由拿去组装自己的应用.有空我再打包新版PHP7,同时把找到的代码补上让WebView支持上传.现在不足之处就是有些扩展无法交叉编译,比如可以导出脚本opcode实现代码保护的opcache扩展,但我看到有人是可以编译ARM版opcache的:
http://repozytorium.mati75.eu/raspbian/pool/main/p/php7.0/
要不你来看看mati75是怎么编译的树莓派版本PHP7和opcache扩展.
乌龟壳
乌龟壳
我有朋友想做android手机上的PHP开发,他给我看了下这个

http://www.54ok.cn/6759.html

帮忙看看PHPDroid和这个哪个更好一些?
安西都护府首席程序员
安西都护府首席程序员
我只问一句,怎么获取手机上的联系人
neconano
neconano
修改 assets/php/www 的index.php 怎么重新编译后还是原来的index.php内容
PHPDroid 更新,PHP 开发 Android 应用

PHPDroid 更新了,集成最新PHP7+Swoole+BusyBox+QRencode 开发 Android 应用。 下载地址: phpdroid_20160703.apk(5.8M) phpdroid_20160703.7z(4.7M) apk里包含PHP-7.0.8和高性能网络编程扩展...

eechen
2016/07/04
7.4K
53
PHP开发者畅谈PHP的未来

1.PHP在移动计算领域的应用. PHDroid已经把PHP的熊熊战火烧到Android上,像开发WAP手机站那样轻松开发APP. 基于SL4A(Scripting Layer for Android)的PFA(PHP for Android)是行不通的. 基于PHP...

开源中国总统
2016/06/21
1
0
PHP开发Linux桌面应用和Android应用思路

PHP7中用opcache.file_cache导出脚本opcode实现源代码保护 http://my.oschina.net/eechen/blog/539995 下载PHPDroid: 基于WebView和PHP内置HTTP服务器开发Android应用 http://my.oschina.ne......

eechen
2015/09/05
0
14
PHP 开发 Android 应用--PHPDroid

个人在Ubuntu上使用交叉编译工具链 arm-linux-musleabi 或 arm-none-linux-gnueabi 按照 DroidPHP 的教程构建了适用于Android(ARM架构)和树莓派Raspbian(ARM架构基于Debian的Linux发行版)的P...

eechen
2016/04/11
4.2K
34
在 WebView 中建立 Android Apps

1. Web Apps的两种形式 在Android中,Web Apps有两种形式供用户访问。一种就是用手机上的浏览器直接访问的网络应用程序,这种情况用户不需要额外安装其他应用,只要有浏览器就行;而另一种,...

华宰
2011/09/06
1K
1

没有更多内容

加载失败,请刷新页面

加载更多

Nginx反向代理

Nginx反向代理 应用场景 A 机器运行的nginx提供的web服务,只有一个内网地址192.168.254.128(内网) B机器有两块网卡,一个地址是192.168.254.137(内网),另一个是192.168.79.128(外网)...

李超小牛子
今天
2
0
数据库事务隔离级别

当数据库上有多个事务同时执行的时候,可能出现下面问题: 脏读(dirty read):指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访...

Jacktanger
今天
1
0
4.61 - 第二个JAVA应用 4.62/63 - Tomcat的管理功能

4.61 - 第二个JAVA应用 方法一:配置文件: /usr/local/tomcat/conf/server.xml <Host name="www.aminglinux.cc" appBase="/data/wwwroot/www.aminglinux.cc" unpackWARs="tr......

Champin
今天
0
0
MariaDB密码重置

MariaDB密码重置 如果记得root的密码: mysqladmin -uroot -paminglinux password "aming-linux" //用此方式将原密码aminglinux重置为aming-linux 如果不记得原密码: # vi /etc/my.cnf......

wzb88
昨天
1
0
印度封禁抖音,称导致该国年轻人“文化堕落”!

本文经授权转载自顶级程序员 (ID:TopCoding) 作者 | 江户川雨 责编 | https://weavi.com/13775725 https://weavi.com/13775726 https://weavi.com/13775724 https://weavi.com/13775723 ......

陈刚生
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部