Android性能:经典ListView适配器convertView缓存及复用机制

简介: Android性能:经典ListView适配器convertView缓存及复用机制Android中的ListView常用Adapter中都会涉及到convertView的使用,使用convertView主要是为了缓存试图View,用以增加ListView的item view加载效率。
Android性能:经典ListView适配器convertView缓存及复用机制


Android中的ListView常用Adapter中都会涉及到convertView的使用,使用convertView主要是为了缓存试图View,用以增加ListView的item view加载效率。有经验的Android开发者通常知道在Adapter的getView中,先判断convertView是否为空null,如果非空,则直接再次对convertView复用,否则才创建新的View。为何要复用convertView呢?参见我之前写的附录文章1。那么Android是如何维护convertView这一机制的运转呢?
(一)当第一次打开Android的ListView初始化时候,在Adapter中此时的convertView无疑均是空的,Android将创建新的convertView,创建多少呢?答案是一屏ListView有多少条item,就创建多少个convertView,注意,这仅仅是在第一次初始化ListView展现item时候才是这样的convertView创建机制,以一个ListView配置一个ArrayAdapter,写例子:
package zhangphil.demo;

import android.app.ListActivity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class MainActivity extends ListActivity {
    private int tagId = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ArrayAdapter mAdapter = new ArrayAdapter(this, 0) {
            private TextView text1;
            private TextView text2;

            @NonNull
            @Override
            public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
                if (convertView == null) {
                    convertView = LayoutInflater.from(getContext()).inflate(android.R.layout.simple_list_item_2, null);
                    //convertView.setMinimumHeight(400);

                    Log.d("位置" + position, "创建新convertView,设置tagId:" + tagId);
                    convertView.setTag(tagId++);
                } else {
                    Log.d("位置" + position, convertView.getTag() + " 复用convertView");
                }

                text1 = convertView.findViewById(android.R.id.text1);
                text1.setTextColor(getResources().getColor(android.R.color.holo_blue_bright));
                text1.setText(getItem(position));

                text2 = convertView.findViewById(android.R.id.text2);
                text2.setTextColor(getResources().getColor(android.R.color.holo_red_light));
                text2.setText("convertView tagId:" + String.valueOf(convertView.getTag()));

                return convertView;
            }

            @Nullable
            @Override
            public String getItem(int position) {
                return "位置position:" + position;
            }

            @Override
            public int getCount() {
                return 99999;
            }
        };

        setListAdapter(mAdapter);
    }
}

定义了一个tagId,用以标记convertView,在新创建的convertView里面打入进去,然后在后续convertView复用时候取出来,以追踪此时复用的到底是哪个convertView。

代码运行结果:




logcat日志输出结果:

11-03 14:40:08.745 22766-22766/zhangphil.demo D/位置0: 创建新convertView,设置tagId:0
11-03 14:40:08.760 22766-22766/zhangphil.demo D/位置1: 创建新convertView,设置tagId:1
11-03 14:40:08.765 22766-22766/zhangphil.demo D/位置2: 创建新convertView,设置tagId:2
11-03 14:40:08.773 22766-22766/zhangphil.demo D/位置3: 创建新convertView,设置tagId:3
11-03 14:40:08.782 22766-22766/zhangphil.demo D/位置4: 创建新convertView,设置tagId:4
11-03 14:40:08.787 22766-22766/zhangphil.demo D/位置5: 创建新convertView,设置tagId:5
11-03 14:40:08.795 22766-22766/zhangphil.demo D/位置6: 创建新convertView,设置tagId:6
11-03 14:40:08.800 22766-22766/zhangphil.demo D/位置7: 创建新convertView,设置tagId:7
11-03 14:40:16.573 22766-22766/zhangphil.demo D/位置8: 创建新convertView,设置tagId:8

可以看出,在ListView的初始化阶段,Android经过计算创建了九个全新的convertView,比当前设备屏幕可见区域的八条再多一条,我给这九个全新的convertView标记了tagId分别为0,1,2,3,4,5,6,7,8。这些打进去的tagId在后续可以追踪观察convertView的具体复用。
此处注意!根据每个屏幕的大小尺寸不一样,以及Adapter中getView返回的View高度不同,在不同的设备上,创建的新convertView个数不同。


(二)convertView发生作用的地方,就是当ListView在向上/向下滑动过程中,convertView缓存和复用机制才发挥出来。比如当手指在屏幕自下往上翻动ListView时候,此时ListView头部的item将滚出设备屏幕,而底部的新item将加载出来,此时convertView的复用机制将发挥作用。由于此前在ListView初始化阶段已经创建了九个全新的convertView,Android系统将之前ListView初始化阶段创建的九个全新convertView都缓存起来,现在,由于ListView的上下翻动,顶部和底部之前显示的item滚出设备屏幕不可见,Android系统要么完全回收这些convertView,要么复用这些convertView。ListView的item有一个共同点:在大多数情况下,这些item的View是相同的,所以,明智的做法是继续复用,这样无疑会提高系统加载性能,要知道每一次创建新的convertView,是有一定系统开销的。于是,在往下翻动ListView时候,代码运行结果如图所示:






logcat日志输出结果如图:
11-03 15:02:50.553 31901-31901/zhangphil.demo D/位置64: 1 复用convertView
11-03 15:02:50.637 31901-31901/zhangphil.demo D/位置65: 2 复用convertView
11-03 15:02:50.720 31901-31901/zhangphil.demo D/位置66: 5 复用convertView
11-03 15:02:50.820 31901-31901/zhangphil.demo D/位置67: 4 复用convertView
11-03 15:02:50.936 31901-31901/zhangphil.demo D/位置68: 7 复用convertView
11-03 15:02:51.069 31901-31901/zhangphil.demo D/位置69: 3 复用convertView
11-03 15:02:51.236 31901-31901/zhangphil.demo D/位置70: 8 复用convertView
11-03 15:02:51.452 31901-31901/zhangphil.demo D/位置71: 0 复用convertView
11-03 15:02:51.786 31901-31901/zhangphil.demo D/位置72: 6 复用convertView


(三)从第(二)小节的叙述和代码运行输出可见,Android的ListView在复用的缓存convertView过程中,并不保证是按顺序复用convertView的,这一点可以从输出的tagId可以看出来。tagId在每一轮复用convertView过程中的输出次序不是严格的0,1,2,3,4,5,6,7,8。而是呈现一定的随机性,但是,唯一可以保证的是,每一轮的复用,都必将把tagId编号0~8的这九个缓存convertView用完,只是不保证复用顺序。此处可以得到一定的启发,如果开发者打算按照某种特定顺序复用这九个缓存的convertView,那么可以在getView创建全新convertView时候,把这些新的convertView依次放入一个首尾相接的链表(一个闭环的圆圈链表),在本例中,该首尾相连的链表元素个数是9。开发者可以根据自己具体情况,从getView判断convertView为空时候作为定义该首尾相连链表长度的依据。


附录:
1,《基于“ViewHolder”技术提升Android ListView中Item View加载效率》链接:http://blog.csdn.net/zhangphil/article/details/44779723 
相关文章
|
12月前
|
数据库 Android开发
Android使用EditText+Listview实现搜索效果(使用room模糊查询)
本文介绍如何在Android中使用EditText与ListView实现搜索功能,并结合Room数据库完成模糊查询。主要内容包括:Room的模糊查询语句(使用`||`代替`+`号)、布局美化(如去除ListView分割线和EditText下划线)、EditText回车事件监听,以及查询逻辑代码示例。此外,还提供了相关扩展文章链接,帮助读者深入了解ListView优化、动态搜索及Room基础操作。
735 65
|
缓存 并行计算 PyTorch
PyTorch CUDA内存管理优化:深度理解GPU资源分配与缓存机制
本文深入探讨了PyTorch中GPU内存管理的核心机制,特别是CUDA缓存分配器的作用与优化策略。文章分析了常见的“CUDA out of memory”问题及其成因,并通过实际案例(如Llama 1B模型训练)展示了内存分配模式。PyTorch的缓存分配器通过内存池化、延迟释放和碎片化优化等技术,显著提升了内存使用效率,减少了系统调用开销。此外,文章还介绍了高级优化方法,包括混合精度训练、梯度检查点技术及自定义内存分配器配置。这些策略有助于开发者在有限硬件资源下实现更高性能的深度学习模型训练与推理。
2323 0
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
缓存 应用服务中间件 nginx
Web服务器的缓存机制与内容分发网络(CDN)
【8月更文第28天】随着互联网应用的发展,用户对网站响应速度的要求越来越高。为了提升用户体验,Web服务器通常会采用多种技术手段来优化页面加载速度,其中最重要的两种技术就是缓存机制和内容分发网络(CDN)。本文将深入探讨这两种技术的工作原理及其实现方法,并通过具体的代码示例加以说明。
1155 1
|
缓存 应用服务中间件 Apache
缓存代理服务器的实现机制和技术选型
缓存代理服务器是一种特殊的代理服务器,其主要功能是缓存从目标服务器(通常是Web服务器)获取的数据,并在客户端再次请求相同数据时直接提供缓存的数据。通过缓存代理服务器可以加快访问速度并减轻目标服务器的负载。
762 108
|
存储 缓存 分布式计算
【赵渝强老师】Spark RDD的缓存机制
Spark RDD通过`persist`或`cache`方法可将计算结果缓存,但并非立即生效,而是在触发action时才缓存到内存中供重用。`cache`方法实际调用了`persist(StorageLevel.MEMORY_ONLY)`。RDD缓存可能因内存不足被删除,建议结合检查点机制保证容错。示例中,读取大文件并多次调用`count`,使用缓存后执行效率显著提升,最后一次计算仅耗时98ms。
444 0
【赵渝强老师】Spark RDD的缓存机制
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
578 4
|
缓存 Java 数据库连接
MyBatis缓存机制
MyBatis提供两级缓存机制:一级缓存(Local Cache)默认开启,作用范围为SqlSession,重复查询时直接从缓存读取;二级缓存(Second Level Cache)需手动开启,作用于Mapper级别,支持跨SqlSession共享数据,减少数据库访问,提升性能。
391 1

热门文章

最新文章