热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android中复用问题哲理性解析

Android中列表的复用机制提高了APP的运行效率,但随之而来的复用的问题总是让程序员们头痛,一个bug找头天也找不到。我就把自己解决这方面的经验

Android中列表的复用机制提高了APP的运行效率,但随之而来的复用的问题总是让程序员们头痛,一个

bug找头天也找不到。我就把自己解决这方面的经验贡献出来供大家参考:


问题1:什么是复用

    复用其实指的是复用View,而绑定View的数据是变化的。


问题2:复用的原理探究

    

 为了彻底弄清楚复用的原理,和特地写了段小程序。

   Adapter代码:

     

class MyAdapter extends BaseAdapter{

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

    @Override
    public Object getItem(int position) {
        return null;
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {



        Log.i(TAG, "aaaaaaaaaa---------- getView: position = " + position  + ",convertView = " + convertView);

        ViewHolder holder;
        if(convertView == null){
            convertView = LayoutInflater.from(SimpleCheckBoxListActivity.this).inflate(R.layout.adapter_simple_checkbox_item,null,false);
            holder = new ViewHolder();
            holder.tv = (TextView) convertView.findViewById(R.id.tv);
            holder.cb = (CheckBox) convertView.findViewById(R.id.cb);
            convertView.setTag(holder);
        }else{
            holder = (ViewHolder) convertView.getTag();
        }
        holder.tv.setText("index = " + position);

        Log.i(TAG, "bbbbbbbbbb---------- getView: position = " + position + ",convertView = " + convertView.toString());


        //将convertView缓存起来,方便后面的分析。
        itemViews.put(position,convertView);

        //分析当前position是否复用了之前哪个位置的view
        int reusePosition = analyseReusedWhichPosition(position);
        if(reusePosition != -1){
            Log.i(TAG, "getView: 位置 " + position + "复用了位置" + reusePosition + "的view");
        }
        return convertView;
    }


    class ViewHolder{
        TextView tv;
        CheckBox cb;
    }


    //分析当前position是否复用了之前哪个位置的view
    private int analyseReusedWhichPosition(int currentPosition){
        View currentPositionView = itemViews.get(currentPosition);
        for (int i = 0; i < currentPosition; i++) {
            View beforePositionView = itemViews.get(i);
            if(beforePositionView == null){
                continue;
            }

            if(beforePositionView == currentPositionView){
                return i;
            }
        }

        return -1;
    }
}

   日志分析:

 1)程序初次运行

    Android中复用问题哲理性解析   


   打印的日志:

    

aaaaaaaaaa---------- getView: position = 0,convertView = null    
bbbbbbbbbb---------- getView: position = 0,convertView = android.widget.LinearLayout{42eceab0 V.E..... ......I. 0,0-0,0}    
aaaaaaaaaa---------- getView: position = 1,convertView = null    
bbbbbbbbbb---------- getView: position = 1,convertView = android.widget.LinearLayout{42ee4650 V.E..... ......I. 0,0-0,0}    
aaaaaaaaaa---------- getView: position = 2,convertView = null    
bbbbbbbbbb---------- getView: position = 2,convertView = android.widget.LinearLayout{42ee6140 V.E..... ......I. 0,0-0,0}    
aaaaaaaaaa---------- getView: position = 3,convertView = null    
bbbbbbbbbb---------- getView: position = 3,convertView = android.widget.LinearLayout{42ee7c10 V.E..... ......I. 0,0-0,0}    
aaaaaaaaaa---------- getView: position = 4,convertView = null    
bbbbbbbbbb---------- getView: position = 4,convertView = android.widget.LinearLayout{42ee96e0 V.E..... ......I. 0,0-0,0}    
aaaaaaaaaa---------- getView: position = 5,convertView = null    
bbbbbbbbbb---------- getView: position = 5,convertView = android.widget.LinearLayout{42eeb1e8 V.E..... ......I. 0,0-0,0}    
aaaaaaaaaa---------- getView: position = 6,convertView = null    
bbbbbbbbbb---------- getView: position = 6,convertView = android.widget.LinearLayout{42eeccb8 V.E..... ......I. 0,0-0,0}    
aaaaaaaaaa---------- getView: position = 7,convertView = null    
bbbbbbbbbb---------- getView: position = 7,convertView = android.widget.LinearLayout{42eee788 V.E..... ......I. 0,0-0,0}

 2)接着向下滑动,索引0没有完全消失,索引8就出现了,这时还没有复用。

   Android中复用问题哲理性解析     

  打印的日志:

   

aaaaaaaaaa---------- getView: position = 8,convertView = null    
bbbbbbbbbb---------- getView: position = 8,convertView = android.widget.LinearLayout{42ef5150 V.E..... ......I. 0,0-0,0}

 3)位置9出现,索引0已经完全消失(复用开始出现)

 Android中复用问题哲理性解析      

 打印的日志:

  

aaaaaaaaaa---------- getView: position = 9,convertView = android.widget.LinearLayout{42eceab0 V.E..... ........ 0,-215-1080,1}    
bbbbbbbbbb---------- getView: position = 9,convertView = android.widget.LinearLayout{42eceab0 V.E..... .......D 0,-215-1080,1}    
getView: 位置 9复用了位置0的view    
可以发现索引9处的hashCode与索引0处的hashCode都是42eceab0

 4)紧接着向下滚动到最后(注意是慢慢地滚动

  Android中复用问题哲理性解析

 打印的日志:

   

aaaaaaaaaa---------- getView: position = 10,convertView = android.widget.LinearLayout{42ee4650 V.E..... ........ 0,-213-1080,3}    
bbbbbbbbbb---------- getView: position = 10,convertView = android.widget.LinearLayout{42ee4650 V.E..... .......D 0,-213-1080,3}    
getView: 位置 10复用了位置1的view    
aaaaaaaaaa---------- getView: position = 11,convertView = android.widget.LinearLayout{42ee6140 V.E..... ........ 0,-205-1080,11}    
bbbbbbbbbb---------- getView: position = 11,convertView = android.widget.LinearLayout{42ee6140 V.E..... .......D 0,-205-1080,11}    
getView: 位置 11复用了位置2的view    
aaaaaaaaaa---------- getView: position = 12,convertView = android.widget.LinearLayout{42ee7c10 V.E..... ........ 0,-202-1080,14}    
bbbbbbbbbb---------- getView: position = 12,convertView = android.widget.LinearLayout{42ee7c10 V.E..... .......D 0,-202-1080,14}    
getView: 位置 12复用了位置3的view    
aaaaaaaaaa---------- getView: position = 13,convertView = android.widget.LinearLayout{42ee96e0 V.E..... ........ 0,-201-1080,15}    
bbbbbbbbbb---------- getView: position = 13,convertView = android.widget.LinearLayout{42ee96e0 V.E..... .......D 0,-201-1080,15}    
getView: 位置 13复用了位置4的view    
aaaaaaaaaa---------- getView: position = 14,convertView = android.widget.LinearLayout{42eeb1e8 V.E..... ........ 0,-188-1080,28}    
bbbbbbbbbb---------- getView: position = 14,convertView = android.widget.LinearLayout{42eeb1e8 V.E..... .......D 0,-188-1080,28}    
getView: 位置 14复用了位置5的view    
aaaaaaaaaa---------- getView: position = 15,convertView = android.widget.LinearLayout{42eeccb8 V.E..... ........ 0,-213-1080,3}    
bbbbbbbbbb---------- getView: position = 15,convertView = android.widget.LinearLayout{42eeccb8 V.E..... .......D 0,-213-1080,3}    
getView: 位置 15复用了位置6的view    
aaaaaaaaaa---------- getView: position = 16,convertView = android.widget.LinearLayout{42eee788 V.E..... ........ 0,-179-1080,37}    
bbbbbbbbbb---------- getView: position = 16,convertView = android.widget.LinearLayout{42eee788 V.E..... .......D 0,-179-1080,37}    
getView: 位置 16复用了位置7的view    
aaaaaaaaaa---------- getView: position = 17,convertView = android.widget.LinearLayout{42ef5150 V.E..... ........ 0,-181-1080,35}    
bbbbbbbbbb---------- getView: position = 17,convertView = android.widget.LinearLayout{42ef5150 V.E..... .......D 0,-181-1080,35}    
getView: 位置 17复用了位置8的view    


aaaaaaaaaa---------- getView: position = 18,convertView = android.widget.LinearLayout{42eceab0 V.E..... ........ 0,-195-1080,21}    
bbbbbbbbbb---------- getView: position = 18,convertView = android.widget.LinearLayout{42eceab0 V.E..... .......D 0,-195-1080,21}    
getView: 位置 18复用了位置0的view    
aaaaaaaaaa---------- getView: position = 19,convertView = android.widget.LinearLayout{42ee4650 V.E..... ........ 0,-210-1080,6}    
bbbbbbbbbb---------- getView: position = 19,convertView = android.widget.LinearLayout{42ee4650 V.E..... .......D 0,-210-1080,6}    
getView: 位置 19复用了位置1的view

 

 可以看到向下慢慢滑动的时候,复用是很有规律的。

 但是如果快速的向下滑动的时候,又发现不了什么规律:复用并非是连续的

 

aaaaaaaaaa---------- getView: position = 8,convertView = android.widget.LinearLayout{42f9a780 V.E..... ........ 0,-85-1080,131}
bbbbbbbbbb---------- getView: position = 8,convertView = android.widget.LinearLayout{42f9a780 V.E..... .......D 0,-85-1080,131}
getView: 位置 8复用了位置0的view
aaaaaaaaaa---------- getView: position = 9,convertView = android.widget.LinearLayout{42f9f818 V.E..... ........ 0,384-1080,600}
bbbbbbbbbb---------- getView: position = 9,convertView = android.widget.LinearLayout{42f9f818 V.E..... .......D 0,384-1080,600}
getView: 位置 9复用了位置3的view
aaaaaaaaaa---------- getView: position = 10,convertView = android.widget.LinearLayout{42f9dd48 V.E..... ........ 0,138-1080,354}
bbbbbbbbbb---------- getView: position = 10,convertView = android.widget.LinearLayout{42f9dd48 V.E..... .......D 0,138-1080,354}
getView: 位置 10复用了位置2的view
aaaaaaaaaa---------- getView: position = 11,convertView = android.widget.LinearLayout{42f9c278 V.E..... ........ 0,-108-1080,108}
bbbbbbbbbb---------- getView: position = 11,convertView = android.widget.LinearLayout{42f9c278 V.E..... .......D 0,-108-1080,108}
getView: 位置 11复用了位置1的view
aaaaaaaaaa---------- getView: position = 12,convertView = null
bbbbbbbbbb---------- getView: position = 12,convertView = android.widget.LinearLayout{42fad3a0 V.E..... ......I. 0,0-0,0}
aaaaaaaaaa---------- getView: position = 13,convertView = android.widget.LinearLayout{42fa2df0 V.E..... ........ 0,60-1080,276}
bbbbbbbbbb---------- getView: position = 13,convertView = android.widget.LinearLayout{42fa2df0 V.E..... .......D 0,60-1080,276}
getView: 位置 13复用了位置5的view
aaaaaaaaaa---------- getView: position = 14,convertView = android.widget.LinearLayout{42fa12e8 V.E..... ........ 0,-186-1080,30}
bbbbbbbbbb---------- getView: position = 14,convertView = android.widget.LinearLayout{42fa12e8 V.E..... .......D 0,-186-1080,30}
getView: 位置 14复用了位置4的view
aaaaaaaaaa---------- getView: position = 15,convertView = android.widget.LinearLayout{42fa48c0 V.E..... ........ 0,-150-1080,66}
bbbbbbbbbb---------- getView: position = 15,convertView = android.widget.LinearLayout{42fa48c0 V.E..... .......D 0,-150-1080,66}
getView: 位置 15复用了位置6的view
aaaaaaaaaa---------- getView: position = 16,convertView = android.widget.LinearLayout{42f9a780 V.E..... ........ 0,78-1080,294}
bbbbbbbbbb---------- getView: position = 16,convertView = android.widget.LinearLayout{42f9a780 V.E..... .......D 0,78-1080,294}
getView: 位置 16复用了位置0的view
aaaaaaaaaa---------- getView: position = 17,convertView = android.widget.LinearLayout{42f9f818 V.E..... ........ 0,13-1080,229}
bbbbbbbbbb---------- getView: position = 17,convertView = android.widget.LinearLayout{42f9f818 V.E..... .......D 0,13-1080,229}
getView: 位置 17复用了位置3的view
aaaaaaaaaa---------- getView: position = 18,convertView = android.widget.LinearLayout{42f9dd48 V.E..... ........ 0,-28-1080,188}
bbbbbbbbbb---------- getView: position = 18,convertView = android.widget.LinearLayout{42f9dd48 V.E..... .......D 0,-28-1080,188}
getView: 位置 18复用了位置2的view
aaaaaaaaaa---------- getView: position = 19,convertView = android.widget.LinearLayout{42fa6390 V.E..... ........ 0,-168-1080,48}
bbbbbbbbbb---------- getView: position = 19,convertView = android.widget.LinearLayout{42fa6390 V.E..... .......D 0,-168-1080,48}
getView: 位置 19复用了位置7的view


5)最后,向上滚动到索引为0的位置

   

aaaaaaaaaa---------- getView: position = 11,convertView = android.widget.LinearLayout{4304de70 V.E..... ........ 0,-212-1080,4}
bbbbbbbbbb---------- getView: position = 11,convertView = android.widget.LinearLayout{4304de70 V.E..... .......D 0,-212-1080,4}
getView: 位置 11复用了位置8的view
aaaaaaaaaa---------- getView: position = 10,convertView = android.widget.LinearLayout{4303ee70 V.E..... ........ 0,1829-1080,2045}
bbbbbbbbbb---------- getView: position = 10,convertView = android.widget.LinearLayout{4303ee70 V.E..... .......D 0,1829-1080,2045}
getView: 位置 10复用了位置1的view
aaaaaaaaaa---------- getView: position = 9,convertView = android.widget.LinearLayout{43040940 V.E..... ........ 0,1829-1080,2045}
bbbbbbbbbb---------- getView: position = 9,convertView = android.widget.LinearLayout{43040940 V.E..... .......D 0,1829-1080,2045}
getView: 位置 9复用了位置2的view
aaaaaaaaaa---------- getView: position = 8,convertView = android.widget.LinearLayout{4303d378 V.E..... ........ 0,1830-1080,2046}
bbbbbbbbbb---------- getView: position = 8,convertView = android.widget.LinearLayout{4303d378 V.E..... .......D 0,1830-1080,2046}
getView: 位置 8复用了位置0的view
aaaaaaaaaa---------- getView: position = 7,convertView = android.widget.LinearLayout{430474b8 V.E..... ........ 0,1825-1080,2041}
bbbbbbbbbb---------- getView: position = 7,convertView = android.widget.LinearLayout{430474b8 V.E..... .......D 0,1825-1080,2041}
getView: 位置 7复用了位置6的view
aaaaaaaaaa---------- getView: position = 6,convertView = android.widget.LinearLayout{43048f88 V.E..... ........ 0,1824-1080,2040}
bbbbbbbbbb---------- getView: position = 6,convertView = android.widget.LinearLayout{43048f88 V.E..... .......D 0,1824-1080,2040}
aaaaaaaaaa---------- getView: position = 5,convertView = android.widget.LinearLayout{43043ee0 V.E..... ........ 0,1822-1080,2038}
bbbbbbbbbb---------- getView: position = 5,convertView = android.widget.LinearLayout{43043ee0 V.E..... .......D 0,1822-1080,2038}
getView: 位置 5复用了位置4的view
aaaaaaaaaa---------- getView: position = 4,convertView = android.widget.LinearLayout{430459e8 V.E..... ........ 0,1823-1080,2039}
bbbbbbbbbb---------- getView: position = 4,convertView = android.widget.LinearLayout{430459e8 V.E..... .......D 0,1823-1080,2039}
aaaaaaaaaa---------- getView: position = 3,convertView = android.widget.LinearLayout{43042410 V.E..... ........ 0,1829-1080,2045}
bbbbbbbbbb---------- getView: position = 3,convertView = android.widget.LinearLayout{43042410 V.E..... .......D 0,1829-1080,2045}
aaaaaaaaaa---------- getView: position = 2,convertView = android.widget.LinearLayout{4304de70 V.E..... ........ 0,1826-1080,2042}
bbbbbbbbbb---------- getView: position = 2,convertView = android.widget.LinearLayout{4304de70 V.E..... .......D 0,1826-1080,2042}
aaaaaaaaaa---------- getView: position = 1,convertView = android.widget.LinearLayout{4303ee70 V.E..... ........ 0,1828-1080,2044}
bbbbbbbbbb---------- getView: position = 1,convertView = android.widget.LinearLayout{4303ee70 V.E..... .......D 0,1828-1080,2044}
aaaaaaaaaa---------- getView: position = 0,convertView = android.widget.LinearLayout{43040940 V.E..... ........ 0,1788-1080,2004}
bbbbbbbbbb---------- getView: position = 0,convertView = android.widget.LinearLayout{43040940 V.E..... .......D 0,1788-1080,2004}



如上所述,到底谁复用了谁是随机不定的,这个我们也没有必要去关心。我们只要知道position是不变就行了。


 另外,除了打日志。可以选中某一个位置的checkbox,然后上下滑动,如果某个checkbox也莫名的选中了,那就说明这个位置的checkbox复用了之前选中的那个checkbox。

  

问题3:Adapter的notifyDataSetChanged()方法作了什么事情

 notifyDataSetChanged,会重新走一遍可见的position的getView方法。

 

问题4:复用出现的场景

    1.if-else的坑:在Adapter中,如果绑定View的数据的时候如果有if判断,往往很多人忘记了加else,这是大多数复用问题出现的根源之一。在一般情况下else不写没有逻辑错误,但是在ListView复

用的情况下如果不写错误就会带来错乱的麻烦。

     实际场景:

    Android中复用问题哲理性解析     比如每个item可能有或没有图片picarrList,之前我只加了if判断,如果有图片就显示。但后来上下一滑动之后发现没有图片的item竟然也显示了其它了item的图片,于是追根溯源发现是这里的问题。


   2.checkbox等的复用问题:果如下图,是一个简单的CheckBox列表

    Android中复用问题哲理性解析

  第1页刚好0-8索引,我将0索引处的checkbox设置为选中状态,然后向下滑动,发现下一个出现的checkbox(索引为10,不是9,也不一定就是10,而是索引0完全消失之后第一个出现的item)竟然也选中了。 

  百度了一下,可以用Map来记录对应position的checkbox的选中状态。而且网上

的这个Map是事先就是预订好大小的了,但实际中Map的大小是确定的。

 细节1):Map来记录对应position的checkbox的选中状态,怎么初始化?

      --1-- 可以先在成员或者构造方法里实例化Map对象

        

Map isTitleCheckBoxSelected = new HashMap();

      --2-- 在getView方法里初始化Map对象,默认checkbox都是未选中状态

if(!isTitleCheckBoxSelected.containsKey(position)){
    Log.i(TAG, "bindData: init checkbox " + position);
    isTitleCheckBoxSelected.put(position,false);
    //如果启动了全选,则新出现的view也要选中。
    if(isSelectedAllStarted){
        isTitleCheckBoxSelected.put(position,true);
    }
}

   上面的这段代码其实是非常妙的,通过contains判断,保证了初始化。如果后面操作了map,

也不会影响这段代码对map的初始化。

   map这种数据结构,由于key是唯一,可以做去重操作。这一点List则不可直接做到。



 细节2):响应checkbox的OnCheckedChangeListener事件,将改变后的状态保存到map中。

   

header_checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if(isCheckedByCode) return;
        isTitleCheckBoxSelected.put(position, !isTitleCheckBoxSelected.get(position));
    }
});

  在onCheckedChanged方法里将对应position的checkbox的状态反转。


 细节3):将map中的对应position的状态值赋值给当前的checkbox

 但是有个问题,checkbox的setChecked方法,看其源码,会走OnCheckedChangeListener的回调

 Android中复用问题哲理性解析   

 而此时,setChecked方法我只想设置View的状态,并不想走它的回调方法。下面有2种方法可以解决这个问题

 方法1:在setChecked方法的前后用一个变量夹住,在回调方法里通过这个变量判断回调是不是在代码

里通过setChecked触发,如果是setChecked触发的,则不执行map的取反的操作。

isCheckedByCode = true;
header_checkbox.setChecked(isTitleCheckBoxSelected.get(position));
isCheckedByCode = false;

 这种方法多申请了个变量,耦合度比较高。

 方法2:在setChecked方法之前将checkbox的监听设置为null,在setChecked方法之后设置真正的监听。


 除了checkbox,其它的一些view,也可以通过以上的方法来解决复用的问题。解决复用要遵循一个原则:MV分离,在view一些事件监听里,一般情况下改变记录状态的Map值之后,切记立马就将值设置给View,而应该通过notifyDatasetChanged()方法将状态更新到view上。



 


 


推荐阅读
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 实验九:使用SharedPreferences存储简单数据
    本实验旨在帮助学生理解和掌握使用SharedPreferences存储和读取简单数据的方法,包括程序参数和用户选项。 ... [详细]
  • 本文介绍了一种自定义的Android圆形进度条视图,支持在进度条上显示数字,并在圆心位置展示文字内容。通过自定义绘图和组件组合的方式实现,详细展示了自定义View的开发流程和关键技术点。示例代码和效果展示将在文章末尾提供。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
  • 设计实战 | 10个Kotlin项目深度解析:首页模块开发详解
    设计实战 | 10个Kotlin项目深度解析:首页模块开发详解 ... [详细]
  • 深入解析 Android TextView 中 getImeActionLabel() 方法的使用与代码示例 ... [详细]
  • Android 自定义 RecycleView 左滑上下分层示例代码
    为了满足项目需求,需要在多个场景中实现左滑删除功能,并且后续可能在列表项中增加其他功能。虽然网络上有很多左滑删除的示例,但大多数封装不够完善。因此,我们尝试自己封装一个更加灵活和通用的解决方案。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 在使用 Qt 进行 YUV420 图像渲染时,由于 Qt 本身不支持直接绘制 YUV 数据,因此需要借助 QOpenGLWidget 和 OpenGL 技术来实现。通过继承 QOpenGLWidget 类并重写其绘图方法,可以利用 GPU 的高效渲染能力,实现高质量的 YUV420 图像显示。此外,这种方法还能显著提高图像处理的性能和流畅性。 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
author-avatar
幽咽小香
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有