我的App-帝都地铁
来源:互联网 发布:python 余弦相似度 编辑:程序博客网 时间:2024/04/25 15:13
1.简介
前一段时间看了包建强老师的《App研发录》,决定将自己写的北京地铁换乘App重构一下,并更名为”帝都地铁”。
此版本将与业务无关的逻辑封装成subwaylib类库,并手写了”网络请求”和”图片加载”模块,优化了”线路搜索”的代码,个人觉得代码质量还是OK的。
Github地址:帝都地铁源码
2.网络请求
发起网络请求的代码示例(金山词霸的每日一句Api):
final AppRequestCallback callback = new AppRequestCallback() { @Override public void onSuccess(String content) { Sentence sentence = JSON.parseObject(content, Sentence.class); if (sentence != null) { ImageLoader.getInstance().displayImage(sentence.getPicture(), ivPicture); tvContent.setText(sentence.getContent()); tvNote.setText(sentence.getNote()); } }};AppHttpRequest.getInstance().performRequest(this, "dsapi", null, callback);
项目中所有的网络请求的配置信息都写在本地xml文件中:
<?xml version="1.0" encoding="UTF-8"?><url> <Node Key="dsapi" Expires="21600" NetType="get" MockClass = "" Url="http://open.iciba.com/dsapi/"/></url>
AppHttpRequest执行请求时,会根据Key值取出网络请求的配置信息,包括:缓存时间(Expires),请求方式(NetType),模拟数据(MockClass),接口地址(Url)。
final URLData urlData = UrlConfigManager.findURL(activity, apiKey);
模拟数据不为空,则进行本地测试,为空则RequestManager创建Request,RequestThreadPool执行Request。
Request request = activity.getRequestManager().createRequest(urlData, params, callback);RequestThreadPool.getInstance().execute(request);
Request是一个实现Runnable的抽象类,包含三个抽象方法:
protected abstract void doGet();protected abstract void doPost();protected abstract void abort();
分别以HttpClient和HttpURLConnection方式实现了Request,项目中默认使用HttpURLConnection方式获取网络请求数据。
public Request createRequest(final URLData urlData, final List<RequestParameter> parameters, final RequestCallback requestCallback) { final Request request = new HurlRequest(urlData, parameters, requestCallback); addRequest(request); return request;}
当以Get方式请求数据时,如果参数不为空,则先拼接参数:
if ((mParameters != null) && (mParameters.size() > 0)) { mUrl = mUrl + HOST_PARAMS_SEPARATOR + formatRequestParams();}
如果缓存时间大于0,则取缓存数据:
if (mExpires > 0) { strCacheContent = CacheManager.getInstance().getFileCache(mUrl);}
如果缓存数据不为空,则返回缓存数据,为空则创建HttpURLConnection连接,获取接口数据,并将数据写入缓存。
if (urlConn.getResponseCode() == HttpURLConnection.HTTP_OK) { // 保存Coocie storeCookie(); // 获取返回的数据 is = urlConn.getInputStream(); String response = BaseUtils.InputStream2String(is); is.close(); // 把成功获取到的数据记录到缓存 if (mExpires > 0) { CacheManager.getInstance().putFileCache(mUrl, response, mExpires); } // 处理返回信息 doResponse(response);}
以上即为执行一次网络请求的流程。
3.图片加载
加载网络图片的代码示例:
ImageLoader.getInstance().displayImage(sentence.getPicture(), ivPicture);
ImageLoader中会分别尝试从内存和硬盘中获取Bitmap,如果取不到则执行ImageRequest:
public void displayImage(final String imageUrl, final ImageViewWrapper imageViewWrapper, final ImageLoadingListener listener) { Bitmap bitmap; if (mImageCache != null) { // 从内存中获取Bitmap bitmap = mImageCache.getBitmapFromMemCache(imageUrl); if (bitmap != null) { imageViewWrapper.setImageBitmap(bitmap); return; } // 从硬盘中获取Bitmap bitmap = mImageCache.getBitmapFromDiskCache(imageUrl);[http://](http://) if (bitmap != null) { mImageCache.addBitmapToMemCache(imageUrl, bitmap); imageViewWrapper.setImageBitmap(bitmap); return; } } // 网络请求Bitmap ImageRequest request = new ImageRequest(imageUrl, imageViewWrapper, listener, mImageCache); ImageThreadPool.getInstance().submit(request);}
ImageRequest实现了Runnable接口,使用HttpURLConnection下载网络图片,并对图片进行inSampleSize处理。开发过程中遇到了这样一个问题:HttpURLConnection获取的InputStream只能被BitmapFactory.decodeStream处理一次:
BitmapFactory.decodeStream returning null when options are set
解决方案是先将InputStream转换为ByteArrayOutputStream,当使用时在转回为InputStream:
// 将InputStream转换为ByteArrayOutputStreambaos = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) > -1) { baos.write(buffer, 0, len);}baos.flush();final BitmapFactory.Options options = BitmapDecoder.getBitmapFactoryOptions( new ByteArrayInputStream(baos.toByteArray()), mImageViewWrapper.getWidth(), mImageViewWrapper.getHeight());final Bitmap bitmap = BitmapDecoder.decodeBitmapFromInputStream(new ByteArrayInputStream(baos.toByteArray()), options);
顺便再说一个小插曲,测试图片缓存的时候,需要断网来测试图片缓存和加载。有一次发现图片加载总是失败,debug一顿找,发现connect时总是异常,顿时不觉明历,最后恍然醒悟,wifi让我关了没开,哎,程序员真是苦啊。
4.线路搜索
本期将线路搜索的代码都写在SubwayMap类中,这样看起来也更直观。
首先,分别获取起点终点车站的车站ID集合:
// 起点车站名对应的车站ID集合List<String> lstFromStationIds = mStationDao.getStationIdsByStationName(fromStationName);// 终点车站名对应的车站ID集合List<String> lstToStationIds = mStationDao.getStationIdsByStationName(toStationName);
这里要说明一下,比如说军事博物馆站是一个换乘车站,它有两个车站ID,分别为0109和0904,车站ID前两位表示车站所在线路,即军事博物馆站属于1号线和9号线,当以军事博物馆站为起点站查询时需要考虑分别从1号线和9号线为起点线路向其他线路换乘,因此需要取出车站名对应的车站ID集合,再根据车站ID集合获取起点终点线路集合。
接着,获取起点终点线路集合,比如从丰台科技园到北京站,则[09,02]:
List<String[]> lstFromToLineIds = getFromToLineIds(lstFromStationIds, lstToStationIds);
接着,获取起点到终点换乘路线详细信息,[09,01,02],[09,06,02],[09,04,02]:
List<String[]> lstTransferRouteLineIds = getFromToTransferRouteLineIds(lstFromToLineIds);
接着,遍历起点到终点换乘路线详细信息,以此加载换乘数据,并获取换乘详细信息:
TransferDetail transferDetail = new TransferDetail();transferDetail.fromStationName = mFromStationName;transferDetail.toStationName = mToStationName;transferDetail.lstTransferRoute = new ArrayList<>();for (String[] lids : lstTransferRouteLineIds) { // 构建临接表建图 createSubwayMap(lids, lstFromStationIds.get(0), lstToStationIds.get(0)); // 从图中搜索两点之间最短距离 TransferRoute transferRoute = searchTransferRoute(lstFromStationIds.get(0), lstToStationIds.get(0)); // 添加换乘路线 updateTransferDetail(transferDetail, transferRoute);}
这里也要说明一下,军事博物馆有两个车站ID,但构建地铁图时只能使用唯一的一个,因此选择车站ID的最小值为图中的车站ID。
最后,返回换乘详情。
transferDetail.ticketPrice = transferDetail.lstTransferRoute.get(0).ticketPrice;return transferDetail;
其中搜索换乘路线详细信息时用到了深度优先搜索算法:
private void DFS(final int from, final int to) { if (SubwayData.LINE_TRANSFERS[from][to] == 1) { int i = 0; String[] lineIds = new String[mStack.size() + 2]; for (int index : mStack) { lineIds[i++] = SubwayData.LINE_EDGES[index]; } lineIds[i++] = SubwayData.LINE_EDGES[from]; lineIds[i] = SubwayData.LINE_EDGES[to]; mLstTransferRouteLineIds.add(lineIds); } else { mStack.push(from); isVisited[from] = true; for (int i = 0; i < SubwayData.LINE_EDGES.length; i++) { if (!isVisited[i] && SubwayData.LINE_TRANSFERS[from][i] == 1 && mStack.size() < mMinTransferTimes) { DFS(i, to); } } isVisited[from] = false; mStack.pop(); }}
搜索两点之间最短距离用到了迪杰斯特拉算法:
int cur = 0, min, tmp;while (!visited[toStationIndex]) { // 寻找当前最小的路径 min = Integer.MAX_VALUE; for (int i = 0; i < size; i++) { if (!visited[i] && distance[i] < min) { min = distance[i]; cur = i; } } // 标记已获取到最短路径 visited[cur] = true; // 修正当前最短路径和前驱结点 for (int i = 0; i < size; i++) { tmp = getFromToDistanceByHeadIndex(cur, i); if (tmp != Integer.MAX_VALUE) { tmp += min; } if (!visited[i] && tmp < distance[i]) { distance[i] = tmp; previous[i] = cur; } }}
以上即为整个项目的大致介绍,具体请看源码。
- 我的App-帝都地铁
- 我眼里的帝都和她哺育的孩子们
- 深圳地铁APP伴您行,妥妥的
- 我如何从一个本科双非报考帝都985最后调剂到帝都221的故事
- 我为啥呆在帝都
- 我在帝都那几年
- 来帝都的时间
- 帝都的所思所想
- 帝都
- 帝都
- 帝都金融民工的一天
- 正在开发一个地铁监控的程序,累死我了
- [面试] 帝都的传说2 - 自我整理!
- 帝都攒钱买房的哥们(zz)
- 在帝都坐车的那些事
- Ng 的机器学习在帝都报告
- 回顾+纪念:离开帝都的第一年
- 地铁的滚梯
- day3Scala函数式编程
- Spring Aop详解
- January 2016 English summary
- 每天进步一点点——五分钟理解一致性哈希算法(consistent hashing)
- C++类型向上转换的一种形式
- 我的App-帝都地铁
- zigbee z-stack 同时使用两个串口
- 窗口内致盲攻击
- azure云中 mount: wrong fs type, bad option, bad superblock on /dev/sdc1
- 软件项目管理重要性认识
- Codeforces Round #341 (Div. 2) --A. Wet Shark and Odd and Even
- Codeforces 264B Good Sequences (dp)
- 为windows命令行cmd添加ls命令
- NLP系列(2)_用朴素贝叶斯进行文本分类(上)