ViewPager的封装三:内存优化

本文详细探讨了ViewPager的内存优化策略,包括Handler的正确销毁以防止内存泄漏,通过convertView实现界面复用减少对象创建,以及考虑Activity生命周期以控制轮播时机。此外,还提到了在特定情况下自定义轮播图无法左滑切换的问题及其解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值