在 iOS 的 APP 中,每个程序都在自己的沙盒中运行,一旦程序删除了,应用的数据也就被清除了,所以大部分程序,需要保存数据的都会使用 iCloud 备份数据,但是如果是创作类的 APP,类似笔记之类的,如果要导出到电脑,就必须还要中转一次,非常麻烦。所以也有很多 APP 就开始内置了 FTP 服务器,一旦启动后,电脑只需要通过 FTP 客户端链接就可以访问 APP 内的数据了。
其实在Android中也有很多这些类似的 APP,为了方便和 PC 之间共享 APP 里的应用数据,也会有 FTP 或者WebDAV服务在 APP 里运行。但是Android不存在和 iOS 的那种沙盒问题,虽然 Android 也有沙盒。通常大部分的手机不会取得 root 权限,敏感的应用数据都会放在沙盒中,也就是 APP 内部数据目录,位于 /data/data/com.xxx.xx/ 中,可以通过 Context.getFilesDir() 获取到该路径,如果手机没有 root 权限,除了 APP 本身,谁也无法访问这里面的数据。但是 Android 可以选择将数据存放在外部沙盒中,也就是 APP 外部数据目录,可以通过
Context.getExternalFilesDir() 获取到该路径,甚至还有其他歪门邪道的 APP 在外置存储里随便建立文件夹 ...
内置以服务器端运行方式和外部进行数据交换的 APP 有很多,比如多看阅读,Documents5 等等。
在实现上大部分都是启动 Socket 监听一个固定端口,然后处理 HTTP 请求,但是对于大部分 APP 码农,处理 HTTP 是一件非常麻烦的事情。要处理 Header,对 POST 和 GET 的处理,对文件上传和普通表单的处理等等,如果不借助第三方库,这个功能想要写好非常困难。
在第三方实现中有 AndroidAsync ,虽然没看过多看的源代码,但是估计十有八九也是采用了这个库。
不过它也可以作为客户端方式,作为监听服务方式运行使用方法非常简单:
AsyncHttpServer server = new AsyncHttpServer();
server.get("/", new HttpServerRequestCallback() {
@Override
public void onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
response.send("Hello!!!");
}
});
server.listen(5000);
对于大部分做过 WEB 的同学可能在提到服务器端程序时,肯定会想到 IIS 、Tomcat、Apache 这些。但是 IIS 是 Windows 平台的,IIS 所依赖的 HTTP.SYS 是系统驱动级别的,移植是不可能移植的,这辈子都不可能移植的。 Tomcat 是运行在 JVM 虚拟机上的 JavaEE 容器,Android 虽然也使用 JAVA 语言,但是其虚拟机是 ART(4.4以前是 Dalvik),Apache 是 C/C++ 开发的,移植到 Android 还是很有希望的。这个各位看官可以去网上找找相关的教程,Apache 如何交叉编译到 ARM,想做个伸手党也可以,很多已经编译好了的。
这里举个栗子说说如何在 Android 上运行 httpd for arm,可以先将编译好的 httpd 放入 raw 文件夹中,在 MainActivity 启动时判断是否在指定位置中,没有则释放。我通常是将其放在单独的服务中运行,这样就算 Activity 销毁了,服务还会在后台运行,这也是服务器必备的一个特性。
private File httpd;
@Override
public void onCreate() {
super.onCreate();
httpd = new File(getFilesDir(), "httpd");
if (!httpd.exists()) {
try {
InputStream ins = getResources().openRawResource(R.raw.httpd);
FileIOUtils.writeFileFromIS(httpd, ins);
Runtime.getRuntime().exec("chmod 777 " + httpd.getAbsolutePath());
} catch (Exception e) {
Log.e(TAG, "onCreate: ", e);
}
}
}
在 Android 中有一个 Runtime 类,这个类主要是用来让 Android 应用程序可以与它所在的运行环境进行交互,可以直接通过调用 Runtime.getRuntime() 的静态方法来得到这个类的实例,再调用 exec 就可以执行命令,接下来我创建了一个二进制执行类,对其做了一个简单的封装。
public class BinExecuter {
/**
* 进程 PID
*/
private int pid;
/**
* 可执行二进制文件路径
*/
private String bin;
/**
* 启动参数
*/
private String paras;
/**
* 进程实例
*/
private Process process;
/**
* 获取 PID
* @return
*/
public int getPid() {
return pid;
}
/**
* 构造函数
* @param bin 可执行二进制文件路径
* @param paras 启动参数
*/
public BinExecuter(String bin, String paras) {
this.bin = bin;
this.paras = paras;
}
/**
* 启动进程
*/
public void start() {
try {
process = Runtime.getRuntime().exec(bin + " " + paras);
Field f = process.getClass().getDeclaredField("pid");
f.setAccessible(true);
pid = f.getInt(process);
f.setAccessible(false);
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 结束进程
*/
public void stop() {
if (pid > 0) {
try {
Runtime.getRuntime().exec("kill -9 " + pid);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
但是这还是不够的,像 httpd 这类程序,启动后,控制台会有输出。例如有客户端请求了某个 url,或者出现什么错误,都会显示在控制台上。Android 上是没有控制台窗口的,那么如何捕捉控制台输出呢,简单,重定向输出到输入流中即可。
InputStream outs = process.getInputStream();
InputStreamReader isrout = new InputStreamReader(outs);
BufferedReader brout = new BufferedReader(isrout);
String line;
try {
while ((line = brout.readLine()) != null) {
log.d(line);
}
} catch (Exception ex) {
ex.printStackTrace();
}
注意了,这里有个大歪鹅(while),主线程会被阻塞的,启动另外的线程就行了,改造这个类,增加控制台输出的监听,可以让它变稍微强大一点。
/** author:yahch**/
public interface BinExecuteCallback {
void onConsoleResponse(String text);
}
private BinExecuteCallback binExecuteCallback;
public void setBinExecuteCallback(BinExecuteCallback binExecuteCallback) {
this.binExecuteCallback = binExecuteCallback;
}
在前段时间我开发的一个 Aria2 服务端中的对应用法如下:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
ariaConfig = (AriaConfig) intent.getSerializableExtra("config");
if (ariaConfig != null) {
Log.d(TAG, ariaConfig.toString());
binExecuter = new BinExecuter(fileAria2c.getAbsolutePath(), ariaConfig.toString());
binExecuter.setBinExecuteCallback(new BinExecuter.BinExecuteCallback() {
@Override
public void onConsoleResponse(String text) {
sendMessage(ARIA2_SERVICE_BIN_CONSOLE, text);
}
});
}
} else {
stopSelf();
}
return super.onStartCommand(intent, flags, startId);
}
private void sendMessage(String name, String message) {
MessageEvent genericEvent = new MessageEvent(name, message);
EventBus.getDefault().post(genericEvent);
}
通过 EventBus 把服务中截取的控制台消息抛到 Activity 中,当然也可以使用广播,我觉得 EventBus 还是要好用些。
现在 GO 语言也百花齐放,GO 天生就是为了服务端而生,而且跨平台能力特别强大,在 Github 上已经有很多程序编译为了 ARM 版本的,像 frp、caddy、filebrowser 这些,都可以移植在 Android 上,我们要做的,就是给他一个壳,控制它运行和停止,以及配置些参数。
网友评论