我的App-帝都地铁

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文件中:



    

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 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转换为ByteArrayOutputStream
baos = 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 lstFromStationIds = mStationDao.getStationIdsByStationName(fromStationName);
// 终点车站名对应的车站ID集合
List lstToStationIds = mStationDao.getStationIdsByStationName(toStationName);

这里要说明一下,比如说军事博物馆站是一个换乘车站,它有两个车站ID,分别为0109和0904,车站ID前两位表示车站所在线路,即军事博物馆站属于1号线和9号线,当以军事博物馆站为起点站查询时需要考虑分别从1号线和9号线为起点线路向其他线路换乘,因此需要取出车站名对应的车站ID集合,再根据车站ID集合获取起点终点线路集合。

接着,获取起点终点线路集合,比如从丰台科技园到北京站,则[09,02]:

List lstFromToLineIds = getFromToLineIds(lstFromStationIds, lstToStationIds);

接着,获取起点到终点换乘路线详细信息,[09,01,02],[09,06,02],[09,04,02]:

List 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;
        }
    }
}

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

你可能感兴趣的:(Android)