使用适当的开源库,如Volley或者Universal ImageLoader
以Volley库为例。Volley使用了线程池来作为基础结构,主要分为主线程,cache线程和network线程。
主线程和cache线程都只有一个,而NetworkDispatcher线程可以有多个,这样能解决比并行问题。如下图:
其中左下角是NetworkDispatcher线程,大致步骤是:
1.不断从请求队列中取出请求
request = mQueue.take();
2.发起网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
3.将网络响应加入缓存
mCache.put(request.getCacheKey(), response.cacheEntry);
下面是NetworkDispatcher线程的主要代码:
@Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Request request; while (true) { try { // Take a request from the queue. request = mQueue.take();//1.从请求队列中取出一个网络请求,mQueue是BlockingQueue<Request>的实现类 } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } try { request.addMarker("network-queue-take"); // If the request was cancelled already, do not perform the // network request. if (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } // Tag the request (if API >= 14) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { TrafficStats.setThreadStatsTag(request.getTrafficStatsTag()); } // Perform the network request. NetworkResponse networkResponse = mNetwork.performRequest(request);//2.发起网络请求 request.addMarker("network-http-complete"); // If the server returned 304 AND we delivered a response already, // we're done -- don't deliver a second identical response. if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } // Parse the response here on the worker thread. Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); // Write to cache if applicable. // TODO: Only update cache metadata instead of entire record for 304s. if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry);//3.将网络响应加入缓存 request.addMarker("network-cache-written"); } // Post the response back. request.markDelivered(); mDelivery.postResponse(request, response); } catch (VolleyError volleyError) { parseAndDeliverNetworkError(request, volleyError); } catch (Exception e) { VolleyLog.e(e, "Unhandled exception %s", e.toString()); mDelivery.postError(request, new VolleyError(e)); } } }
要理解Volley的并行性实现,必需理解PriorityBlockingQueue并发类。主要到我们在NetworkDispatcher循环中并没有显示加锁或者使用synchronize,因为PriorityBlockingQueue的实现是线程安全的。
BlockingQueue
implementations are thread-safe. All queuing methods achieve their effects atomically using internal locks or other forms of concurrency control.
所以可以有多个NetworkDispatcher线程同时从请求队列中取出请求而不会产生线程冲突,那就是Volley支持多线程下载图片的方式。
注意到,NetworkDispatcher的实现其实是策略设计模式:
/** The queue of requests to service. */ private final BlockingQueue<Request> mQueue; /** The network interface for processing requests. */ private final Network mNetwork; /** The cache to write to. */ private final Cache mCache; /** For posting responses and errors. */ private final ResponseDelivery mDelivery; /** Used for telling us to die. */ private volatile boolean mQuit = false; /** * Creates a new network dispatcher thread. You must call {@link #start()} * in order to begin processing. * * @param queue Queue of incoming requests for triage * @param network Network interface to use for performing requests * @param cache Cache interface to use for writing responses to cache * @param delivery Delivery interface to use for posting responses */ public NetworkDispatcher(BlockingQueue<Request> queue, Network network, Cache cache, ResponseDelivery delivery) { mQueue = queue; mNetwork = network; mCache = cache; mDelivery = delivery; }
NetworkDispatcher构造函数的几个参数都是接口,而run方法则使用这些策略类方法实现了算法的主体流程,具体实现有些留给了开发者,有些则是框架实现。比如ImageCache作为一级缓存的Cache方法留给了开发者实现,由开发者控制具体的缓存策略,当然Volley建议我们使用LRUCache作为L1缓存的实现。
最后,NetworkDispatcher的数组则构成了RequestQueue类中线程池,由RequestQueue统一启动和停止:
/** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests * @param threadPoolSize Number of network dispatcher threads to create * @param delivery A ResponseDelivery interface for posting responses and errors */ public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; } /** * Starts the dispatchers in this queue. */ public void start() { stop(); // Make sure any currently running dispatchers are stopped. // Create the cache dispatcher and start it. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); // Create network dispatchers (and corresponding threads) up to the pool size. for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } }
使用Volley时,可以覆写Activity或者Fragment中的onScrollStateChanged,如果处于AbsListView.OnScrollListener.SCROLL_STATE_FLING的状态,则imageLoader.stopProcessingQueue()(停止网络图片加载)。imageLoader是NetworkImageView的一个引用,负责加载图片,这样在快速滑动时就不会加载图片。
另外也同样可以在Adapter中设置标志位,表示是否正在快速滑动,如果正在快速滑动,Adapter中的getView只需要把TextView等简单数据显示出来,把复杂逻辑和onClickListener等移到非Fling状态时才设置。
关于Volley的介绍可以看这篇博客:http://blog.csdn.net/t12x3456/article/details/9221611
采用扁平化的布局
层层嵌套的布局会花费很多渲染时间。
使用merge标签
一个常见的不需要嵌套的例子是使用FrameLayout作为布局的单一根节点,当该布局被添加到一个父容器时,它就会成为冗余。更好的做法是使用merge标签。当包含有merge标签的布局被添加到另一个布局时,该布局的merge节点会被删除,而该布局的子View会被直接添加到新的父布局中。
mege标签结合include标签一起使用可以复用布局同时,减少嵌套
include标签可以把一个布局的内容插入到另一个布局。利用include标签可以在垂直和水平方向的不同布局中共享大多数UI布局。
延迟加载
View Stub是一个可见的、大小为0的View,它就像一个延迟填充的include标签,只有在显示调用inflate方法或者被置为可见是,这个stub才被填充。
更多详解介绍可以参考:《Android4高级编程》P90,《Android优化技术详解》P76
注意Adapter的getView函数的编写
(1)复用convertView。convertView是ListView的RecycleBin内部类帮我们缓存的,如果ListView的不同列表项有不同的类型,可以在Apdater的getViewTypeCount中返回类型数,ListView会根据不同类别分别缓存。可以看我之前对ListView源代码的分析:http://blog.csdn.net/mba16c35/article/details/43638793
(2)使用ViewHolder。新建列表项视图的各项子视图都存在ViewHolder中,然后setTag,下次直接通过
ItemViewHolder holder = (ItemViewHolder) convertView.getTag();
就省去了findViewById的步骤。
下面是一段示例代码。XXNetworkImageView继承Volley中的NetworkImageView。
@Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView==null) { convertView = mInflater.inflate(layoutID, null); ItemViewHolder holder = new ItemViewHolder(); holder.bookCover = (XXNetworkImageView) convertView.findViewById(R.id.book_booklist_cover); holder.bookName = (TextView) convertView.findViewById(R.id.book_booklist_name); holder.bookClickNumber = (TextView) convertView.findViewById(R.id.book_booklist_clicknumber); holder.bookType = (TextView) convertView.findViewById(R.id.book_booklist_type); holder.bookAuthor = (TextView) convertView.findViewById(R.id.book_booklist_author); holder.bookState = (ImageView) convertView.findViewById(R.id.book_booklist_state); convertView.setTag(holder); } bindView(convertView, position); return convertView; } private void bindView(View convertView, int position) { ItemViewHolder holder = (ItemViewHolder) convertView.getTag(); String imageUrl = (String) Util.IMG_URL_PREFIX + bookList.get(position).getImgUrl(); String currentUrl = holder.bookCover.getUrl(); if (currentUrl == null || !currentUrl.equals(imageUrl)) { if (mContext != null) { holder.bookCover.setImageUrl(imageUrl, imageLoader, Util.dip2px(mContext, 95)); } else { holder.bookCover.setImageUrl(imageUrl, imageLoader); } } holder.bookName.setText(String.valueOf(bookList.get(position).getName())); holder.bookClickNumber.setText(String.valueOf(bookList.get(position).getFreePage())); holder.bookType.setText("类型: "+ String.valueOf(bookList.get(position).getTypes())); holder.bookAuthor.setText("作者: " + String.valueOf(bookList.get(position).getAuthor())); if(String.valueOf(bookList.get(position).getScale()).equals("0")) holder.bookState.setImageResource(R.drawable.tag0); // else if(String.valueOf(bookListData.get(position).get("state")).equals("1")) // holder.bookState.setImageResource(R.drawable.tag); } static class ItemViewHolder { XXNetworkImageView bookCover; TextView bookName; TextView bookClickNumber; TextView bookType; TextView bookAuthor; ImageView bookState; }