谷歌Volley网络框架讲解——BasicNetwork类

这个类是toolbox工具箱包里的,实现了Network接口。

先来看下Network这个interface,performRequest(Request*)执行一个请求,以一个Request为参数,返回一个

NetworkResponse 。
public interface Network {
/**
* Performs the specified request.执行这个请求
* @param request Request to process//待处理的请求
* @return A {@link NetworkResponse} with data and caching metadata; will never be null
* 返回一个请求结果,不会为空
* @throws VolleyError on errors
*/
public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

BasicNetwork实现了Network接口,我们来看下UML图。

再来看下它的构造函数,两个参数HttpStack和ByteArrayPool,这两个参数就是主要的成员变量。

 /**
* 带一个默认大小的ByteArrayPool缓冲池
* @param httpStack HTTP stack to be used
*/
public BasicNetwork(HttpStack httpStack) {
// If a pool isn't passed in, then build a small default pool that will give us a lot of
// benefit and not use too much memory.
//如果一个池没有通过,将建立一个小的默认缓存池,这样会给我们带来很大的益处,不需要耗费很多内存
this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
} /**
* 主构造方法BasicNetwork(HttpStack*,ByteArrayPool*)
* @param httpStack HTTP stack to be used
* @param pool a buffer pool that improves GC performance in copy operations
*/
public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
mHttpStack = httpStack;
mPool = pool;
}

再看看哪个方法用到了mHttpStack,就是在实现Network接口的performRequest()方法,并且mHttpStack有个跟Network接口同名的方法,这才是真正执行请求的方法,也是直接传入请求返回响应。

而mPool是在entityToBytes()这个方法中用到,顾名思义这个方法就是把HttpEntity转换为bytes数据,而这个缓存池就是为便捷转换数据格式。

再详细看下最重要的方法performRequest(),代码中均以加上注释,见解有误望读者们见谅和请教。

/**
* @title performRequest执行各种Request请求并以NetworkResponse的形式返回结果
* @param Request
* @return NetworkResponse
* @throws VolleyError
* 定义:{@link Network#performRequest(Request)}
* 被调:{@link NetworkDispatcher#run()}
*
*/
@Override//NetworkDispatcher的run()方法中调用
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();//开始请求时间
while (true) {
HttpResponse httpResponse = null;//apache的请求结果
byte[] responseContents = null;//请求的内容
Map<String, String> responseHeaders = new HashMap<String, String>();//响应结果头部信息
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();//保存缓存数据
addCacheHeaders(headers, request.getCacheEntry());//先获取缓存数据
httpResponse = mHttpStack.performRequest(request, headers);//去调用mHttpStack的实现方法执行请求
StatusLine statusLine = httpResponse.getStatusLine();//获取http状态线
int statusCode = statusLine.getStatusCode();//获取状态码 responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Handle cache validation.//处理缓存验证
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {//返回缓存数据
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
request.getCacheEntry().data, responseHeaders, true);
} //把HttpEntity转化为byte[]数据
responseContents = entityToBytes(httpResponse.getEntity());
// if the request is slow, log it.//如果请求很慢,就打印出来看一下
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine);//打印 //连接正常但是返回无内容,抛出IO异常
if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_NO_CONTENT) {
throw new IOException();
}
return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
} catch (SocketTimeoutException e) {//读取超时,重试
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException e) {//连接超时,重试
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException e) {//Bad URL
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {//IO异常
int statusCode = 0;
NetworkResponse networkResponse = null;
if (httpResponse != null) {
statusCode = httpResponse.getStatusLine().getStatusCode();
} else {//如果没有返回httpResponse,就说明没连接
throw new NoConnectionError(e);
}
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
if (responseContents != null) {//返回数据不为空
networkResponse = new NetworkResponse(statusCode, responseContents,
responseHeaders, false);//创建响应体
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
statusCode == HttpStatus.SC_FORBIDDEN) {//认证失败异常,重试
attemptRetryOnException("auth",
request, new AuthFailureError(networkResponse));
} else {//服务器异常
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);//只有状态码为5XX才抛出服务器异常
}
} else {//网络异常
throw new NetworkError(networkResponse);
}
}
}
}

A:先是通过mHttpStack把请求执行并且获取它的响应结果,根据HttpStatus做出各种判断。

B:然后再把httpResponse的Entity转化为ByteArray,并处理各种发生的异常。

C:最后的过程是这样的:通过Volley创建一个RequestQueue请求队列,当这个队列开始运作的时候会启动NetworkDispatcher这个工作线程,而BasicNetwork的performRequest()的方法就在NetworkDispatcher线程run()方法中调用,然后通过mHttpStack的performRequest()方法获取一个networkResponse,在NetworkDispatcher线程把这个networkResponse转化为期望的数据类型,比如Response<String>,Response<Json>,Response<Bitmap>。

Android内存分析和调优(中)

前文中讨论了如果使用adb shell procrank, dumpsys meminfo和showmaps分析进程的内存占用情况。

本文将继续细化,具体分析导致内存过大的dalvik heap。

Dalvik heap分析和优化

Dalkvik heap是最常见的android应用内存优化的对象。

通过上文的分析,我们可以通过adb shell的命令,知道用了多少dalvik heap。在ADT的eclipse的DDMS视图,可以更细致的查看这些内存用到什么地方。
参考DDMS使用说明(搜索viewing heap),我们可以首先在devices view中选中一个进程,然后enable "update heap“(不带红箭头的半杯水图标),之后在heap view中点击”Cause GC"。这样子除了Heap Size, Allocated, Freed,还可以看到data object,class object,和n-byte array分别占用的内存大小。

不过真心说,这个还是太粗糙了,没法精确到具体的类。此时大名鼎鼎的MAT就派上用场了。

MAT是对java内存镜像进行分析的工具。所以首先需要导出进程的内存镜像,可以在DDMS上的device view点击Dump HPROF file(带红箭头的半杯水图标),生成hprof文件。因为android的文件格式跟通用的java的hprof格式不一样,还需要通过hprof-conv命令来转换。然后就可以用MAT来打开。

看起来挺麻烦的。事实上,现在MAT的eclipse插件可以把上面的工具一键完成。只需要点击Dump HPROF file图标,然后MAT插件就会自动转换格式,并且在eclipse中打开分析结果。eclipse中还专门有个Memory Analysis视图,可以更详细的查看MAT的分析结果。

MAT可以根据内存镜像,以可视化的方式告诉我们哪个类,哪个对象分配了多少内存。但如果只是这样,用处就没那么大了。因为不像c++的对象本身可以存放大量内存,java的对象成员都是些引用。真正的内存都在堆上,看起来是一堆原生的byte[], char[], int[]。所以我们如果只看对象本身的内存,那么数量都很小。我们称之位shallow heap。

于是MAT提出了Retained Heap的概念,它表示如果一个对象被释放掉,那会因为该对象的释放而减少引用进而被释放的所有的对象(包括被递归释放的)所占用的heap大小。于是,如果一个对象的某个成员new了一大块int数组,那这个int数组也可以计算到这个对象中。相对于shallow heap,Retained heap可以更精确的反映一个对象实际占用的大小(因为如果该对象释放,retained heap都可以被释放)。这里要说一下的是,Retained Heap并不总是那么有效。例如我在A里new了一块内存,赋值给A的一个成员变量。此时我让B也指向这块内存。此时,因为A和B都引用到这块内存,所以A释放时,该内存不会被释放。所以这块内存不会被计算到A或者B的Retained Heap中。为了纠正这点,MAT中的Leading Object(例如A或者B)不一定只是一个对象,也可以是多个对象。此时,(A, B)这个组合的Retained Set就包含那块大内存了。对应到MAT的UI中,在Histogram中,可以选择Group By class, superclass or package来选择这个组。(又开始Histogram中不显示Retained heap,需要点击那个计算器的按钮才会计算出来)。这里最小的粒度是类级别的。

为了计算Retained Memory,MAT引入了Dominator Tree。加入对象A引用B和C,B和C又都引用到D(一个菱形)。此时要计算Retained Memory,A的包括A本身和B,C,D。B和C因为共同引用D,所以他俩的Retained Memory都只是他们本身。D当然也只是自己。我觉得是为了加快计算的速度,MAT改变了对象引用图,而转换成一个对象引用树。在这里例子中,树根是A,而B,C,D是他的三个儿子。B,C,D不再有相互关系。把引用图变成引用树,计算Retained Heap就会非常方便,显示也非常方便。对应到MAT UI上,在dominator tree这个view中,显示了每个对象的shallow heap和retained heap。然后可以以该节点位树根,一步步的细化看看retained heap到底是用在什么地方了。要说一下的是,这种从图到树的转换确实方便了内存分析,但有时候会让人有些疑惑。本来对象B是对象A的一个成员,但因为B还被C引用,所以B在树中并不在A下面,而很可能是平级。

为了纠正这点,MAT中点击右键,可以List objects中选择with outgoing references和with incoming references。这是个真正的引用图的概念,表示该对象的出节点(被该对象引用的对象)和入节点(引用到该对象的对象)。

另外一个类似的功能是右键菜单的Path to GC RootsGC roots是可能导致GC的节点。这个Path则是从这些GC root节点中的某个到当前对象的最短引用路径。对这个如何计算不是很确定,我想应该是根据引用树而不是dominator tree。后面会看到这个功能在非常的有用。

说完工具,下面是具体的减少内存大小。一般要解决两个问题:内存泄露和释放暂时不需要的内存。

Java内存泄露归根结底都是一个原因导致的,应该被释放的对象被生命期更长的对象引用,所以没法被GC。这个生命期更长的对象很常见的是static对象,会持续整个进程。在个人实际工作中,我会先用adb shell dumpsys meminfo查看dalvik heap会不会持续增长。如果是,我会在在dominator Tree中按照Retained Memory排序,找出比较大的(经常是Bitmap),然后用Path to GC Roots看看其引用情况。在这个Path中,一般会发现我们app自己包的类,可以分析这个类是不是还是需要的。如果不需要,那说明可能存在内存泄露。此时,在对这个自己包的类查看incoming references。看看到底是哪些引用导致它没有释放。用这种方法,会比较快的发现问题。MAT自己也提供了智能的内存分析工具,我没有用,不好评论。

一个制造内存泄露的很有效的办法是不断的切换横屏和竖屏。现实中很多内存泄露都是因为static的对象指向了Activity对象(作为context传),而切换横屏和竖屏会导致Activity重新生成。所以如果有问题,内存很快就会变大。从编码上讲,avoid-memory-leak这篇文章教育我们,在需要context的地方,尽量使用getApplicationContext,而不是Activity本身。

另外一个可以减少内存的方法是删除临时不用的内存。编码中可能是为了内存cache以提高性能,可能只是偷懒,之前场景使用的内存并没有被释放掉。这样子下次再回到这个场景,会快一点;但会可能会占用不少内存。我觉得在android这类内存受限的系统上,还是应该谨慎使用控件换时间的策略。如果想删除临时不用的内存,也可以使用mat像监测内存泄露一样,看看哪些比较大的内存临时不用却仍然被引用,然后删除对其引用。

关于mat的一个小技巧是mat经常发现比较大的内存泄露是图片,此时如果知道图片是什么内容就很容易定位到何时导致的内存泄露。这个帖子回答了这个问题。

关于dalvik mat最后再推荐自己看的一个android memory manage video(slides , contentcontent2)。里面对MAT和内存泄露都有介绍。这个blog也是对二者都有介绍,很好。关于MAT更好的文档集合在这里,MAT作者写的。

 
 

谷歌Volley网络框架讲解——BasicNetwork类的更多相关文章

  1. 谷歌Volley网络框架讲解——第一篇

    自从公司新招了几个android工程师后,我清闲了些许.于是就可以有时间写写博客,研究一些没来的研究的东西. 今年的谷歌IO大会上,谷歌推出了自己的网络框架——Volley.不久前就听说了但是没有cl ...

  2. 谷歌Volley网络框架讲解——HttpStack及其实现类

    前两篇已经对网络请求流程已经梳理了个大概,这次我们着重看一下HttpStack和它的其实现类.我们之前在Network篇讲过它仅有一个实现类,而今天我们讲的HttpStack有两个实现类. 其中Htt ...

  3. 谷歌Volley网络框架讲解——Network及其实现类

    我们看到Network接口只有一个实现类BasicNetwork,而HttpStack有两个实现类. BasicNetwork这个类是toolbox工具箱包里的,实现了Network接口. 先来看下N ...

  4. 谷歌Volley网络框架讲解——网络枢纽

    研究了这么久的Volley,愈来愈发现这个框架的精美和人性化.比起民间一些框架强很多,一开始总是盲人摸象找不到头绪,现在终于有些明朗了.Volley其实就是一个请求队列的代理类,我们看下UML. 这就 ...

  5. Volley网络框架的使用

    Volley的特点:   使用网络通信更快.更简单 Get/Post网络请求网络图像的高效率异步请求 可以对网络请求的优先级进行排序处理 可以进行网络请求的缓存 可以取消多级别请求 可以和Activi ...

  6. Volley网络框架完全解析(使用篇)

    在Android中,网络请求无非就这两种:HttpURLConnection和HttpClient( Apache),我们在使用时一般都会对它们进行一系列的封装,但是这过程不免有些繁琐,所以,Goog ...

  7. Volley网络框架完全解析(实战篇)

    好了,今天就通过一个瀑布流demo,来使用Volley框架请求网络图片. 前言: 我们使用NetworkImageView显示图片: 1.因为该控件可以自动的管理好请求的生命周期,当与父控件detac ...

  8. Volley网络框架完全解析(缓存篇)

    在上一篇中讲完了Volley框架怎么使用,那么这篇就来讲讲Volley框架的缓存机制 我们看Volley内部源码发现: Volley框架内部自己处理了DiskBasedCache硬盘缓存,但是没有处理 ...

  9. Android网络框架Volley

    Volley是Google I/O 2013推出的网络通信库,在volley推出之前我们一般会选择比较成熟的第三方网络通信库,如: android-async-http retrofit okhttp ...

随机推荐

  1. iOS 开发小技巧

    1.Xcode配置 1.1> 安装Alcatraz包管理器 打开Terminal终端命令行 curl -fsSL https://raw.github.com/supermarin/Alcatr ...

  2. C# 打开指定文件或网址

    System.Diagnostics.Process.Start的妙用: 文件夹打开时自动选中一个文件,比如自动选中此目录下的指定文件方法: Process.Start("Explorer& ...

  3. Access to the temp directory is denied. Identity 'NT AUTHORITY\NETWORK SERVICE' under which XmlSerializer is running does not have sufficient permiss

    造成错误的原因是用bat代码清理系统垃圾时造成的权限丢失而引起的 错误描述 1.An error occurred creating the configuration section handler ...

  4. 怎样才能充分利用SQL索引

    原文:怎样才能充分利用SQL索引 背景:目前WEB的普及太快,很多网站都会因为大流量的数据而发生服务器习惯性死机,一个查询语句只能适用于一定的网络环境.没有优化的查询当遇上大数据量时就不适用了. 本文 ...

  5. android ListView之BaseAdapter的使用方式

    通常在使用自己定义适配器的时候,我们都会掌握一种固定的模式.充分利用convertView+缓存的方式. private ArrayList<ListBean> list ; privat ...

  6. eclipse中使用git进行版本号控制

    协作开发的时候没有版本号控制是非常痛苦的事情,使用git能够非常好的完毕这项任务,由于非常多的开源码都在github上公布,因此学会使用git是非常重要的一项技能. 这篇写的是在eclipse下使用的 ...

  7. BestCoder Round #11 (Div. 2) 题解

    HDOJ5054 Alice and Bob Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/O ...

  8. Django查询的琐碎记录

    我的需求是这样的,获取指定用户的获“赞”总数. 用户 models.py class UserProfile(models.Model): user = models.OneToOneField(Us ...

  9. 负载均衡DNS和反向代理优缺点

    负载均衡 (Load Balancing) 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用性. 负载均衡(又 ...

  10. leetcode第七题--Reverse Integer

    Problem: Reverse digits of an integer. Example1: x = 123, return 321Example2: x = -123, return -321 ...