在 Android 設備上搭建 Web 服務器--AndServer,不用註解的方式

一般而言,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

作者:葉志陳
鏈接:https://www.jianshu.com/p/6d2f324c8f42
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章