一般而言,Android 應用在請求資料時都是以 Get 或 Post 等方式向遠端伺服器發起請求,那你有沒有想過其實我們也可以在 Android 裝置上搭建一個小型 Web 伺服器,並且實現常規的下載圖片、下載檔案、提交表單等功能呢? 下面要介紹的就是如何在 Android 裝置上搭建一個 Web 伺服器,這個 Web 伺服器的功能有如下幾點:
- 接受客戶端檔案上傳、下載檔案
- 動態 Http API,像 Java 的 Servlet 一樣寫介面
- 部署靜態網站,例如純Html,支援 JS、CSS、Image 分離
- 部署動態網站
這需要依賴一個開源庫來實現: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 操作
以上三個例子都是在電腦端呼叫的,這和在手機端呼叫是同個效果的