重构了之前一个自定义view中的视图绘制代码。
业务需求是,在指定区域内绘制一排竖线,显示区域内一共51个,区域外的70个,每10个显示一个长的竖线,要求可以用手指左右滑动,所有竖线循环显示,队首队尾的竖线首位相接,类似于广告弹幕的循环方式。
之前的代码是面向过程的编码思路,思路很好,但是代码很烂,一大堆参数不知道作什么的,一大堆循环不知道运行到哪里了,bug一大堆不知道怎么调试。再此情况下,决定重构此处代码,改成面向对象编程。
我的思路很简单
1.一个51长度的数组,表示需要被显示的竖线容器,在ondraw方法中,可以直接根据此数组绘制出所有竖线。一个121长度的数组,作为被复用的静态对象池。
2.一个基类Line,定义一些参数,竖线的宽度,长度,位置偏移量,颜色。两个实现内部静态类,ShortLine和LongLine
3.在view类创建时,在static块中初始化所有会用到的对象,即107个ShortLine和13个LongLine对象
4.在view处理点击事件的地方,计算出需要展示的对象位置,获得静态对象池中的数组下标,从此开始复制数组。
众所周知,内存泄漏有一种情况,是容器类持有了对象的引用,那么在此例中是否有这种可能性呢?
思考一下,我们做了什么,在数组中引用了静态内部类的实例,这会不会导致内存泄漏呢?
带着这个问题,我决定做一个小的实验
1.创建一个静态数组,一个静态ArrayList
2.创建一个基类Fruit,一个实现类Apple
3.在程序开始,new100个Fruit对象,分别填充到数组和ArrayList中
4.点击跳转到下一个activity中,并且在之前的activity的生命周期函数中
跳转后做两个对比
(1)new100个Apple对象替换之前的fruit对象,观察之前的Fruit对象是否被回收。
(2)clear ArrayList,把数组一个一个赋值null,观察Fruit对象的引用情况,对比做清除操作是否带来变化。
下面开始具体实验
第一种情况:
数组ArrayList分别加入100个new Fruit,然后跳转到下一个activity中
ArrayList
private static final ArrayList<Fruit> fruitsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.tv_to_next);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, MainActivity2.class));
}
});
arrayListReference();
}
public void arrayListReference(){
//test1.1
for(int i = 0; i<100; i++){
fruitsList.add(new Fruit(i));
}
}
跳转activity之前的dump信息,显示出fruit对象有100个,并且被ArrayList直接引用
跳转activity之后的dump信息,显示出fruit对象有100个,并且被ArrayList直接引用,此时可以说发生了内存泄漏
1.1after_jump_mem_dump.png
数组
private static final Fruit[] fruitsArray = new Fruit[100];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.tv_to_next);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, MainActivity2.class));
}
});
arrayReference();
}
public void arrayReference(){
//test1.2
for(int i = 0; i<100; i++){
fruitsArray[i] = new Fruit(i);
}
}
跳转activity之前的dump信息,显示出fruit对象有100个,并且被直接引用
1.2before_jump_mem_dump_a.png
跳转activity之后的dump信息,显示出fruit对象有100个,并且被直接引用,此时可以说发生了内存泄漏
这种情况是可以抢救一下的,我们在activity的生命周期函数onPause中,可以释放一些引用。那么在这种情况下,又会发生什么呢?
ArrayList
(PS:不要被dump里的Fruit[]这个对象迷惑了,我为了省事,没有注销掉private static final Fruit[] fruitsArray = new Fruit[100];这行代码,忽略即可)
加入下列代码
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
//test2.1
fruitsList.clear();
}
直接看跳转之后,dump情况如何
有小伙伴可能会有疑问,woc,你这100个对象还在这里躺着呢啊!!!
不用着急,我们手动触发一下GC试一试
2.1after_clear_jump_mem_dump_GC.png
此时就会发现,这100个对象已经被回收了。内存泄漏问题可以说被解决了。
数组
加入下列代码
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
//test2.2
for (int i = 0; i < fruitsArray.length - 1;i++){
fruitsArray[i] = null;
}
}
直接看跳转之后,dump情况如何,这次就不放手动GC的图了,有兴趣的小伙伴可以自行实践一下
2.2after_clear_jump_mem_dump_a.png
事情到这里,有聪明的小伙伴就要问了,如果替换元素会发生什么情况呢?项目中的实际情况,中也有替换的场景,那么被替换下来的元素会不会造成内存泄漏呢?
ArrayList
更新下列代码
public void arrayListReference(){
//test1.1
for(int i = 0; i<100; i++){
fruitsList.add(new Fruit(i));
}
fruitsList.clear();
for(int i = 0; i<100; i++){
fruitsList.add(new Apple(i));
}
}
这里已经搜不到Fruit实例了
1.1after_replace_mem_dump.png
但是搜索apple实例,还是存在的(PS:这里用数组替换的结果做示例,结果都是一样的。)
1.2after_replace_mem_dump_a.png
数组
更新下列代码
public void arrayReference(){
//test1.2
for(int i = 0; i<100; i++){
fruitsArray[i] = new Fruit(i * 2);
}
for(int i = 0; i<100; i++){
fruitsArray[i] = new Apple(i * 3);
}
}
实验结束,总结一下,在activity生命周期中,如果容器类持有了一些对象的引用,需要在适当的时机(不再需要这些数据时)清除这些引用,不然有可能造成内存泄漏。如果有错误,还请大佬批评指正。
到此为止,其实还有一个拓展思考问题,为什么之前需要手动GC,后面替换之后直接就被回收了?
网友评论