给新人的一些基础常识

本文主要介绍了对java对象的简单认知、常见错误排查、常见错误思想和常见陋习

本文适合刚上手的小白或者对基础理解不够的人阅览,已经对android有很好的了解或者什么都不会的可以绕道了。

 

首先:引用是什么?指针是什么?new干了什么?

对于刚撸代码的小白来说引用、指针是左脑的面粉,new是右脑的水,动一动脑筋...

既然面对刚上手的人当然是通俗的讲好了:内存就好比一块矩形,new对象就是在矩形里创建了一块区域(例如:TestClass test=new TestClass()),你的“test”就是引用(比作一个牵线的线头),“=”比作一条有方向的线。发个图更形象,如下代码:

 申明一下,这些图只是方便理解有个概念,并不是原理

public class MainActivity extends AppCompatActivity {
    TestClass mTest;
    static TestClass staticTest;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TestClass test = new TestClass();//示例代码
        test = new TestClass();
        mTest = test;
        staticTest = mTest;
    }

    public static class TestClass {
    }
}

1.首先MainActivity被系统创建:系统会new 一个MainActivity并绑定一些属性(具体过程暂时忽略)

 2.然后系统调用onCreate,并执行完TestClass test = new TestClass()时:对象1被new出来,test引用将线指向对象1

3. 第二句test = new TestClass():test会将线换成对象2

4.第三句 mTest = test:mTest是MainActivity的成员变量,此时mTest也会将线指向对象2

5.第四句staticTest = mTest:staticTest是静态成员变量 (和MainActivity对象无关,可以暂时理解他是属于系统的某个静态块)也指向了对象2

6.onCreate 执行完毕:onCreate方法内声明的引用(对象2)都将结束,经过一段时间触发垃圾回收后对象1会被回收,对象2依然被系统间接的持有,所以不会被回收。

此处说一下垃圾回收原理:从root(此处可以理解为系统)出发所有可达(被引用或间接引用,注意是被引用)的对象都不会回收,不可达的都会被回收

7.过了一段时间,Activity调用了finish:此时系统将会取消对MainActivity的引用,由于MainActivity只有系统一个引用,根据垃圾回收原理,MainActivity将处于待回收状态

 8.再过一段时间,触发垃圾回收时:MainActivity会被回收,而对象2由于一直被静态引用着,所以无法回收,此为内存泄漏之一(后面讲述内存泄漏相关),不想引起泄漏:不使用静态或在合适的时间加上staticTest=null

强引用、软引用、弱引用、虚引用

主要用于垃圾回收时的区分

强引用:上面的举例都是强引用

软引用:可以理解为一条可被切断的线,当内存不足时会切断并回收该对象

弱引用:可以理解为一条随时都有可能断掉的线,每次gc时都有可能回收

虚引用:一条不存在的线(get永远是null),主要用于判断对象是否被回收

此处只做理解,具体用法还是自行查找吧

 

引用和指针的区别

NullPointerException(空指针异常)大家都不陌生吧,所以大家肯定会诧异指针是什么?为什么我没用到过?现在告诉你们一个悲伤的故事:java里面没有指针...😂😂😂...哈哈哈哈哈哈,是不是很惊奇。指针其实是属于c/c++的,这两位可不是一般的nb,如果汇编是鼻祖那么这两位就是祖师爷了,java只是他们的后辈(自行脑补)。所以,遇到这种报指针问题的就脑补成引用就行了,具体区别由于是跨语言的没(博)太(主)大(比)可(较)比(笨)性。

新手的常见问题和陋习:

常见问题1:闪退,什么鬼?一脸懵逼

闪退不可怕,可怕的是你去百度了一下“app打开闪退怎么解决”...

记住闪退第一条:debug看日志,首先debug app(这个还不会的话百度一下吧,一大堆),复现崩溃步骤(如果不能复现,可能和手机型号、手机版本、app版本有关),然后app崩溃,如图(如果按照图上选的发现没日志打印了,自行检查一下app有没有运行(选的就是当前app当然得运行了),设备、包名、日志级别是否选错,筛选是否不小心加上了(logcat也有可能会抽风,一直什么都没有可以考虑重启AS))

找到关键error错误(如果是你手动捕获并打印的一般是蓝色,类似上面的图)和caused by,可以看到MainActivity的108行报了一个空指针异常,点击即可跳到108行

没有指定到我的代码怎么办?

第一种,可以看到自己的代码:先看一下源码(源码也是人写的),常见的错误还是很好解决的。如图,android源码抛出个空指针异常

点进去发现是pkg为null了(@NonNull为警示作用,具体还得看运行)

    public ComponentName(@NonNull Context pkg, @NonNull Class<?> cls) {
        mPackage = pkg.getPackageName();
        mClass = cls.getName();
    }

pkg为什么为null,向下看和自己的代码相关,Main的106行是自己的代码 ,其实是自己传了个null过去

startActivity(new Intent(null, SheZhiActivity.class));

 第二种,错误里全是系统的信息:这个就相对难点了,有以下方式:1.翻译日志:系统错误一般会描述的比较详细,将异常描述翻译过来有可能解决问题;2.断点查看法:找到当前崩溃前自己最后调用的代码,打上断点,然后根据当前断点数据分析原因;3.百度:将如上caused by后面代码复制“Attempt to invoke virtual method   on a null object reference”(注意去掉无关内容)搜索;4.发帖求助:要记住,发帖要贴出以下内容:日志的完整信息(要完整不是一行就完事了)、问题的详细描述(发个“app打开就闪退了”贴吧里都是神仙吗?能算出来?)、出现问题的代码(尽量贴出来,伪代码也行),还是原话发贴“app打开闪退怎么解决”和发“自己为什么是猪”没什么区别

 

常见问题2:NullPointerException(空指针异常)

空指针异常是老生常谈的问题了,但对于刚上手的人一直不明白:明明赋值了为什么还是空?

NullPointerException意思就是就是调用者为null。比如"mTest.length()"报空指针异常,说明mTest一定是null。

至于为什么是null一定是以下3种原因:1.根本没赋值语句,2.赋值的逻辑返回的是null,3.赋值逻辑并没有被先执行(代码执行顺序不对)。如下,虽然mTest被多次赋值,但最终还是抛出了空指针异常。如果一眼就看出问题那么恭喜你,对java理解的比较好。

public class MainActivity extends AppCompatActivity {
    String mTest = getSt();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        printTest();
        initTest();
    }

    private void initTest() {
        mTest = "t";
    }

    private void printTest() {
        System.out.println(mTest.length());
    }

    public String getSt() {
        if (mTest == null) {
            return null;
        } else {
            return mTest + "1";
        }
    }
}

如果没有看出问题也不怕:

1.debug你的app,并在给mTest赋值的地方都打上断点,此处有3个地方

2.运行崩溃的地方,如果你的断点一次没碰到就崩溃了说明你的赋值逻辑没走,如果断点停了我就不多说了吧。本例里停在了“return null;”,返回的就是null当然会崩溃了

 

常见问题3:为什么会内存泄漏?内存溢出是怎么回事?

内存泄漏其实就是你不要的对象但是又回收不掉。好桑心,java不是自动管理内存吗?教程里都是骗人的?

放心,内存泄漏是需要一定条件的,并不是随随便便就会内存泄漏。

引起内存泄漏的条件:一些系统级无法回收的东西强引用了没有用的对象(翻译来翻译去不都是一样嘛)

扯半天说点实际的吧,可能引起内存泄漏的操作:静态属性引用(静态本身就无法回收,比如最初的staticTest是无法回收的),service引用(生命周期太长,如果引用的对象从来不用,不就是内存泄漏吗),类似无限循环或发送了一个延时很长的handler(handler的消息队列也是系统层的,生命周期也很长,详解见:常见问题4),网络请求不设置超时(如果不设置超时或者超时时间很长(我写的都是20s),网络不畅的情况下会等待很久,此时你的Activity也是无法正常回收的),类似Thread调Sleep很久(长时间占用内存又啥都不干),一些第三方SDK需要传类似context、Listener一定要注意(很多第三方SDK是跨进程的,好多东西都保存在静态变量里面,他们很懒基本不置null,比如微信的分享),自定义Application长时间引用了一些无用的对象(有些人认为application不是静态的不会产生内存泄漏,其实application是贯穿整个APP进程的。application在app就是活的,application亡app就一定不在内存里了

内存溢出是怎么回事:内存比作上面的矩形区域,你的app现在想占满整个区域,系统肯定不愿意了,所以就直接给你干掉然后抛出OOM(OutOfMemory)。

内存溢出产生的条件:1.n个内存泄漏(不需要再解释了吧);2.一次性操作过大的东西:读取大文件(比如你想读一个2G的文件到内存当然是不可能的了)、操作大量的Bitmap(bitmap很占用内存的)、类似死循环超量级的递归(这我就不多说了吧)

 注意:由于内部类默认会直接引用外部类,所以如果上述错误的操作引用了内部类同时也会导致外部类泄漏,如下。

public class TestActivity extends BaseActivity {
    static Object test1, test2, test3, test4;
    @Override
    protected void onCreate() {
        test1 = new MyObj();//内部类
        test2 = new Object() {};//匿名内部类
        test3 = new MyStaticObj();//静态类不会引起TestActivity泄漏,其他3个都属于内部类都会引起TestActivity泄漏
        test4 = new MyStaticObj() {};//匿名内部类
    }
    class MyObj {}
    static class MyStaticObj {}
}

原因:静态引用了内部类,所以内部类不会被回收,内部类又直接引用了外部类(可能你感觉啥都没写,但java在创建内部类时就会直接持有外部类的引用),所以外部类也无法回收(还是上面的引用的原理)。

吓得我赶紧把

①
TextView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //...
    }
});

改成

②TextView.setOnClickListener(this);

③错误写法TextView.setOnClickListener(new MyListnenr());
class MyListnenr implements View.OnClickListener {
@Override
    public void onClick(View v) {
        //...
    }
}

④错误写法TextView.setOnClickListener(myListener);
private View.OnClickListener myListener=new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //...
    }
};

⑤错误写法//等等大神级写法

但是③④⑤是完全错误的写法,不仅浪费敲代码时间还影响阅读。首先你得让内部类先泄漏,自己引用内部类内部类又引用自己根本不会泄漏的(参见上面的回收原理),请使用①②写法或者使用黄油刀自动生成。

 

常见问题4:AS总是提示Handler有内存泄漏风险,好怕怕...

Android Studio一直提示不是没道理的,因为handler确实很容易出现内存泄漏。下面介绍如何安全的使用handler:

1.如果你的handler用在主线程,那就简单多了:

只要不是无限循环发送handler(或持续几十秒的handler),并且在你不需要时保证handler没有消息就不会有问题(和普通的对象没啥区别,会被自动回收),也不需要任何特殊处理。错误例子:


    //错误例子:循环倒计时,没有尽头
    private Handler mHandlerDaoJiShi = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //if (isFinishing()) return;//此处为修正结果,当Activity或该类关闭时,应当立即停止

            //...一堆倒计时逻辑

            sendEmptyMessageDelayed(1, 1000);//无限循环发送(同理连发几十秒不判断也是不对的)
        }
    };

2.如果你的handler在子线程里,那必须在适当的时候调用quit来停止handler:

    private Handler mHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //...
        new Thread() {
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        switch (msg.what) {
                            case 0:
                                //...一堆其他逻辑
                                break;
                            case 10:
                                Looper.myLooper().quit();//必须在不要的时候退出handler
                                break;
                            default:
                                break;
                        }
                    }
                };
                Looper.loop();
            }
        }.start();
    }


    @Override
    protected void onDestroy() {
        mHandler.sendEmptyMessage(10);//不需要时必须退出子线程的handler
        super.onDestroy();
    }

3.你也可以根据官方建议写成静态的(没多大作用,不知道为毛是这种建议),但子线程还是必须调用quit 

 

常见问题5:为什么我的改了数据但界面确没有任何变化?

当你第一次写listview的adapter刷新的时候。如下:

    @Override
    public void onCreat(...) {
        super.onCreat(...);
        //...lv初始化省略
        mList = new ArrayList<>();//初始化一个list
        mAdapter = new BaseAdapter(mList);//初始化一个adapter
        mLv.setAdapter(mAdapter);
    }

    public void notify() {
        mList = (ArrayList<String>) getIntent().getSerializableExtra("list");//此表示获取到数据
        mAdapter.notifyDataSetChanged();//刷新adapter,似乎没毛病
    }

然而运行之后脸都被打肿了数据也没变...

其实这个问题很简单,还是你对引用理解的不够透彻,按照我开头讲的思路:

1.初始化时你的mList把线指向了一块list的内存(暂且把这个内存当做0x01吧),然后mAdapter也指向了0x01

2.各种骚操作调用了notify方法:mList把线重新指向了新的list内存(新的哦,0x02),但你的mAdapter依然是0x01,然后调用notifyDataSetChanged

3.请问:你的mAdapter一直在引用着0x01会自动变成0x02吗?当然不会。

如何解决?很简单2种思路均可解决:

①一直使用0x01:变来变去太麻烦,从头到尾都使用一个不就行了。

    public void notify() {
        //直接addAll,不用等于这样就不会重新指向,地址就不会变了
        mList.clear();
        mList.addAll((ArrayList < String >) getIntent().getSerializableExtra("list"));
        mAdapter.notifyDataSetChanged();
    }

②adapter也改成0x02:把adapter也同时改成0x02不就解决了。我们需要在adapter里增加setList方法,然后刷新之前调用即可

    //你的adapter里
    public void setList(ArrayList<String> list){
        this.list=list;//adapter将list重新赋值
    }
    public void notify() {
        mList = (ArrayList<String>) getIntent().getSerializableExtra("list");
        mAdapter.setList(mList);//把adapter的list重新赋值
        mAdapter.notifyDataSetChanged();
    }

 

常见问题6:APP闪退时Logcat的日志总是一闪而过,完全抓不到嘛

由于各种第三方厂商效仿苹果,崩溃不会提示而是直接重启APP,导致APP内的日志被重新刷新看不到崩溃信息。如果你在Logcat中选择了你的包名和“Show only selected application”(意思为只展示当前选中的包名)则一定会出现这种问题(截图效果见常见问题1),你只需要把“Show only...”改成“No Filters”(意思为不过滤,展示所有APP日志)然后向上找到你app的错误就行了(你可以使用筛选或者日志级别来更快的找到)

 

 错误思想1:据说静态常量也有可能回收,于是啥都写在自定义Application里

如图,博主比较懒所以没写setget方法(也不太喜欢这种写法,太冗余,一般不需要,除非你是开发SDK的)

public class MyApplication extends MultiDexApplication {
    public static PayCache staticPayCache;//这种为了防止被回收放在Application里是绝对错的,之前说过静态的和当前类无关
    public MainToHomeData mMthd;//这种写法也不对的,各个界面之间的数据应该由两界面处理,就算数据比较杂也不能放在这里,反而会引起泄漏
    public SharedPreferences mSingleSp;//这种写法是对的,将sp归为一个,配合下面的mApp可以实现任意对象访问sp文件
    public static MyApplication mApp;//这种写法比较特殊,上面说过Application对象和app共存亡,并且Application也是单例的,其他地方切勿模仿

    @Override
    public void onCreate() {
        super.onCreate();
        mApp=this;
        //...初始化sp信息
        mSingleSp=getSharedPreferences("data", Context.MODE_PRIVATE);
    }
}

 

错误思想2:内存泄漏太可怕,上面说的都不要用

可怕的不在于泄漏,而在于用的人。

和上面的mApp一样,如果你到处都在用,干嘛不声明成单例的呢?

但也有反作用哦:比如MainActivity一般都是垫底的、不会finish的、经常用到的,所以你搞成静态的了

public class MainActivity extends AppCompatActivity{
    public static MainActivity mainActivity;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainActivity=this;
    }
}

然而有没有想过,当退出app的时候,这个引用依然存在(app并没有死,只是没有界面了而已,除非用户手动杀死)。那难道就不能用了?其实你只要在Destroy的时候置null就行了

public class MainActivity extends AppCompatActivity {
    public static MainActivity mainActivity;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainActivity = this;
    }

    @Override
    protected void onDestroy() {
        mainActivity = null;
        super.onDestroy();
    }
}

所以总结一下:慎用,使用时尽量减少对其他对象或属性的引用并及时释放

 

错误思想3:方法调用方法调用方法...感觉自己高大上

有些人看了2分钟源码或者看了一些jar包就感觉方法调方法是装逼必备啊,于是写了

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initData();
    }

    private void initData() {
//        toastData("这是吐司");一行代码后
        initViewData();
    }

    private void initViewData() {
        //viewxxx,一行代码后
        initViewInfoData();
    }

    private void initViewInfoData() {
        //...各种方法调用
    }
}

别人各种方法是为了给你调用的,你写的方法是干嘛的呢?

凡事都有度,抽离方法有好也有坏,记住以下几点:

1.具有特殊意义:如果某些代码比较特殊,可以抽离成方法(比如网络请求操作)

2.有共性:如果某段方法多次使用,可以抽离成方法(这个方法如果够强大可以写成工具类)

3.单方法逻辑过于复杂:逻辑复杂的代码抽离多个方法比较方便阅读

4.单方法体积过于庞大:一个方法500行博主也不想看到,适当抽离出来一些

5.给其他人使用:如果你的代码要供别人使用,自然少不了方法

6.写SDK:如果你在写sdk,可以多写点方法供第三方调用(当然一堆private也是坑)

7.封装:如果你正在封装基类,当然要加一些特殊方法

还有,不供其他地方使用的方法就不要写public了,虽然你的代码也许从来没人调用过

 

错误思想4:抑制注解符好nb,感觉自己逼格又升一级

看到别人、源码、jar包使用或者代码总是提示警告,于是加上各种抑制符,清爽干净又高大上

@SuppressWarnings("unused", "ConstantConditions")
@TargetApi(Build.VERSION_CODES.LOLLIPOP)

首先告诉你这是严重错误的思想,对于这种注解=掩耳盗铃,这种注解只是告诉编译器“你不要再提示我了”,真正运行的时候该崩溃还是崩溃。顺便给你们发一个16年前辈们的代码

    public TestView(Context context) {
        this(context, null);
    }

    public TestView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, 0, 0);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public TestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

 四个构造函数,没什么报错,加上了高大上的@TargetApi,完美运行到了5.0(16年5.0刚普及)手机上。但当你打开4.x手机的时候,闪退闪退闪退还是闪退,打开日志一看,没有4参的构造函数,what?其实编译器已经明确指出4参构造函数是api21才有,然而你去掩耳盗铃式的抑制了该警告,并且一条龙式的调用该构造(其他构造都调用了4参的,四参的api21以下没有,所以崩溃)。

为什么别人能用而我确一用就出问题?如上别人用的是super,而你用的是this。类似空指针的抑制别人已经提前判断过了,而你什么都不判断直接调用

好害怕这些都不能用那还有什么用?不是不能用,而是只有当你完全确定(不是猜测)不会出现这种问题(属于编译器识别错误)才能使用

 

陋习1:什么都往成员变量上声明

有些人看到那些内存浪费等等的介绍感觉整个app都不好了,于是为了节约内存什么都放在成员变量里面

public class MainActivity extends AppCompatActivity {
    private String mData;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mData = "这是吐司";
        toastData();
        //...n行骚操作
        mData = "这是第二个吐司";
        toastData();
    }
    private void toastData() {
        Toast.makeText(this, mData, Toast.LENGTH_SHORT).show();
    }
}

博主会很伤心的告诉你:不仅没有节约内存,反而浪费了内存并增加了别人的阅读难度

如果你按如下写

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        toastData("这是吐司");
        //...n行骚操作
        toastData("这是第二个吐司");
    }
    private void toastData(String toastSt) {
        Toast.makeText(this, toastSt, Toast.LENGTH_SHORT).show();
    }
}

这个字符串将会很快被回收(它属于方法内变量),而第一种写法只有当Activity finish时才会被回收。为什么又难阅读呢,再给你们发一段我前辈们的代码吧

//这些成员变量没几个真正需要呆在这的
    private double lat;
    private double lon;
    private int productId,storeId;
    private int selectId;
    int position;
    double salePrice;
    List<DeliveryWayBean> wayList;
    SelectStoreAdapter adapter;
    AddressBean addressBean;
...

如果代码提示lat<0了,我现在想看看lat是在哪里被赋值成负数了,是不是感觉很头大,到处都有lat =xxx,到处都是成员变量,真正用到的成员变量反而要翻开仔细找找。

遇到这种其实还是和静态变量的道理差不多:用一下就结束的传参肯定是最好,经常用的(有意义的)或需要全局控制的当然非成员变量莫属了

 

陋习2:万行代码不注释,反正我记着呢;每行代码都注释,记忆消失恐惧症

每行代码都注释就不多说了,主要是浪费时间让人有些头大。整个类没有一行注释这是对自己有多自信

第一条:开发一般不是你一个人,别人看你代码和看天书有什么区别

第二条:给你3个月,你看你的旧代码和猪看没什么区别

注释应该加在关键点上,例如:复杂的逻辑运算、给别人使用的sdk、自定义复杂的逻辑、方法名不能够完全表达方法的意思等。像那些浅显的、谁都能一眼看出的就不要注释了,反而看的时候还要多看一行

 

陋习3:喜欢各种scrollview和listview嵌套

由于新入手的对listview接触比较多,当遇见添加header解决不了的问题的时候于是就出现了scrollview嵌套listview、当遇见多级条目的时候就出现了listview嵌套listview的情况,为了解决滑动冲突去百度各种解决办法。现在我遗憾的告诉你,listview的时代早已过去,上面的问题以前确实存在过,但在RecyclerView的时代里是不存在的。

listview嵌套的缺点(貌似没有优点)

1.绘制效率低下,可能会感觉到卡顿(曾经亲测,仅仅是scrollview嵌套listview展示第一页就需要绘制10次以上才能出来,并且listview在滑动时会一直重绘,如果是普通的view一般2遍就出来了并且很少重绘)

2.内存消耗大,没有复用性,可能会引起OOM(重绘多了自然内存消耗大,没有复用性加载的越多内存消耗越大。假设加载了200张中等大小的图,基本上就滑不动或者挂掉了)

这里说一下复用是干嘛的:现在有10000个条目要展示,你把这些条目都加载到内存基本上会挂掉。仔细想想,其实手机屏幕最多也就能显示10个而已。所以就有了大胆的个想法,当滑动的时候仅仅把具体位置的10条加载出来,其他都不去管,这样内存里永远只有10个,理论上就可以无限加载了,这就是复用。

复用的原理:首先listview(RecyclerView同理)会从第一个去加载view(此时convertView一定是null),当第10个时发现铺满全屏了,加载结束。现在你往下滑了一下,需要加载第11个,此时lv判断出第一个已经在屏幕外了,于是lv将第一个移除,放在了getView的convertView里,剩下的就是大家经常做的了,如下代码:如果convertView不是null就将convertView拿来修修补补再返回给lv,如果是null就创建新的。

    public final View getView(int position, View convertView, ViewGroup parent) {
        VH holder;
        if (convertView == null || convertView.getTag(R.id.tag_view_holder) == null) {
            //模仿recyclerview,除了bind是position外,其他都是viewType
            holder = onCreateViewHolder(parent, getItemViewType(position), mInflater);
            holder.itemView.setTag(R.id.tag_view_holder, holder);
        } else {
            holder = (VH) convertView.getTag(R.id.tag_view_holder);
        }
        bindViewHolder(holder, position);
        return holder.itemView;
    }

了解了复用的原理想必经常出现的复用问题大家也明白为什么了吧。如下代码,如果在adapter里这么写会出现滑着滑着所有的tv都看不见了。因为你的tv是复用的,这次把它gone了,下次复用没有visible,setText自然也看不到了,到最后把那10个复用的tv都gone掉了,当然什么都看不见了。类似图片加载错乱也同理。所以:复用的控件if干了啥else必须反过来再干一遍(else干的if自然也少不了)

if (bean.a==1){
    holder.mTv.setVisibility(View.GONE);
}else{
    //holder.mTv.setVisibility(View.VISIBLE);
    holder.mTv.setText("1");
}

不扯这么多了,给大家看一下RecyclerView的优点

1.绘制效率极高(就算嵌套也和普通的控件没什么区别)

2.可以共用复用池(多个rv可以共享一个复用池来提高嵌套的复用性)

3.横滑、竖滑、多列、瀑布流仅仅是一行代码的事

4.和Material Design系列控件随意组合效果炫酷

5.倒序排列、旋转布局、翻页布局、翻书布局、浮动布局、上拉下拉、滑动动画等等等等开源框架诸多,只要是跟列表有关的都是它,这些也都是一两行代码的事

...还有更多等你探索

新人可能恐惧RecyclerView,其实rv比lv更简单,如下rv展示列表只比listview多1行:

mRv.setLayoutManager(new LinearLayoutManager(this));
mRv.setAdapter(new XXAdapter(...));

后续将加上更多rv的使用方式

rv对adapter的简单封装,可以供使用和学习:https://blog.csdn.net/weimingjue/article/details/88190755


转载请注明出处王能的博客:https://blog.csdn.net/weimingjue/article/details/87921494

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页