ListView的原理

Listview在项目开发使用率是非常高的,一直以来都只是会使用,今天突然想看看ListView和Adapter是怎么配合的。

列表的显示需要三个元素:

  • ListVeiw: 用来展示列表的View。
  • 适配器 : 用来把数据映射到ListView上
  • 数据: 具体的将被映射的字符串,图片,或者基本组件。

ListView 针对每个item,要求 adapter “返回一个视图” (getView),也就是说ListView在开始绘制的时候,系统要首先用getCount()函数得到要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先获得一个View(这个看实际情况,如果是一个简单的显示则是View,如果是一个自定义的里面包含很多控件的时候它其实是一个ViewGroup),然后再实例化并设置各个组件及其数据内容并显示它。好了,绘制完这一行了。那 再绘制下一行,直到绘完为止。

getItem()getItemId()方法将会在调用ListView的响应方法的时候被调用到。所以要保证ListView的各个方法有效的话,这两个方法也得重写。比如:没有完成getItemId方法的功能实现的话,当调用ListView的getItemIdAtPosition方法时将会得不到想要的结果,因为该方法就是调用了对应的适配器的getItemId方法。

下图是ListView加载数据的工作原理:
这里写图片描述

下面简单说下上图的原理:

如果你有几千几万甚至更多的选项(item)时,其中只有可见的项目存在内存(内存内存哦,说的优化就是说在内存中的优化!!!)中,其他的在Recycler中
ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的
当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图

代码块:

public class MultipleItemsList extends ListActivity {

    private MyCustomAdapter mAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAdapter = new MyCustomAdapter();
        for (int i = 0; i < 50; i++) {
            mAdapter.addItem("item " + i);
        }
        setListAdapter(mAdapter);
    }

    private class MyCustomAdapter extends BaseAdapter {

        private ArrayList mData = new ArrayList();
        private LayoutInflater mInflater;

        public MyCustomAdapter() {
            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        public void addItem(final String item) {
            mData.add(item);
            notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public String getItem(int position) {
            return mData.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            System.out.println("getView " + position + " " + convertView);
            ViewHolder holder = null;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.item1, null);
                holder = new ViewHolder();
                holder.textView = (TextView)convertView.findViewById(R.id.text);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.textView.setText(mData.get(position));
            return convertView;
        }

    }

    public static class ViewHolder {
        public TextView textView;
    }
}

执行程序,查看日志:
这里写图片描述

getView 被调用 9 次 ,convertView 对于所有的可见项目是空值(如下):

这里写图片描述

然后稍微向下滚动List,直到item10出现:

这里写图片描述

convertView仍然是空值,因为recycler中没有视图(item1的边缘仍然可见,在顶端)再滚动列表,继续滚动:

这里写图片描述

convertView不是空值了!item1离开屏幕到Recycler中去了,然后item11被创建,再滚动下:

这里写图片描述

此时的convertView非空了,在item11离开屏幕之后,它的视图(…0f8)作为convertView容纳item12了,好啦,结合以上原理,下面来看看今天最主要的话题,主角ListView的优化:

首先,这个地方先记两个ListView优化的一个小点:

  • ExpandableListView 与 ListActivity
    由官方提供的,里面要使用到的ListView是已经经过优化的ListView,如果大家的需求可以用Google自带的ListView满足的的话尽量用官方的,绝对没错!

  • ListView优化,其实并不是指其它的优化,就是内存是的优化,提到内存…(想到OOM,折腾了我不少时间),很多很多,先来写下,如果我们的ListView中的选项仅仅是一些简单的TextView的话,就好办啦,消耗不了多少的,但如果你的Item是自定义的Item的话,例如你的自定义Item布局ViewGroup中包含:按钮、图片、flash、CheckBox、RadioButton等一系列你能想到的控件的话, 你要在getView中单单使用文章开头提到的ViewHolder是远远不够的,如果数据过多,加载的图片过多过大,你BitmapFactory.decode的猛多的话,OOM搞死你,这个地方再警告下大家,是警告……….也提醒下自己:

小马碰到的问题大家应该也都碰到过的,自定义的ListView项乱序问题,我很天真的在getView()中强制清除了下ListView的缓存数据convertView,也就是convertView = null了,虽然当时是解决了这个问题让其它每次重绘,但是犯了大错了,如果数据太多的话,出现最最恶心的错,手机卡死或强制关机,关机啊哥哥们……O_O,客户杀了我都有可能,但大家以后别犯这样的错了,单单使用清除缓存convertView是解决不了实际问题的,继续……

下面来列举下真正意义上的优化吧:

  • ViewHolder Tag 必不可少,这个不多说!

  • 如果自定义Item中有涉及到图片等等的,一定要狠狠的处理图片,图片占的内存是ListView项中最恶心的,处理图片的方法大致有以下几种:

    • 不要直接拿个路径就去循环decodeFile();这是找死….用Option保存图片大小、不要加载图片到内存去;

    • 拿到的图片一定要经过边界压缩

    • 在ListView中取图片时也不要直接拿个路径去取图片,而是以WeakReference(使用WeakReference代替强引用。比如可以使
      用WeakReference
      mContextRef)、SoftReference、WeakHashMap等的来存储图片信息,是图片信息不是图片哦!

    • 在getView中做图片转换时,产生的中间变量一定及时释放

  • 尽量避免在BaseAdapter中使用static 来定义全局静态变量,我以为这个没影响,这个影响很大,static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(比如Context的情况最多),这时就要尽量避免使用了..

  • 如果为了满足需求下必须使用Context的话:Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题

  • 尽量避免在ListView适配器中使用线程,因为线程产生内存泄露的主要原因在于线程生命周期的不可控制

记下小马自己的错误:
之前使用的自定义ListView中适配数据时使用AsyncTask自行开启线程的,这个比用Thread更危险,因为Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了线程执行池(ThreadPoolExcutor,要想了解这个类的话大家加下我们的Android开发群五号,因为其它群的存储空间快满了,所以只上传到五群里了,看下小马上传的Gallery源码,你会对线程执行池、软、弱、强引用有个更深入的认识),这个类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。这个问题的解决办法小马当时网上查到了记在txt里了,如下:

  • 将线程的内部类,改为静态内部类。

  • 在线程内部采用弱引用保存Context引用

示例代码如下:

public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends
            AsyncTask<Params, Progress, Result> {
        protected WeakReference<WeakTarget> mTarget;  

        public WeakAsyncTask(WeakTarget target) {
            mTarget = new WeakReference<WeakTarget>(target);
        }  

        /** {@inheritDoc} */
        @Override
        protected final void onPreExecute() {
            final WeakTarget target = mTarget.get();
            if (target != null) {
                this.onPreExecute(target);
            }
        }  

        /** {@inheritDoc} */
        @Override
        protected final Result doInBackground(Params... params) {
            final WeakTarget target = mTarget.get();
            if (target != null) {
                return this.doInBackground(target, params);
            } else {
                return null;
            }
        }  

        /** {@inheritDoc} */
        @Override
        protected final void onPostExecute(Result result) {
            final WeakTarget target = mTarget.get();
            if (target != null) {
                this.onPostExecute(target, result);
            }
        }  

        protected void onPreExecute(WeakTarget target) {
            // No default action
        }  

        protected abstract Result doInBackground(WeakTarget target, Params... params);  

        protected void onPostExecute(WeakTarget target, Result result) {
            // No default action
        }
    }

转载:http://www.xuanyusong.com/archives/1252

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在英汉两种语言中,字母“c”是具有重要意义的一个字母。在普通话拼音系统中,字母“c”同样有着重要的作用。 首先,在英语中,字母“c”有着多种发音。它可以作为/k/音或/s/音出现。在单词中,字母“c”的发音取决于其后的字母。比如,在单词“cat”中,“c”发/k/音,而在单词“city”中,“c”发/s/音。此外,字母“c”还可以与其他字母组合,形成特定的拼读规则,例如“ch”发/ʧ/音,“ck”发/k/音等。 在汉语中,字母“c”同样有着特殊的地位。在普通话拼音系统中,字母“c”代表清辅音/ts/音。例如,在拼音“chūn”的“c”发出/ ts/音。此外,在拼音中,“c”也可以与其他字母组合,形成不同的拼音规则。比如,“ch”发/ʈʂ/音,“ci”发/ tsʰ/音等。 除了在英汉两种语言中的拼写规则中发挥作用,字母“c”在其他方面也有很多用途。在计算机科学中,字母“c”是一种编程语言,它被广泛用于开发各种应用程序。此外,在数学中,字母“c”通常用于表示常数,例如圆周率“π”等。 总之,字母“c”在英汉两种语言中都有着重要的作用。它在拼写规则中发挥着不同的音值,并广泛应用于其他领域,如编程和数学。 ### 回答2: 对于字母"c"来说,它是英语字母表中的第三个字母。它是一个辅音字母,发音为/k/。 在英语中,"c"通常会与其他字母组合使用,形成不同的音素。例如,与"a"组合,形成"ca",可以在单词中发出/ka/的音素,例如"cat"(猫);与"i"组合,形成"ci",可以发出/si/的音素,例如"city"(城市);与"o"组合,形成"co",可以发出/ko/的音素,例如"coat"(外套)。 除了在英语中使用外,"c"在其他一些语言中也有不同的用途和发音。例如,在西班牙语中,"c"可以发出"th"的音素,例如"gracias"(谢谢);在法语中,"c"可以发出/s/或/k/的音素,这取决于其与后面字母的组合,例如"cinq"(五)或"croissant"(牛角面包)。 此外,"c"还可以代表其他含义。在数学中,"c"常常表示速度的常数,例如"c=3x10^8"(光速等于3×10的8次方米/秒)。在计算机科学中,"c"是一种编程语言,它被广泛用于开发系统和应用程序。 总的来说,"c"是一个在英语和其他一些语言中使用的字母,它有不同的语音和用途。无论是作为一个字母还是作为代表特定含义的符号,"c"在我们日常生活和学习中都扮演着重要的角色。 ### 回答3: C是一种高级的编程语言,它拥有简洁的语法和强大的功能,被广泛应用于计算机科学领域。C语言的设计初衷是为了开发系统级软件,例如操作系统和编译器等。与其他编程语言相比,C语言更接近底层,更加直接地与计算机硬件进行交互。 C语言具有良好的可移植性和高效性。由于C语言的语法规则较为简单,并且提供了丰富的库函数,使得开发者可以快速构建程序,提高开发效率。此外,C语言还具有较低的内存占用和较高的执行速度,使其成为许多嵌入式系统和高性能计算领域的首选语言。 在C语言中,我们可以使用变量、数组、结构体、指针等数据类型来构建复杂的数据结构,并通过函数来操作这些数据。C语言支持面向过程的编程方式,但也可以通过库函数实现面向对象的特性。此外,C语言还提供了丰富的控制结构,如条件语句、循环语句和跳转语句,使开发者能够灵活地控制程序的流程。 C语言是许多其他编程语言的基础。许多高级编程语言(如C++、Java和Python)都借鉴了C语言的一些语法和特性。因此,学习和掌握C语言对于进一步学习其他编程语言非常有帮助。 总之,C语言是一种重要的编程语言,具有广泛的应用领域和强大的功能。通过学习C语言,我们可以全面理解计算机底层的工作原理,并能够开发出高效、可靠的程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值