1、Handler不销毁
Handler可能出现内存泄漏问题(Activity的生命周期没有Handler的生命周期大),如果Handler不销毁,那么Handler就会一直执行,activity就不会销毁,可以在startScroll()方法中添加Log,然后退出当前Activity,查看该Log是否一直在打印。
解决思路:当Activity退出时,会调用onDetachedFromWindow()方法,我们在这里将Handler移除即可。
@Override
protected void onDetachedFromWindow() {
mHandler.removeMessages(SCROLL_MSG);
mHandler = null;
super.onDetachedFromWindow();
}
在ViewPager源码也是这么做的:
@Override
protected void onDetachedFromWindow() {
removeCallbacks(mEndScrollRunnable);
// To be on the safe side, abort the scroller
if ((mScroller != null) && !mScroller.isFinished()) {
mScroller.abortAnimation();
}
super.onDetachedFromWindow();
}
2、界面复用
mBannerView.setAdapter(new BannerAdapter() {
@Override
public View getView(int position) {
String url = result.get(position).getBanner_url().getUrl_list().get(0).getUrl();
ImageView iv = new ImageView(MainActivity.this);
Glide.with(MainActivity.this).load(url).placeholder(R.mipmap.ic_launcher).into(iv);
return iv;
}
@Override
public int getCount() {
Log.e("TAg", "getCount()" + result.size());
return result.size();
}
});
如上代码,每次getView(),不管三七二十一,都会new ImageView,这肯定不是行的,既然用到了Adapter模式,我们也可以采用BaseAdapter.getView()方法那样,提供一个convertView的参数,当该参数为空时,才去创建ImageView。避免了创建不必要的对象。
修改后的BannerAdapter.getView()方法:
public abstract View getView(View convertView, int position);
BannerViewPager中怎么处理呢?修改的自然是PagerAdapter中实例化和销毁Item这两处进行修改了:
@Override
public Object instantiateItem(ViewGroup container, int position) {
View bannerItemView = mAdapter.getView(getConvertView(), position % mAdapter.getCount());
container.addView(bannerItemView); //container 就是ViewPager
return bannerItemView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
mConvertViews.add((View) object);
}
instantiateItem()方法中,在getView()中添加参数,用于获取convertView,目前先不管它是如何实现的。再来看下,destroyItem()方法,这里将要移除的View添加到了mConvertViews的集合中,还是不懂,直接把这个待销毁的object,传给convertView不就行了吗?当然不行,下面是直接复用View的异常信息:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:4417)
at android.view.ViewGroup.addView(ViewGroup.java:4258)
at android.support.v4.view.ViewPager.addView(ViewPager.java:1509)
at android.view.ViewGroup.addView(ViewGroup.java:4198)
at android.view.ViewGroup.addView(ViewGroup.java:4171)
所以,我们不能直接复用销毁的View,需要先将它们放到一个集合中,然后判断一下,只有这个View的父View为空时,才返回这个View,否则创建新的ItemView。
看来,获取复用View的getConvertViews()方法:
private View getConvertView() {
for (int i = 0; i < mConvertViews.size(); i++) {
// 获取没有添加在ViewPager里面的View,
// 如果没有被父View持有,则复用该View
if (mConvertViews.get(i).getParent() == null) {
return mConvertViews.get(i);
}
}
return null;
}
添加convertView参数后的getView()方法:
@Override
public View getView(View convertView, int position) {
String url = result.get(position).getBanner_url().getUrl_list().get(0).getUrl();
ImageView iv = null;
if (convertView == null) {
iv = new ImageView(BannerActivity.this);
} else {
iv = (ImageView) convertView;
}
Glide.with(MainActivity.this).load(url).placeholder(R.mipmap.ic_launcher).into(iv);
return iv;
}
3、Activity生命周期管理
这里是自定义轮播组件,那么就需要考虑到它的轮播时机,假如已经切换到其他界面了,那么也就没必要继续轮播了。
private Application.ActivityLifecycleCallbacks mActivityLifecycleCallbacks = new SimpleActivityLifecycleCallback() {
@Override
public void onActivityResumed(Activity activity) {
if (activity == mActivity) {
startScroll();
}
}
@Override
public void onActivityPaused(Activity activity) {
if (activity == mActivity) {
mHandler.removeMessages(SCROLL_MSG);
}
}
};
SimpleActivityLifecycleCallback是我们对Application.ActivityLifecycleCallbacks的默认实现,这样就没必要重写那么多不必要的接口了。
我们只管理当前Activity,所以要判断一下,只有是当前Activity时,才进行处理。
注册:
public void setAdapter(BannerAdapter adapter) {
this.mAdapter = adapter;
super.setAdapter(new BannerPagerAdapter());
mActivity.getApplication().registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
}
反注册:
@Override
protected void onDetachedFromWindow() {
mHandler.removeMessages(SCROLL_MSG);
mHandler = null;
mActivity.getApplication().unregisterActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
super.onDetachedFromWindow();
}
4、结尾
用了四篇小文来记录BannerView的开发过程,从泛读ViewPager源码,到BannerView从里到外封装,添加自定义属性,最后的内存优化,这些只是开发的套路,里面还有一些接口没实现,也存在内存优化的空间。
5、补充一点
这个自定义轮播图,刚进入界面,是无法左滑切换的,我们修复一下:
BannerViewPager.setAdapter()
public void setAdapter(BannerAdapter adapter) {
setCurrentItem(Integer.MAX_VALUE >> 2 * mAdapter.getCount());
}
如何获取代码?
Git clone https://github.com/droid4j/anKataLite.git
本篇对应的标签v0.6
git checkout v0.6