最近的项目中,因为使用Android作为服务端来做一个实时接收数据的功能,所以这次需要做一个Android本地微服务器。所以这个时候,我首先想到了springboot,因为它是一个服务端框架。但实际上我们根本不用这么大的服务器框架,配置这些也太麻烦了。于是,我找了三个框架,Ijetty、NanoHttpd和AndroidAsync,这三个框架比较小,适合Android。对比了一下,ijetty使用起来太复杂了,会莫名其妙的报一些不好解决的问题,所以弃用了。因为没有仔细研究Ijetty,所以重点研究了NanoHttpd和AndroidAsync;然后说下两个的优缺点:1.NanoHttpd是BIO作为底层封装的框架,而AndroidAsync是NIO作为底层封装的,其他的都是一样的,而AndroidAsync其实是在NanoHttpd框架之后编写。因此,从某种意义上说,AndroidAsync是NanoHttpd的优化版,当然也要看具体的应用场景。2、NanoHttpd只能用于HttpServer,但AndroidAsync除了HttpServer应用外,还可以用于webSocket、HttpClient等。其中,从AndroidAsync中分离出来的Ion库也比较有名。3、NanoHttpd的底层处理包含很多返回状态码(例如:200、300、400、500等);但是,笔者在阅读了AndroidAsync的源码后发现,AndroidAsync底层封装返回的状态码只有两种:200、404,正是笔者发现了这个坑(下面会讲到,OPTIONS的例子).我们来看看具体的使用方法。1、先说一下NanoHttpd:因为NanoHttpd的框架其实是一个文件,直接从github上下载即可。如果下载地址有下载的文件,可以继承这个文件,写一个类,如下:publicclassHttpServerextendsNanoHTTPD{privatestaticfinalStringTAG="HttpServer";publicstaticfinalStringDEFAULT_SHOW_PAGE="index.html";publicstaticfinalintDEFAULT_PORT=9511;//这个参数定义随便,最大清晰度是1024-65535;1-1024为系统公共端口,1024-65535为非系统端口publicenumStatusimplementsResponse.IStatus{REQUEST_ERROR(500,"请求失败"),REQUEST_ERROR_API(501,"请求接口无效"),REQUEST_ERROR_CMD(502,"无效命令”);privatefinalintrequestStatus;privatefinalStringdescription;Status(intrequestStatus,Stringdescription){this.requestStatus=requestStatus;this.description=description;}@OverridepublicStringgetDescription(){returndescription;}@OverridepublicintgetRequestStatus(){returnrequestStatus;}}publicHttpServer(){//初始化端口super(DEFAULT_PORT);}@OverridepublicResponseserve(IHTTPSessionsession){StringMapuri=session;headers=session.getHeaders();//无法接收post参数的问题,http://blog.csdn.net/obguy/article/details/53841559try{session.parseBody(newHashMap());}catch(IOExceptione){e.printStackTrace();}catch(ResponseExceptione){e.printStackTrace();}Mapparms=session.getParms();try{LogUtil.d(TAG,uri);//判断uri的合法性,自定义方法,this是判断是否是接口的方法(parms!=null){LogUtil.d(TAG,parms.toString());}if(StringUtil.isEmpty(uri)){thrownewRuntimeException("无法获取请求地址");}if(Method.OPTIONS.equals(session.getMethod())){LogUtil.d(TAG,"OPTIONS探测请求");returnaddHeaderResponse(Response.Status.OK);}switch(uri){case"/test":{//接口2//该方法包括封装返回的接口请求数据并处理异常和跨域returngetXXX(parms);}default:{returnaddHeaderResponse(Status.REQUEST_ERROR_API);}}}else{//用于静态资源处理StringfilePath=getFilePath(uri);//根据url获取文件路径if(filePath==null){LogUtil.d(TAG,"sdcardnotfound");returnsuper.serve(session);}Filefile=newFile(filePath);if(file!=null&&file.exists()){LogUtil.d(TAG,"filepath="+file.getAbsolutePath());//根据文件名返回mimeType:image/jpg,video/mp4,etcStringmimeType=getMimeType(filePath);Responseres=null;InputStreamis=newFileInputStream(file);res=newFixedLengthResponse(Response.Status.OK,mimeType,is,is.available());//下面是跨域参数(因为一般要和h5联调,所以***设置)response.addHeader("Access-Control-Allow-Headers",allowHeaders);response.addHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,HEAD");response.addHeader("Access-Control-Allow-Credentials","true");response.addHeader("Access-Control-Allow-Origin","*");response.addHeader("Access-Control-Max-Age",""+42*60*60);returnres;}else{LogUtil.d(TAG,"filepath="+file.getAbsolutePath()+"资源没有exist");}}}catch(Exceptione){e.printStackTrace();}//自行封装返回请求returnaddHeaderRespose(Status.REQUEST_ERROR);}根据上面的ex充足,主要是为了有以下几点:1)可以接收到请求,不管是post还是get,或者其他请求,如果需要过滤,可以自己处理;2)注意上述处理中无法接收post参数的问题,参考链接已在代码注释中给出,请参考;3)如果请求中既有接口又有静态资源(比如html),那么要注意区分这两种请求,比如可以用uri来识别;当然,返回可以是流的形式,可以调用API方法newFixedLengthResponse();4)笔者建议大家先处理好跨域问题,因为Android可能会和h5联合调试,所以设置跨域后调试更方便,当然有些场景也可以忽略,见个人需要;方法已经写在上面的代码中;5)当然***最重要的一点肯定是开启关闭的代码了:/***开启本地网页点歌服务*/publicstaticvoidstartLocalChooseMusicServer(){if(httpServer==null){httpServer=newHttpServer();}try{//启动网络服务if(!httpServer.isAlive()){httpServer.start();}Log.i(TAG,"Theserverstarted.");}catch(Exceptione){httpServer.stop();Log.e(TAG,"Theservercouldnotstart.e="+e.toString());}}/***关闭本地服务*/publicstaticvoidquitChooseMusicServer(){if(httpServer!=null){if(httpServer.isAlive()){httpServer.stop();Log.d(TAG,"关闭局域网点餐服务");}}}2再看AndroidAsync:这个框架比较有意思,功能也很多。本文直接讲了HttpServer的相关知识,其他的就不展示了。老规矩,先说说用法:在Gradle中添加:dependencies{compile'c??om.koushikdutta.async:androidasync:2.2.1'}代码示例:(此处不做跨域处理,如有需要请参考previousexampleto处理)publicclassNIOHttpServerimplementsHttpServerRequestCallback{privatestaticfinalStringTAG="NIOHttpServer";privatestaticNIOHttpServermInstance;publicstaticintPORT_LISTEN_DEFALT=5000;AsyncHttpServerserver=newAsyncHttpServer();publicstaticNIOHttpServergetInstance(){if(mInstance==null){//增加类锁,保证只初始化一次synchronized(NIOHttpServer.class){if(mInstance==null){mInstance=newNIOHttpServer();}}}returnmInstance;}//模仿nanohttpdpublicstaticenumStatus{REQUEST_OK(200,"请求成功"),REQUEST_ERROR(500,"请求失败"),REQUEST_ERROR_API(501,“无效的请求接口”),REQUEST_ERROR_CMD(502,“无效的命令”),REQUEST_ERROR_DEVICEID(503,“不匹配的设备ID”),REQUEST_ERROR_ENV(504,“不匹配的服务环境”);privatefinalintrequestStatus;privatefinalStringde脚本;状态(intrequestStatus,Stringdescription){this.requestStatus=requestStatus;this.description=description;}publicStringgetDescription(){returndescription;}publicintgetRequestStatus(){returnrequestStatus;}}/***开启本地服务*/publicvoidstartServer(){//如果有其他请求方式,比如下面这行代码server.addAction("OPTIONS","[\\d\\D]*",this);server.get("[\\d\\D]*",this);server.post("[\\d\\D]*",this);server.listen(PORT_LISTEN_DEFALT);}@OverridepublicvoidonRequest(AsyncHttpServerRequestrequest,AsyncHttpServerResponseresponse){Log.d(TAG,"进来吧,哈哈");Stringuri=request.getPath();//This是获取header参数的地方,所以一定要记住Multimapheaders=request.getHeaders().getMultiMap();if(checkUri(uri)){//对于接口的处理//注意:这个地方是获取post请求的参数,一定要牢记Multimaparms=((AsyncHttpRequestBody)request.getBody()).get();if(headers!=null){LogUtil.d(TAG,headers.toString());}if(parms!=null){LogUtil.d(TAG,"parms="+parms.toString());}if(StringUtil.isEmpty(uri)){thrownewRuntimeException("无法获取请求的地址");}if("OPTIONS".toLowerCase().equals(request.getMethod().toLowerCase())){LogUtil.d(TAG,"OPTIONS探测请求");addCORSHeaders(Status.REQUEST_OK,response);return;}switch(uri){case"/test":{//Interface2//该方法包括封装返回的接口请求数据和处理异常和跨域getFilePath(uri);//根据url获取文件路径if(filePath==null){LogUtil.d(TAG,"sdcardnotfound");response.send("sdcardnotfound");return;}Filefile=newFile(filePath);if(file!=null&&file.exists()){Log.d(TAG,"filepath="+file.getAbsolutePath());response.sendFile(file);//和nanohttpd}else{Log.d(TAG,"filepath="+file.getAbsolutePath()+"资源不存在");}}}}的区别根据上面的例子,主要有以下几点:{大概是关于API的使用}1)例如:server.addAction(“OPTIONS”,“[\d\D]”,this)是一个通用的过滤请求的方法,最后一个参数是请求的方法,比如“OPTIONS”、“DELETE”、“POST”、“GET”等(注意大小写),第二个参数是过滤uri的正则表达式,这里是过滤所有的uri,第三个是回调参数。server.post("[\d\D]",this),server.get("[\d\D]*",this)是前面方法的特例。server.listen(PORT_LISTEN_DEFALT)这是监听端口;2)request.getHeaders().getMultiMap()这是获取header参数的地方,一定要记住;3)((AsyncHttpRequestBody)request.getBody()).get()是获取post请求参数的地方;4)获取静态资源的代码在回调方法onResponse的else中,例子如上。5)说一下OPTIONS的坑,因为在AndroidAsync框架中封装的返回http的状态码只有两个。如果过滤方式不包含OPTIONS等请求方式,返回给客户端的http状态码其实是400,而浏览器反映的错误信息原来是跨域问题,拖了半天找到它,请注意。总结:1)同一个页面:NanoHttpd耗时:1.4sAndroidAsync耗时:1.4s但是第二次进入时,AndroidAsync的耗时明显比第一次少。估计是AndroidAsync底层做了一些处理。2)从api分析,NanoHttpd的使用更方便,获取传参的api更易用;AndroidAsync的api相对复杂一些,比如params的获取。3)从场景分析,如果需要高并发量,必须使用AndroidAsync;但如果不需要,那就详细分析。