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

谷歌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作者写的。

 
 
原文地址:https://www.cnblogs.com/Leo_wl/p/3287582.html