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(); }如上代码,每次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; }这里是自定义轮播组件,那么就需要考虑到它的轮播时机,假如已经切换到其他界面了,那么也就没必要继续轮播了。
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(); }用了四篇小文来记录BannerView的开发过程,从泛读ViewPager源码,到BannerView从里到外封装,添加自定义属性,最后的内存优化,这些只是开发的套路,里面还有一些接口没实现,也存在内存优化的空间。
这个自定义轮播图,刚进入界面,是无法左滑切换的,我们修复一下:
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