我的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;        }    }}

以上即为整个项目的大致介绍,具体请看源码。

1 0