摘要
记一次因为Java基础不牢固,导致踩坑的经历。
背景
因为考拉项目UI升级,但因为新版本UI变动太大,怕考虑不周出问题,所以保留以前版本,也就是要在UI上做ABtest,可以通过开关一键切换。
商品详情页涉及改动面略大,并且旧版产品承诺说会尽快下线,同时时间特别紧张,所以在商详页的改版中,遵循的原则是最小改动,尽量不将新版UI和老版耦合,如果某些模块已经是独立的自定义View,那么重新创建新的,而且因为历史遗留问题,外部Fragment或者Activity存在全局变量的引用,而且有多处,所以我们把新的自定义View继承旧版自定View,复制所有的局部变量和方法,以便后续下线旧版直接删除类文件即可。
部分代码如下
以下为旧版底部购买栏BottomBuyView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class BottomBuyView extends LinearLayout { private ForbidFastClickListener mClickListener = new ForbidFastClickListener() { @Override public void onForbidFastClick(View view) { onViewClick(view); } }; private boolean isPunctuality; private boolean isFactory; private boolean isDeposit; private boolean isTimeSale; public BottomBuyView(Context context) { this(context, null); } public BottomBuyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BottomBuyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initViews(); } protected void initViews() { inflate(getContext(), R.layout.bottom_buy_view, this); } }
|
为了最小改动,我们新写一个底部购买栏BottomBuyViewNew
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class BottomBuyViewNew extends BottomBuyView { private ForbidFastClickListener mClickListener = new ForbidFastClickListener() { @Override public void onForbidFastClick(View view) { onViewClick(view); } }; private boolean isPunctuality; private boolean isFactory; private boolean isDeposit; private boolean isTimeSale; public BottomBuyViewNew(Context context) { this(context, null); } public BottomBuyViewNew(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BottomBuyViewNew(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void initViews() { inflate(getContext(), R.layout.bottom_buy_view_new, this); } }
|
以上代码可以注意下第三个构造方法,在BottomBuyView
中调用initView();在BottomBuyViewNew
中只是调用了super(),然后复写initView()来实现替换View的方式,ForbidFastClickListener
为防止快速点击的View.OnClickListener的实现类。
重点来了
发现BottomBuyViewNew
的点击事件一直无法生效,debug发现mClickListener
为null….
百思不得其解,一开始以为是子类和父类有一个同名的全局变量导致的,于是换了个变量名发现依然无法响应点击事件。
那么子类父类局部变量、构造函数的加载顺序到底是怎样?
参考文章Java中父类与子类的加载顺序详解
父类非静态语句块1
父类非静态语句块2
父类构造函数
子类非静态变量
子类非静态语句块1
子类非静态语句块2
子类构造函数
很明显这个顺序就能说明mClickListener
为什么为null,因为initView()是复写的父类的方法,所以在父类的构造函数中就调用了,此时还没有加载子类的非静态变量,所以mClickListener
为null。
为了规避这个问题,我们修改代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class BottomBuyViewNew extends BottomBuyView { private ForbidFastClickListener mClickListener = new ForbidFastClickListener() { @Override public void onForbidFastClick(View view) { onViewClick(view); } }; private boolean isPunctuality; private boolean isFactory; private boolean isDeposit; private boolean isTimeSale; public BottomBuyViewNew(Context context) { this(context, null); } public BottomBuyViewNew(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BottomBuyViewNew(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initViewNew(); } @Override protected void initViews() { } private void initViewNew() { inflate(getContext(), R.layout.bottom_buy_view_new, this); } }
|
结论
基础知识能力需加强,一不小心就容易踩坑。
参考
- Java中父类与子类的加载顺序详解