轉(zhuǎn)自嚴(yán)振杰的博客:http://blog.yanzhenjie.com

大家好,今天跟大家介紹一個(gè)讓原生Android也可以做Web開(kāi)發(fā)的開(kāi)源項(xiàng)目——AndServer点晴。
開(kāi)源地址:https://github.com/yanzhenjie/AndServer
AndServer
是一個(gè)Android端的Web服務(wù)器选侨,類(lèi)似Apache或者Tomcat完沪,但又有不同戏罢,它是一個(gè)普通的Android Library烁兰,Android項(xiàng)目Gradle遠(yuǎn)程依賴(lài)或者添加Jar包皆可引入該項(xiàng)目减响,然后就通過(guò)正常Android開(kāi)發(fā)一樣開(kāi)發(fā)App了靖诗。
AndServer
是用純Android API寫(xiě)一個(gè)庫(kù),所以不用任何第三方的庫(kù)或者什么硬件編譯支示,打成Jar包后僅僅580kb刊橘。
很多人看到這里就有疑問(wèn)了:**它的使用場(chǎng)景是什么?
**按照國(guó)際慣例颂鸿,我舉個(gè)栗子:
某公司公開(kāi)了一款TV APP促绵,可以安裝在電視或者盒子上,有一個(gè)功能是這樣的: APP安裝在電視上打開(kāi)后据途,會(huì)生成一個(gè)唯一ID绞愚,用戶用微信掃碼通過(guò)公司提供的公眾號(hào)和該電視上的該APP綁定,用戶通過(guò)公眾號(hào)打開(kāi)一個(gè)H5頁(yè)面颖医,這個(gè)頁(yè)面可以上傳圖片或者視頻到服務(wù)器位衩,服務(wù)器檢測(cè)到該用戶和某個(gè)屏幕的APP綁定,動(dòng)態(tài)把用戶發(fā)送的圖片或者視頻發(fā)送到電視播放熔萧。 這樣一個(gè)功能是很好的體驗(yàn)糖驴,但是耗費(fèi)服務(wù)器資源、遠(yuǎn)端上傳佛致、遠(yuǎn)端下載也都需要時(shí)間贮缕,還不如我直接用U盤(pán)拷貝到電視呢。那么如果我通過(guò)網(wǎng)頁(yè)在局域網(wǎng)直接把視頻或者圖片發(fā)送到電視上的APP播放俺榆,豈不是更加直接快捷感昼?
當(dāng)然大部分同學(xué)可能很少接觸到APP之間互相在局域網(wǎng)通信、局域網(wǎng)上傳下載罐脊、局域網(wǎng)登錄這樣的需求定嗓,還有很多栗子我就不舉了,下面開(kāi)始正式安利萍桌。
特點(diǎn)
接受客戶端文件上傳宵溅、下載文件。
動(dòng)態(tài)Http API上炎,就像Java的Servlet一樣寫(xiě)接口恃逻。
部署靜態(tài)網(wǎng)站,例如純Html,支持JS寇损、CSS凸郑、Image分離。
部署動(dòng)態(tài)網(wǎng)站润绵,例如Html表單线椰,當(dāng)然可以結(jié)合上面的Android Http接口。
基本上和Java的Servlet一樣的功能尘盼,如果你做過(guò)Java開(kāi)發(fā)或者其它語(yǔ)言的Web開(kāi)發(fā)應(yīng)該就很熟悉了。
依賴(lài)
Gradle
compile 'com.yanzhenjie:andserver:1.0.2'
Maven
<dependency>
<groupId>com.yanzhenjie</groupId>
<artifactId>andserver</artifactId>
<version>1.0.2</version>
<type>pom</type>
</dependency>
ADT烦绳,可以去AndServer主頁(yè)下載Jar包卿捎。
使用方法
最好的教程是sample,建議到AndServer主頁(yè)下載sample運(yùn)行查看效果径密,然后結(jié)合README就更加清晰了午阵。
創(chuàng)建服務(wù)器
AndServer andServer = new AndServer.Build()
...
.build();
// 創(chuàng)建服務(wù)器。
Server mServer = andServer.createServer();
...
// 啟動(dòng)服務(wù)器享扔。
mServer.start();
...
// 停止服務(wù)器底桂。
mServer.stop();
...
// 服務(wù)器正在運(yùn)行嗎?
boolean running = mServer.isRunning();
端口號(hào)和響應(yīng)超時(shí)設(shè)置
AndServer andServer = new AndServer.Build()
.port(8080) // 默認(rèn)是8080惧眠,Android平臺(tái)允許的端口號(hào)都可以籽懦。
.timeout(10 * 1000) // 默認(rèn)10 * 1000毫秒。
...
.build();
...
部署網(wǎng)站
部署網(wǎng)站是通過(guò)Website接口氛魁,你也可以自己實(shí)現(xiàn)這個(gè)接口暮顺,當(dāng)然AndServer
已經(jīng)提供了兩個(gè)默認(rèn)實(shí)現(xiàn):
AssetsWebsite
StorageWebsite
如果用上面兩個(gè)實(shí)現(xiàn)注冊(cè)你的網(wǎng)站,那么你的默認(rèn)首頁(yè)(index.html)是:
http://ip:port/
http://ip:port/youPath
http://ip:port/youPath/index.html
注冊(cè)網(wǎng)站到AndServer
// 或者
Wesite wesite = new StorageWebsite(youPath);
AndServer andServer = new AndServer.Build()
...
.website(wesite);
.build();
AssetsWebsite的使用
如果你的網(wǎng)站在assets下秀存,那么你就用AssetsWebsite來(lái)部署你的網(wǎng)站捶码。使用方法是:
//AssetManager不能被關(guān)閉。
AssetManager mAssetManager = getAssets();
Wesite wesite = new AssetsWebsite(mAssetManager, youPath);
上面我們看到new AssetsWebsite時(shí)需要傳一個(gè)AssetManager和一個(gè)path或链,path支持assets根目錄和子目錄惫恼,下面是這兩種情況的舉例。如果你的網(wǎng)站在assets根目錄下, 你的path就填""澳盐,比如:

Wesite wesite = new AssetsWebsite(mAssetManager, "");
那么你的默認(rèn)首頁(yè)訪問(wèn)地址就是:
http://ip:porthttp://ip:port/index.html
那么你的其它頁(yè)面訪問(wèn)地址是:
http://ip:port/login.htmlhttp://ip:port/error.html
比如:
http://192.168.1.12:8080/index.html
http://192.168.1.12:8080/login.html
```
如果你的網(wǎng)站根目錄在assets的子目錄下祈纯,那么你傳入assets的相對(duì)目錄地址就好了比如你的網(wǎng)站在assets下web目錄,例如:
(https://dn-mhke0kuv.qbox.me/bd0a4a7041e8e8e5e35c.png)
```
Wesite wesite = new AssetsWebsite(mAssetManager, "web");
```
那么你的默認(rèn)首頁(yè)訪問(wèn)地址就是:
```
http://ip:porthttp://ip:port/webhttp://ip:port/web/index.html
```
那么你的其它頁(yè)面訪問(wèn)地址是:
```
http://ip:port/web/login.html http://ip:port/web/error.html
```
例如:
```
http://192.168.1.12:8080/
http://192.168.1.12:8080/index.html
http://192.168.1.12:8080/web/index.html
http://192.168.1.12:8080/web/index.html
http://192.168.1.12:8080/web/login.html
```
###StorageWebsite的使用
如果你的網(wǎng)站在內(nèi)存設(shè)備下洞就,只要以文件的形式可以讀取到盆繁,那么你就用StorageWebsite來(lái)部署你的網(wǎng)站,比如你的網(wǎng)站在SD卡下時(shí)旬蟋。使用方法是:
```
Wesite wesite = new StorageWebsite(youPath);
```
它很簡(jiǎn)單油昂,只要傳入你的網(wǎng)站的存儲(chǔ)目錄地址即可,例如你的網(wǎng)站在SD卡下的www目錄:
```
File file = new File(Environment.getExternalStorageDirectory(), "www");
String websiteDirectory = file.getAbsolutePath();
Wesite wesite = new StorageWebsite(websiteDirectory);
```
訪問(wèn)地址和AssetsWebsite的道理相同。
###像Servlet一樣寫(xiě)Http接口
Http API是通過(guò)RequestHandler接口來(lái)注冊(cè)的冕碟,它是一個(gè)[Java ](http://lib.csdn.net/base/java)interface拦惋,它和Java的Servlet一樣。你需要實(shí)現(xiàn)這個(gè)接口安寺,然后在AndServer注冊(cè)即可厕妖,
例如:
```
public class RequestLoginHandler implements RequestHandler {
@Override
public void handle(HttpRequest req, HttpResponse res, HttpContext con) {
Map<String, String> params = HttpRequestParser.parse(request);
// Request params.
String userName = params.get("username");
String password = params.get("password");
if ("123".equals(userName) && "123".equals(password)) {
StringEntity stringEntity = new StringEntity("Login Succeed", "utf-8");
response.setEntity(stringEntity);
} else {
StringEntity stringEntity = new StringEntity("Login Failed", "utf-8");
response.setEntity(stringEntity);
}
}
}
```
然后在AndServer中注冊(cè):
```
AndServer andServer = new AndServer.Build()
...
.registerHandler("login", new RequestLoginHandler())
.build();
```
現(xiàn)在你就得到了一個(gè)唯一的訪問(wèn)地址:http://ip:port/login, 例如:
```
http://192.168.1.12:8080/login?username=123&password=123
```
文件下載和文件上傳的例子請(qǐng)下載sample查看。提交Html表單到Android端在Html的form的action中填入你注冊(cè)RequestHandler時(shí)的key就可以了挑庶,然后在RequestHandler的
```
handle(HttpRequest, HttpResponse, HttpContext)
```
方法就可以獲取form提交的參數(shù)了言秸。比如我們上面注冊(cè)Login RequestHandler在form中這樣使用:
```
<form id="form1" method="post" action="login">
...
</form>
```
###監(jiān)聽(tīng)服務(wù)器的狀態(tài)
服務(wù)器一般情況下有三種狀態(tài):成功啟動(dòng)、啟動(dòng)時(shí)失敗迎捺、成功停止服務(wù)器举畸,失敗時(shí)會(huì)返回一個(gè)異常,一般情況下是網(wǎng)絡(luò)問(wèn)題或者端口被占用
```
private Server.Listener mListener = new Server.Listener() {
@Override
public void onStarted() {
// 服務(wù)器啟動(dòng)成功.
}
@Override
public void onStopped() {
// 服務(wù)器停止了凳枝,一般是開(kāi)發(fā)者調(diào)用server.stop()才會(huì)停止抄沮。
}
@Override
public void onError(Exception e) {
// 服務(wù)器啟動(dòng)發(fā)生錯(cuò)誤,一般是端口被占用岖瑰。
}
};
AndServer andServer = new AndServer.Build()
...
.listener(mListener)
.build();
```