添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
帅气的毛衣  ·  WenCodes - Android ...·  2 周前    · 
冷静的勺子  ·  Androidx 下 Fragment ...·  2 周前    · 
任性的紫菜汤  ·  Fragment的setUserVisibl ...·  4 周前    · 
儒雅的枇杷  ·  Threejs is bad ...·  2 周前    · 
活泼的棒棒糖  ·  getting ...·  3 周前    · 
成熟的小狗  ·  PostgreSQL Python 教程: ...·  4 周前    · 
想发财的遥控器  ·  QQ音乐,又涨价了·  7 月前    · 

tabLayout + viewPager2 + fragment 用不好会导致内存泄漏

前言

tabLayout + viewPager2 + fragment 这是在app开发中非常常见的UI架构了 但是用不好的话 是会导致内存泄漏的

Demo地址

( app 模板使用 android studio 创建一个就行 我只用 HomeFragment )

先了解一下viewPager

之前我们都是使用viewPager 但是 viewPager 有两个毛病

  • 不能关闭预加载

  • 更新Adapter不生效

    虽然能解决 但是很麻烦

    viewPager 有个方法 offscreenPageLimit 用来设置 当前页前后预加载的数量 即使设置为0也不起任何作用 因为源码中默认值为1

    先来看个图:

    上面是 ViewPager 默认情况下的加载示意图,当切换到当前页面时,会默认预加载左右两侧的布局到 ViewPager 中,
    尽管两侧的 View 并不可见的,我们称这种情况叫预加载;由于 ViewPager offscreenPageLimit 设置了限制,
    页面的预加载是不可避免

    如果使用 一定要使用viewPager 也可以做到 fragment 懒加载 主要是对 fragment 做手脚 结合生命周期方法和setUserVisibleHint状态,控制数据延迟加载,而布局只能提前进入

    viewPager2基本使用

    1、 承载tabLayout 和 viewPager2 需要一个 fragment 或者 activity 这里我们使用 fragment

    HomeFragment 如下:

    public class HomeFragment extends Fragment { private HomeViewModel homeViewModel ; private TabLayout tabLayout ; private ViewPager2 viewPager2 ; private final ArrayList < String > tabTitles = new ArrayList < > ( ) ; // 用List 存放fragment 不推荐使用 用不好 导致内存泄漏 private ArrayList < BlankFragment > fragments = new ArrayList < > ( ) ; public View onCreateView ( @NonNull LayoutInflater inflater , ViewGroup container , Bundle savedInstanceState ) { homeViewModel = new ViewModelProvider ( this ) . get ( HomeViewModel . class ) ; View root = inflater . inflate ( R . layout . fragment_home , container , false ) ; tabLayout = root . findViewById ( R . id . tabs ) ; viewPager2 = root . findViewById ( R . id . viewPager ) ; return root ; @Override public void onViewCreated ( @NonNull View view , @Nullable Bundle savedInstanceState ) { super . onViewCreated ( view , savedInstanceState ) ; // 初始化 tabTitles 和 fragments for ( int i = 0 ; i < 15 ; i ++ ) { tabTitles . add ( String . valueOf ( i ) ) ; fragments . add ( BlankFragment . newInstance ( tabTitles . get ( i ) ) ) ; // 关闭预加载 viewPager2 . setOffscreenPageLimit ( ViewPager2 . OFFSCREEN_PAGE_LIMIT_DEFAULT ) ; // 可以不设置 因为默认是 -1 默认不进行预加载 // 这个必须设置 不然仍然会启用预加载 ( ( RecyclerView ) viewPager2 . getChildAt ( 0 ) ) . getLayoutManager ( ) . setItemPrefetchEnabled ( false ) ; // 设置缓存数量,对应 RecyclerView 中的 mCachedViews,即屏幕外的视图数量 ( ( RecyclerView ) viewPager2 . getChildAt ( 0 ) ) . setItemViewCacheSize ( 0 ) ; // 第一种方法 (可能会内存泄漏) //FragmentAdapter adapter = new FragmentAdapter(this, fragments); //viewPager2.setAdapter(adapter); // 这里使用第二种方法 viewPager2 . setAdapter ( new FragmentStateAdapter ( this ) { @NonNull @Override public Fragment createFragment ( int position ) { return BlankFragment . newInstance ( tabTitles . get ( position ) ) ; @Override public int getItemCount ( ) { return tabTitles . size ( ) ; } ) ; // viewPager2 滑动监听 viewPager2 . registerOnPageChangeCallback ( new ViewPager2 . OnPageChangeCallback ( ) { @Override public void onPageSelected ( int position ) { super . onPageSelected ( position ) ; } ) ; // 这里第四个参数一定要设置为false 如果设置为true时 我们在滑动时 BlankFragment的创建 和 销毁 都很正常 // 一旦 我们通过 点击tabLayout时 如果两个tab距离过远 那么所有划过的tabLayout 都会创建和销毁BlankFragment 这显然不是我们想要的 // tabLayout 和 viewPager 联动 new TabLayoutMediator ( tabLayout , viewPager2 , true , false , new TabLayoutMediator . TabConfigurationStrategy ( ) { @Override public void onConfigureTab ( @NonNull TabLayout . Tab tab , int position ) { tab . setText ( tabTitles . get ( position ) ) ; } ) . attach ( ) ;

    fragment_home.xml 布局文件:

    <?xml version="1.0" encoding="utf-8"?> < LinearLayout xmlns: android = " http://schemas.android.com/apk/res/android " xmlns: app = " http://schemas.android.com/apk/res-auto " xmlns: tools = " http://schemas.android.com/tools " android: layout_width = " match_parent " android: orientation = " vertical " android: layout_height = " match_parent " tools: context = " .ui.home.HomeFragment " > < com.google.android.material.tabs.TabLayout android: id = " @+id/tabs " android: layout_width = " match_parent " android: layout_height = " 40dp " app: tabGravity = " center " app: tabIndicatorColor = " #ff678f " app: tabIndicatorFullWidth = " false " app: tabIndicatorHeight = " 2dp " app: tabMode = " scrollable " android: layout_marginTop = " 0dp " app: tabSelectedTextColor = " #ff678f " app: tabTextColor = " #333333 " app: tabUnboundedRipple = " true " /> < androidx.viewpager2.widget.ViewPager2 android: id = " @+id/viewPager " android: layout_width = " match_parent " android: background = " @color/purple_200 " android: layout_height = " 0dp " android: layout_weight = " 1 " /> </ LinearLayout >

    2、viewPager2的 FragmentStateAdapter 创建fragment时 我们 复用一个fragment

    BlankFragment 如下:

    public class BlankFragment extends Fragment { private String mParam1 ; private TextView mTextView ; public static BlankFragment newInstance ( String param1 ) { BlankFragment fragment = new BlankFragment ( ) ; Bundle args = new Bundle ( ) ; args . putString ( "param1" , param1 ) ; fragment . setArguments ( args ) ; return fragment ; @Override public void onPause ( ) { super . onPause ( ) ; Log . i ( "TAG=======onPause" , mParam1 ) ; @Override public void onCreate ( Bundle savedInstanceState ) { super . onCreate ( savedInstanceState ) ; if ( getArguments ( ) != null ) { mParam1 = getArguments ( ) . getString ( "param1" ) ; Log . i ( "TAG=======onCreate" , mParam1 ) ; Toast . makeText ( getContext ( ) , mParam1 , Toast . LENGTH_SHORT ) . show ( ) ; @Override public void onDestroy ( ) { super . onDestroy ( ) ; Log . i ( "TAG=======onDestroy" , mParam1 ) ; @Override public View onCreateView ( LayoutInflater inflater , ViewGroup container , Bundle savedInstanceState ) { // Inflate the layout for this fragment View root = inflater . inflate ( R . layout . fragment_blank , container , false ) ; mTextView = root . findViewById ( R . id . text1 ) ; mTextView . setText ( mParam1 ) ; Log . i ( "TAG=======onCreateView" , mParam1 ) ; return root ; // 对外提供一个刷新Ui的方法 public void refreshUi ( ) { if ( getArguments ( ) != null ) { mParam1 = getArguments ( ) . getString ( "param1" ) ; mTextView . setText ( mParam1 + "ddd" ) ; @Override public void onDestroyView ( ) { super . onDestroyView ( ) ; Log . i ( "TAG======onDestroyView" , mParam1 ) ;

    fragment_blank.xml 如下:

    <?xml version="1.0" encoding="utf-8"?> < LinearLayout xmlns: android = " http://schemas.android.com/apk/res/android " xmlns: tools = " http://schemas.android.com/tools " android: layout_width = " match_parent " android: layout_height = " match_parent " android: orientation = " vertical " android: gravity = " center " tools: context = " .ui.BlankFragment " > < TextView android: id = " @+id/text1 " android: textSize = " 26sp " android: layout_width = " wrap_content " android: layout_height = " wrap_content " android: text = " black " /> </ LinearLayout >

    至此 我们已经完成了 tabLayout + viewPager2 + fragment

    viewPager2常用方法

  •