上一篇讲解了自定义控件的绘制及动画效果的实现原理,不过示例代码还有很多很多需要改进的地方,本篇来讲解几个优化的技巧。
优化1:减少在onDraw方法上的计算和对象创建
在上一篇文章我们知道,控件要多次调用onDraw方法才能实现动画效果。如果在onDraw方法上做过于耗时的事情,就会导致动画看起来很卡顿,严重的话甚至导致ANR。
其次,由于频繁调用onDraw方法,如果在上面创建对象,会产生大量临时的对象,进而导致GC频繁。
回顾一下上一篇写的onDraw方法。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
//1
Paint paint = new Paint();
paint.setColor(Color.RED);
int left = (width - 50) / 2;
int top = (height - 50) / 2;
int right = left + 50;
int bottom = top + 50;
canvas.drawRect(left,top,right,bottom,paint);
}
代码1的Paint的实例对象实在太“短命”了,创建出来才画了一个正方形,就要等待被回收了。我们可以改进一下代码,减少“短命”实例对象的出现。
//1
private Paint mPaint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
//2
mPaint.setColor(Color.RED);
int left = (width - 50) / 2;
int top = (height - 50) / 2;
int right = left + 50;
int bottom = top + 50;
//3
canvas.drawRect(left,top,right,bottom,mPaint);
}
把Paint变成一个私有成员变量,复用mPaint就可以了。
优化2:避免Handler导致内存泄漏
上篇文章讲了如何通过Handler来实现动画效果,效果是可以实现,但是那么写是有内存泄漏风险的,回顾一下上篇文章的代码。
private Hnadler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
mCount++;
if(mCount <= 4){
invalidate();
sendEmptyMessageDelayed(0,250);
}
}
}
这是一个匿名内部类,匿名内部类会持有外部类的对象实例,简单地说就是外部的类的对象实例和内部类的实例绑定在一起。那么问题就来了。如果sendEmptyMessageDelayed方法的延时时间特别长(比如5分钟),Activity被销毁的时候,由于控件和handler是绑定在一起的,handler迟迟不能被回收,控件也就没法被回收了,这时就导致内存泄漏了。
那该怎么办呢?首先先想想Handler为什么迟迟不能被回收,因为还有一个5分钟后才能发送的消息,取消发送。(Handler里的MessageQueue和Message是强引用的关系,不了解Handler内部原理的朋友可以看看我写的这篇文章)
其次将Handler写成静态内部类的形式,静态内部类持有对外部类对象实例。这里就以WaveLoadingView的Handler为例吧。
private AnimHandler mAnimHandler = new AnimHandler(this);
private static final class AnimHandler extends Handler{
private SoftReference<WaveLoadingView> mView;
public AnimHandler(WaveLoadingView view){
mView = new SoftReference<>(view);
}
@Override
public void handleMessage(Message msg) {
WaveLoadingView view = mView.get();
if(view != null){
view.refreshFrame();
}
}
//1
public void recycle(){
mView.clear();
}
}
WaveLoadingView把处理动画的Handler写成了静态内部类的形式,还提供了一个回收的方法。来看看recycle方法。
protected void recycle(){
//1
mAnimHandler.removeCallbacksAndMessages(null);
mAnimHandler.recycle();
//2
mAnimHandler = null;
}
handler调用removeCallbacksAndMessages方法就可以移除所有Message了。然后重写控件的onDetachedFromWindow方法。
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
recycle();
}
onDetachedFromWindow方法会在Activity销毁时调用,在这个方法上我们可以做一些控件的回收释放操作。这么一来Handler内存泄漏的问题也就得到了解决。
最后
下一篇将讲解如何为控件添加自定义监听器。