针对ViewPager2一种通用的性能优化手段

在某些场景下ViewPager2性能低下

性能低下场景:

ViewPager2里面每个页面渲染耗时严重,就会导致用户体验差。假设每个页面渲染需要耗费2s,又加上ViewPager2的复用机制,导致耗时时间会变为:2 * 3 = 6s。这就导致了页面初始化久,用户滑动卡顿

提出解决方案

造成上述问题本质

一般造成上述场景卡顿的基本原因在于:Adapter.onBindViewHolder()方法执行过于耗时。比如:设置大量的图片,将复杂数据,绑定在复杂的视图上。

解决问题的核心原理

拆分Adapter.onBindViewHolder()执行,将方法执行时间2s拆分为几个方法,严格控制每个方法的执行时间。

具体解决方案

方案一:ViewPager2 + Fragment

Fragment.onViewCreate()方法执行一部分onBindViewHolder()方法,在Fragment.OnResume()方法中又执行一个部分onBindViewHolder()方法。结合了Fragment的生命周期,完美将OnBindViewHolder()方法的时间拆分为两段,并且OnResume()方法只有在用户滑动到此页面时才会调用。

方案二:ViewPager2 + View

View的生命周期并不像Fragment那样存在onCreate与onResume。所以并不能做到像Fragment那样拆分OnBindViewHolder方法。在参考了FragmentStateAdapter源码之后,鄙人实现了让ViewHolder拥有Fragment的生命周期。ViewHolder拥有三个生命周期方法:

  1. onAttach - 当前页面被ViewPager2加载,但此时不可见
  2. onDettach - 当前页面被ViewPager2移除
  3. onResume - 当前页面被ViewPager2加载,此时可见

结合ViewHolder的生命周期,我们可以做类似优化。

  1. 在OAttach方法中,执行一部分onBindViewHolder()方法
  2. 在OnResume方法中,执行剩下部分的onBindViewHolder()方法
  3. 在onDettach方法中,回收资源

方案实现源码

方案一:

因官方已给出解决方案,各位看官可自行查找FragmentStateAdapter在ViewPager2上使用。可以自行谷歌或者百度找示例,就不再贴使用示例。

方案二:谷歌官方并未提供此解决方案,故贴出鄙人方案源码.

框架源码:

java 复制代码
package com.midea.light.device.adapter;

import android.annotation.SuppressLint;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.SimpleArrayMap;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;

abstract class _DataSetChangeObserver extends RecyclerView.AdapterDataObserver {
    @Override
    public abstract void onChanged();

    @Override
    public final void onItemRangeChanged(int positionStart, int itemCount) {
        onChanged();
    }

    @Override
    public final void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
        onChanged();
    }

    @Override
    public final void onItemRangeInserted(int positionStart, int itemCount) {
        onChanged();
    }

    @Override
    public final void onItemRangeRemoved(int positionStart, int itemCount) {
        onChanged();
    }

    @Override
    public final void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        onChanged();
    }
}

abstract public class ViewPager2ViewStateAdapter<T extends ViewPager2ViewStateAdapter.ViewHolder> extends RecyclerView.Adapter<T> {
    ViewPager2 viewPager2;

    // 记录各位置的ViewHolder
    final SimpleArrayMap<Integer, T> viewHolders = new SimpleArrayMap<>();

    public ViewPager2ViewStateAdapter(ViewPager2 viewPager2) {
        this.viewPager2 = viewPager2;
        // 设置ViewPager2的滑动监听
        this.viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {

            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
                // 滑动触发时,尝试更改ViewHolder状态为Resume
                updateViewHolderLifecycle();
            }

            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                // 滑动触发时,尝试更改ViewHolder状态为Resume
                updateViewHolderLifecycle();
            }
        });
        // 设置ViewPager2数据源变化监听
        registerAdapterDataObserver(new _DataSetChangeObserver() {
            @Override
            public void onChanged() {
                // 数据集更改时,尝试更改ViewHolder状态为Resume
                updateViewHolderLifecycle();
            }
        });
    }

    /**
     * 尝试更新当前ViewHolder的Resume状态
     */
    void updateViewHolderLifecycle() {
        if (this.viewPager2.getScrollState() != ViewPager2.SCROLL_STATE_IDLE) {
            return;
        }


        final int currentItem = this.viewPager2.getCurrentItem();
        T currentViewHolder = this.viewHolders.get(currentItem);
        if(currentViewHolder != null) {
            holderResume(currentViewHolder, currentItem);
        }
    }

    /**
     * 更新当前ViewHolder的状态为Attach
     * @param viewHolder
     * @param position
     */
    void holderAttach(T viewHolder, int position) {
        viewHolder.state = 1;
        viewHolder.holderAttach();
        if(viewPager2.getCurrentItem() == position) {
            holderResume(viewHolder, position);
        }
    }

    /**
     * 更新当前ViewHolder的状态为Resume
     * @param viewHolder
     * @param position
     */
    void holderResume(T viewHolder, int position) {
        if (viewHolder.state == 2) {
            return;
        }
        if (viewHolder.state != 1) {
            holderAttach(viewHolder, position);
        }
        viewHolder.state = 2;
        viewHolder.holderResume();
    }

    /**
     * 更新当前ViewHolder的状态为Detached
     * @param viewHolder
     * @param position
     */
    void holderDetached(T viewHolder, int position) {
        viewHolder.state = 3;
        viewHolder.holderDetached();
    }


    @Override
    public void onViewAttachedToWindow(@NonNull T holder) {
        super.onViewAttachedToWindow(holder);
        // ViewPager2挂载ViewHolder时,会自动调用此方法,此时ViewHolder不可见
        holderAttach(holder, holder.position);
    }

    @Override
    public void onViewDetachedFromWindow(@NonNull T holder) {
        super.onViewDetachedFromWindow(holder);
        // ViewPager2卸载ViewHolder时,会自动调用此方法
        holderDetached(holder, holder.position);
    }

    @Override
    public void onBindViewHolder(@NonNull T holder, @SuppressLint("RecyclerView") int position) {
        holder.position = position;
        viewHolders.put(position, holder);
    }

    /**
     * 拥有生命周期的ViewHolder
     * state含义:
     *  1 ->  Attach    -> holderAttach()
     *  2 ->  Resume    -> holderResume()
     *  3 ->  Detached  -> holderDetached()
     */
    public abstract static class ViewHolder extends RecyclerView.ViewHolder {
        int position;
        // 1 show 2 resume 3 hide
        int state = -1;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
        }

        public abstract void holderAttach();

        public abstract void holderDetached();

        public abstract void holderResume();

    }

}

简单使用示例:

java 复制代码
private class ViewPagerAdapter extends ViewPager2ViewStateAdapter<ViewPagerAdapter.Holder> {
    List<MSmartLiveData<TabRoomBean>> rooms;

    public ViewPagerAdapter(ViewPager2 viewPager2) {
        super(viewPager2);
    }

    public void setData(List<MSmartLiveData<TabRoomBean>> rooms) {
        this.rooms = rooms;
        notifyDataSetChanged();
    }

    public List<MSmartLiveData<TabRoomBean>> getData() {
        return this.rooms;
    }

    @NonNull
    @Override
    public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new Holder(DeItemFamilyCardBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull Holder holder, int position) {
        super.onBindViewHolder(holder, position);
        MSmartLiveData<TabRoomBean> liveData = rooms.get(position);
        //1. 给ViewHolder注入数据源
        holder.initData(liveData);
    }

    @Override
    public int getItemCount() {
        return rooms == null ? 0 : rooms.size();
    }

    class Holder extends ViewPager2ViewStateAdapter.ViewHolder {
        final DeItemFamilyCardBinding binding;
        List<DeSubitemFamilyCardBinding> subItem;
        MSmartLiveData<TabRoomBean> mRoom;

        public Holder(@NonNull DeItemFamilyCardBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
            subItem = new ArrayList<>();
            subItem.add(this.binding.sub1);
            subItem.add(this.binding.sub2);
            subItem.add(this.binding.sub3);
            subItem.add(this.binding.sub4);
        }

        public DeItemFamilyCardBinding getBinding() {
            return binding;
        }

        public void initData(MSmartLiveData<TabRoomBean> room) {
            mRoom = room;
        }

        final void initImageLogo(ImageView imageView, boolean isPower, DeviceCardUIEntity entity) {
            Glide.with(getContext())
                    .load((isPower ? entity.getOnLogo() : entity.getOffLogo()))
                    .override(40, 40)
                    .into(imageView);
        }

        private void _initData(TabRoomBean entity) {
            List<MSmartLiveData<DeviceCardUIEntity>> deviceInfoEntities = new ArrayList<>();
            Collection<MSmartLiveData<DeviceCardUIEntity>> _devices = loader.getDevicesInShow(entity.getRoomId());
            if (CollectionUtil.isNotEmpty(_devices)) {
                deviceInfoEntities.addAll(_devices);
                final String familyId = familyLiveData.getOriginData().getFamilyId();
                final String roomId = entity.getRoomId();
                String strOrder = DeviceRepository.getInstance().getDeviceOrder(familyId, roomId);
                if (!TextUtils.isEmpty(strOrder)) {
                    List<String> list = Arrays.asList(strOrder.split(","));
                    DeviceOrderUtils.sort(list, deviceInfoEntities);
                }
            }

            int count = deviceInfoEntities.size();

            binding.tvEmpty.setVisibility(count == 0 ? View.VISIBLE : View.INVISIBLE);

            for (int i = 0; i < subItem.size(); i++) {
                subItem.get(i).getRoot().setVisibility(count > i ? View.VISIBLE : View.INVISIBLE);
                if (subItem.get(i).getRoot().getVisibility() == View.VISIBLE) {
                    initSubItem(deviceInfoEntities.get(i), subItem.get(i));
                }
            }
        }

        private void initSubItem(MSmartLiveData<DeviceCardUIEntity> device, DeSubitemFamilyCardBinding binding) {
                DeviceCardUIEntity entity = device.getValue();
                binding.tvDeviceName.setText(entity.getDeviceName());
                initImageLogo(binding.iconDevice, entity.isPower(), entity);
                binding.getRoot().setSelected(entity.isPower());
        }

        @Override
        public void holderAttach() {
            // 2. 执行部分视图渲染代码
            binding.tvRoomName.setText(mRoom.getOriginData().getName());
            binding.imageDetail.setOnClickListener(this);
        }

        @Override
        public void holderDetached() {
            // 4. 此处可执行相关资源回收代码
        }

        @Override
        public void holderResume() {
            // 3. 执行剩下部分的渲染视图代码
            initData(mRoom);
            binding.imageBackground.setBackgroundResource(parseRoomImage(mRoom.getOriginData().getIcon()));
        }
    }
}

void main() {
    viewPager2 = new ViewPager2(getContext());
    viewPager2.setAdapter(viewpagerAdapter = new ViewPagerAdapter(viewPager2));
}
相关推荐
坐吃山猪3 小时前
SpringBoot01-配置文件
java·开发语言
我叫汪枫3 小时前
《Java餐厅的待客之道:BIO, NIO, AIO三种服务模式的进化》
java·开发语言·nio
yaoxtao3 小时前
java.nio.file.InvalidPathException异常
java·linux·ubuntu
Swift社区5 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT6 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy6 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss7 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续7 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0447 小时前
ReAct模式解读
java·ai