在 Android 设备上搭建 Web 服务器

一般而言,Android 应用在请求数据时都是以 Get 或 Post 等方式向远程服务器发起请求,那你有没有想过其实我们也可以在 Android 设备上搭建一个小型 Web 服务器,并且实现常规的下载图片、下载文件、提交表单等功能呢? 下面要介绍的就是如何在 Android 设备上搭建一个 Web 服务器,这个 Web 服务器的功能有如下几点:

  1. 接受客户端文件上传、下载文件
  2. 动态 Http API,像 Java 的 Servlet 一样写接口
  3. 部署静态网站,例如纯Html,支持 JS、CSS、Image 分离
  4. 部署动态网站

这需要依赖一个开源库来实现:AndServer

AndServer 类似于 Apache 和 Tomcat,支持在同个局域网下的设备能够以常规的网络请求方式来向 Web 服务器请求数据,只要指明 Web 服务器的 IP 地址和端口号即可

那么,这个 Web 服务器的用途有哪些呢?

说下我现在遇到的一个需求吧!需要实现两台设备(Android 或 ios 设备)在无网络情况下进行数据交流。本来是打算让设备之间的交流通道以 Wifi 来链接,即某一台设备连上另一台设备的 Wiif 热点,这样两者之间就建立起了一条“通道”,之后通过建立 Socket 连接来获取输入输出流,通过输入输出流来交换数据。可是这样就需要处理好在高并发情况下的数据同步和解析问题,比较麻烦,而通过 AndServer 就可以直接套用项目已有的网络请求框架,直接以网络请求的方式来交流数据,而服务端也较好的处理了并发问题

Gradle 远程依赖

implementation 'com.yanzhenjie:andserver:1.1.3'

搭建服务器

搭建服务器的方式很简单,支持链式调用。指明服务器在本机的 IP 地址上监听,并指定端口号为 1995 ,并开放了三个接口分别用于:下载文件、下载图片、Post表单

       AndServer server = AndServer.serverBuilder()
                .inetAddress(NetUtils.getLocalIPAddress())  //服务器要监听的网络地址
                .port(Constants.PORT_SERVER) //服务器要监听的端口
                .timeout(10, TimeUnit.SECONDS) //Socket超时时间
                .registerHandler(Constants.GET_FILE, new DownloadFileHandler()) //注册一个文件下载接口
                .registerHandler(Constants.GET_IMAGE, new DownloadImageHandler()) //注册一个图片下载接口
                .registerHandler(Constants.POST_JSON, new JsonHandler()) //注册一个Post Json接口
                .filter(new HttpCacheFilter()) //开启缓存支持
                .listener(new Server.ServerListener() {  //服务器监听接口
                    @Override
                    public void onStarted() {
                        String hostAddress = server.getInetAddress().getHostAddress();
                        Log.e(TAG, "onStarted : " + hostAddress);
                        ServerPresenter.onServerStarted(ServerService.this, hostAddress);
                    }

                    @Override
                    public void onStopped() {
                        Log.e(TAG, "onStopped");
                        ServerPresenter.onServerStopped(ServerService.this);
                    }

                    @Override
                    public void onError(Exception e) {
                        Log.e(TAG, "onError : " + e.getMessage());
                        ServerPresenter.onServerError(ServerService.this, e.getMessage());
                    }
                })
                .build();

开启服务器

server.startup();

停止服务器

server.shutdown();

接口处理器

在注册接口时,除了指明开放出来的 Url 地址外,还需要指明相应的处理器,专门用于处理该接口的请求操作 开放出来的三个接口分别对应于三个地址

public class Constants {

    //服务端接口的端口号
    public static final int PORT_SERVER = 1995;

    public static final String GET_FILE = "/file";

    public static final String GET_IMAGE = "/image";

    public static final String POST_JSON = "/json";

}
 ···
 .registerHandler(Constants.GET_FILE, new DownloadFileHandler()) //注册一个文件下载接口
 .registerHandler(Constants.GET_IMAGE, new DownloadImageHandler()) //注册一个图片下载接口
 .registerHandler(Constants.POST_JSON, new JsonHandler()) //注册一个Post Json接口
 ···

例如,假设设备的 IP 地址是:192.168.0.101 ,那么在访问 http://192.168.0.101:1995/file 接口时,请求操作就会由 DownloadFileHandler 来处理

下载文件

DownloadFileHandler 实现了 RequestHandler 接口,在 handle 方法中可以获取到请求头,表单数据这些信息,,通过注解声明支持 Get 方式调用,在此直接返回一个文本文件用于下载

/**
 * 作者:leavesC
 * 时间:2018/4/5 16:30
 * 描述:https://github.com/leavesC/AndroidServer
 * https://www.jianshu.com/u/9df45b87cfdf
 */
public class DownloadFileHandler implements RequestHandler {

    @RequestMapping(method = {RequestMethod.GET})
    @Override
    public void handle(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) throws HttpException, IOException {
        File file = createFile();
        HttpEntity httpEntity = new FileEntity(file, ContentType.create(getMimeType(file.getAbsolutePath()), Charset.defaultCharset()));
        httpResponse.setHeader("Content-Disposition", "attachment;filename=File.txt");
        httpResponse.setStatusCode(200);
        httpResponse.setEntity(httpEntity);
    }

    private File createFile() {
        File file = null;
        OutputStream outputStream = null;
        try {
            file = File.createTempFile("File", ".txt", MainApplication.get().getCacheDir());
            outputStream = new FileOutputStream(file);
            outputStream.write("leavesC,这是一段测试文本".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return file;
    }

}

这里直接在浏览器中访问接口(要和 Android Web服务器运行在同个局域网下),可以直接下载到文件

下载图片

类似的,下载图片的接口处理器 DownloadImageHandler 可以如下设计,在 handle 方法中返回应用的图标

/**
 * 作者:leavesC
 * 时间:2018/4/5 16:30
 * 描述:https://github.com/leavesC/AndroidServer
 * https://www.jianshu.com/u/9df45b87cfdf
 */
public class DownloadImageHandler extends SimpleRequestHandler {

    private File file = new File(Environment.getExternalStorageDirectory(), "leavesC.jpg");

    @RequestMapping(method = {RequestMethod.GET})
    @Override
    protected View handle(HttpRequest request) throws HttpException, IOException {
        writeToSdCard();
        HttpEntity httpEntity = new FileEntity(file, ContentType.create(getMimeType(file.getAbsolutePath()), Charset.defaultCharset()));
        return new View(200, httpEntity);
    }

    private void writeToSdCard() throws IOException {
        if (!file.exists()) {
            synchronized (DownloadImageHandler.class) {
                if (!file.exists()) {
                    boolean b = file.createNewFile();
                    if (!b) {
                        throw new IOException("What broken cell phone.");
                    }
                    Bitmap bitmap = BitmapFactory.decodeResource(MainApplication.get().getResources(), R.mipmap.ic_launcher_round);
                    OutputStream outputStream = null;
                    try {
                        outputStream = new FileOutputStream(file);
                        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } finally {
                        if (outputStream != null) {
                            outputStream.flush();
                            outputStream.close();
                        }
                    }
                }
            }
        }
    }

}
Post表单

这里需要将注解值改为 RequestMethod.POST,通过 HttpRequestParser.getContentFromBody(httpRequest) 函数可以获取到表单数据,这里直接检测表单数据是否为 Json 字符串,是的话则为之多添加一个属性 :"state" 作为返回值,否则返回只包含属性 “state” 的 Json 字符串

/**
 * 作者:leavesC
 * 时间:2018/4/5 16:30
 * 描述:https://github.com/leavesC/AndroidServer
 * https://www.jianshu.com/u/9df45b87cfdf
 */
public class JsonHandler implements RequestHandler {
    
    @RequestMapping(method = {RequestMethod.POST})
    @Override
    public void handle(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) throws HttpException, IOException {
        String content = HttpRequestParser.getContentFromBody(httpRequest);
        JSONObject jsonObject = null;
        try {
            jsonObject = new JSONObject(content);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if (jsonObject == null) {
            jsonObject = new JSONObject();
        }
        try {
            jsonObject.put("state", "success");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        StringEntity stringEntity = new StringEntity(jsonObject.toString(), "utf-8");
        httpResponse.setStatusCode(200);
        httpResponse.setEntity(stringEntity);
    }

}

这里在 Postman 这个工具上进行 Post 操作

以上三个例子都是在电脑端调用的,这和在手机端调用是同个效果的

基本的操作就介绍到这里,再具体的内容可以看示例代码:AndroidServer

欢迎关注我的简书账号:叶应是叶

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏乐沙弥的世界

WSREP has not yet prepared node for application use

最近PXC 5.7出现脑裂,前端Navicate连接到MySQL时,提示WSREP has not yet prepared node for applicat...

58330
来自专栏潇涧技术专栏

Android Asynchronous Http Client

本文总结了著名的Android的异步网络请求库Asynchronous Http Client的使用

10510
来自专栏皮皮之路

【Spring】Spring boot多数据源历险记

540160
来自专栏bboysoul

开源堡垒机jumpserver搭建

之前说了国产良心kodexplorer,今天再说一个国内比较好的开源项目jumpserver,除此之外还可以的国内开源项目我觉得就是宝塔面板了。废话不多说上教程...

92930
来自专栏白驹过隙

Protobuf - 使用scons编译proto文件

20070
来自专栏白驹过隙

Protobuf - 使用scons编译proto文件

483160
来自专栏黑白安全

Jenkins拿shell方法

最近一直在学习怎么利用jenkins反序列化,妹的,就是没有一个顺手的GUI工具,能让我直接秒杀服务器。

56220
来自专栏JadePeng的技术博客

Docker+Jenkins持续集成环境(3)集成PMD、FindBugs、Checkstyle静态代码检查工具并邮件发送检查结果

为了规范代码,我们一般会集成静态代码检测工具,比如PMD、FindBugs、Checkstyle,那么Jenkins如何集成这些检查工具,并把检查结果放到构建邮...

54830
来自专栏移动开发之家

Android插件化快速入门与实例解析(VirtualApk)

集成一个第三方相册功能,只需集成一个插件APK到项目中,无需集成额外代码,并且支持随时更新相册功能,无需发布版本更新,无需AndroidManifest中声明四...

7720
来自专栏恰童鞋骚年

.NET Core微服务之服务间的调用方式(REST and RPC)

  微服务之间的接口调用通常包含两个部分,序列化和通信协议。常见的序列化协议包括json、xml、hession、protobuf、thrift、text、by...

27960

扫码关注云+社区

领取腾讯云代金券