I am working on a project, part of which is Android application. As using Android Studio is frustrating experience and I already failed starting some projects due to its “usefulness”, this time I decided to work the problem around as much as I can, since the project is quite important to me and amount of development to be done is rather significant. Usually I would use some low level language for that as it guarantees best performance, or Python for its ease of use. But I already have experience in building complex stuff in C/C++ and tried webapps in Python/Django, so I know it would be too much for a hobby project. At the same time I remember PHP as very nice tool for a guy who have little experience. So after preparing successful PoC for PHP side, I decided to bundle whole PHP inside of Android application. This allows to deliver environment with similar concepts as notorious Electron, but way less resource hungry. Let me present you PHP for Android development. Just to be clear I show here only binary side, no Android project configuration here, but if I manage to prepare a demo app for that, I will make it as part 2 of this post.
Usage
As usual, the project is on Github, here. The usage is as simple as running make. For building there is a standard make
, or make all
. For installing into current directory, you can type make install
. And to change installation directory, you specify it the usual way: make install DESTDIR=/installation/path
. And basically that’s it. The result of installation is structure like below:
app/ └── src └── main └── jniLibs ├── arm64-v8a │ ├── libsqlite3.so │ └── php.so └── armeabi-v7a ├── libsqlite3.so └── php.so
In my opinion this requires a bit of explanation. While directory structure is like that just because Android Studio requires it that way, the files might not be entirely obvious. SQLite you see here is simply SQLite library built from upstream sources, nothing fancy. PHP is linked to it, so while starting PHP process, its directory must be pointed by LD_LIBRARY_PATH
environment variable. PHP itself is here only disguised as shared library. This is because of the fact that Android Studio builts into APK only shared libraries, not checking the file contents, so requirement is it has to be named like lib*.so
. Other than that it is just ordinary Linux binary linked for use in Android, so it can be copied to /data/local/tmp
for testing and ran from there, even on non-rooted devices (on newer Androids only by ADB).
Now to run such PHP variant, few configuration adjustments might be required:
LD_LIBRARY_PATH
environment variable must point to valid path oflibsqlite3.so
TMPDIR
envvar must be set to some writable directory, as Android lacks/tmp
. Otherwise sessions will be broken and any attempt to use temporary files would lead to PHP warningphp.ini
might require some adjustments, especially if any PHP extension is built (by default my distribution does not require any, but in theory user might want one)- PHP can now be started by:
${nativeLibraryDir}/php.so -S 127.0.0.1:8000 -c ${phpIniPath}
, wherenativeLibraryDir
can be retrieved by something like that in Kotlin:ContextWrapper(applicationContext).applicationContext.applicationInfo.nativeLibraryDir
andphpIniPath
is some arbitrary path, most likely in/data/data
directory, as this is the safest place to keep it
And basically that’s it. In case I mange to prepare a demo APK for bundling PHP, I will add second part, as I mentioned at the beginning. Happy hacking! And now it’s time for some technical details.
Internals
To give a quick overview, I used docker image based on Alpine to prepare stable environment for building PHP. Because it requires Android NDK installation it should be easily adaptable for building any other arbitrary software, provided it can stand restrictions and deficiencies of Android bionic libc implementation, which is always causing weird troubles.
For the PHP itself there are few constraints, as a lot of XML-related stuff must be disabled in order for it to compile. Maybe it is possible to enable it back and overcome the issues, but I don’t know what exactly is disabled, as I don’t use it anyway. What I needed for my application was SQLite support, as I use it as a database in order to avoid deploying any heavier SQL database to Android. And I think for most of use cases it is sane to have and use it as database.
As can be seen in the Dockerfile, the only file that I compile is php binary (libsqlite3.so is external to PHP). If make all is ran, it tries to build a bit more, like PHP debugger, but I don’t see any usage for that in APK environment, so I simply ignore that and other stuff that is possibly configured. For sure attempt to build it is going to cause more trouble than what is already here.
As can be seen in the repo, there not much else, beside what I mentioned. Maybe as the last detail, there is one nice hack that I did in Makefile in order to map architecture naming, which apparently is different between NDK and Gradle buildsystem. I don’t know what is the reason for having this inconsistency, as vendor should be the same for both tools. Anyway to overcome that, I made both variables reconfigurable by user in Dockerfile, so I was able to pass them from Makefile and in Makefile I did something like:
PLATFORMS=armv7a aarch64 armv7a_LIBDIR=armeabi-v7a aarch64_LIBDIR=arm64-v8a $(PLATFORMS): docker build --build-arg=LIBDIR=$($@_LIBDIR) .
I know this is hard to read kind of stuff, especially for someone not familiar with make language. Anyway I just leave it as an example of how to map one variable to other. How it works, I leave as an exercise to the reader 🙂