Fragment 数据懒加载及原理

Fragment 数据懒加载及原理,第1张

最近据后台同事反馈说,某些接口调用的频率有点高,而这块业务还没完全开放,照理说很少会用到,于是让我查查怎么回事。

我看了下日志,把网络请求日志过滤出来,发现的确有问题,每次打开首页后都有许多那块业务相关的网络请求。于是马上联想到可能是因为首页改版之后嵌套使用了 ViewPager,业务未完全开放的那个 fragment 里嵌套了一个 ViewPager,里面有多个 fragment,这样每次打开首页都会去加载该 page,然后是一连串的 fragment 初始化以及网络请求,所以为了解决该问题就不得不使用懒加载。

最终想要实现的效果是:1) 当 fragment 不可见的时候不加载数据;2) 当数据已经加载过之后,除非手动刷新否则不重新请求数据。

首先,默认情况下,由于 ViewPager 会预加载左右两边相邻的 至少 1 个 fragment,通过 setOffscreenPageLimit() 设置预加载 page 数为 0 并不会起作用,这点从 ViewPager 的源码中可以看到:

从以上源码可以看出相邻 fragment 的加载是必然的,但是我们如果可以得知 fragment 可见性,那么就可以在 fragment 可见时才去加载数据。这样虽然不是完全的懒加载,只是数据懒加载,但是同样也可以满足我们的需求了。

那么 fragment 中有没有可以获取当前 fragment 是否可见的方法呢,当然是有的,它就是 setUserVisibleHint(boolean isVisibleToUser)

无论你使用的是 FragmentPagerAdapter 还是 FragmentStatePagerAdapter,当它们初始化 fragment 的时候,该方法都会被调用两次。

一次是在实例化的时候,也就是在 instantioateItem() 方法中:

一次是在用户滑动到当前 fragment 的时候,在 setPrimaryItem() 方法中:

另外,当用户从当前 fragment 滑出的时候,setPrimaryItem() 方法也会被调用。

来看下 setUserVisibleHint() 的注释:

系统正是通过该方法来判断当前 fragment 的 UI 是否对用户可见,而该方法被暴露出来的主要目的也是让我们可以提醒系统当前 fragment 已经不可见了,是时候重新更新 fragment 的生命周期了。

不过如果只是实现数据懒加载,我们不需要直接去调用该方法,只要覆写它并实现控制数据加载的逻辑就可以了。

这里我参考了一种比较简便的做法,原文来自 尹star 的 ViewPager+Fragment LazyLoad 最优解 。

实现效果: lazy_load_fragment_demo

项目地址: aJIEw/DemoUI-LazyLoadFragment

可以看到只有第一次进入 fragment 的时候才会加载数据,而且也不会主动加载相邻的 fragment 或者已经加载过的数据了。

首先,由于 setUserVisibleHint() 会在 fragment 实例化时就先被调用 (在 onAttach() 之前),所以我们最好在 view 创建完毕之后加载数据,因此需要设置一个 view 是否初始化完毕的标志位。另外,当然也需要一个 view 是否可见的标志位,只有等到 view 可见才允许加载。然后还可以选择保存数据的初始化状态,这样可以控制在 fragment 生命周期中的合适时机重新加载数据。所以,我们需要以下 3 个标志位:

然后接下来分为两种情况,一种是 view 初始化完毕但是此时还不可见的情况。很显然,我们只要判断 setUserVisibleHint() 中参数的值就可以了:

还有一种情况是,如果当前 fragment 是整个 ViewPager 的第一个 fragment,那么 setUserVisibleHint(true) 会在 view 初始化之前就在 setPrimaryItem() 中被调用,此时 view 已经可见了,但是我们要等到 view 初始化才加载数据,所以我们要在某个地方判断 view 是否已经初始化并且去加载数据。

最好的地方是在 onActivityCreated() 中。根据 fragment 生命周期我们知道,onActivityCreated() 会在 onCreateView() 之后调用,此时 view 已经初始化完毕,我们可以在这里将 isViewInitiated 标记为 true,同时在这里为第一个显示的 fragment 加载数据:

最后,我们还需要判断下数据是否已经加载过,避免重复加载。

我们将以上所有判断逻辑写在 prepareFetchData() 中,判断条件为 view 已经初始化、可见且数据未加载:

最后再定义一个抽象方法 fetchData(),让子类去实现:

这样一个完整的数据懒加载就实现完毕了。

我们可以看下以上 *** 作的日志来验证下我们的想法。

第一次打开,FirstFragment 作为第一个可见的 fragment 立马被初始化:

此时 isVisibleToUser 会在 isViewInitiated 之前设为 true,所以 FirstFragment 会在 onActivityCreated() 中真正开始获取数据。

另外,由于预加载的存在,SecondFragment 也会被创建,但是此时还不可见:

当滑动到 SecondFragment 的时候,SecondFragment 状态变为可见,setUserVisibleHint(true) 被调用,所以开始获取数据:

而此时 FirstFragment 由可见变为不可见:

ThirdFragment 则开始第一次被创建,同样此时并不可见:

当滑动到 ThirdFragment 的时候,状态变为可见,所以也就开始获取数据:

此时 SecondFragment 由可见变为不可见:

而 FirstFragment 由于超出了 ViewPager 可以保存的 Fragment 的数量,所以被销毁:

此时 SecondFragment 重新变得可见:

而 FirstFragment 也开始重新被创建:

此时 FirstFragment 重新变得可见,虽然 FirstFragment 之前被销毁了,但是由于之前获取的数据会被恢复,所以现在不会重新去获取数据:

当然我们也可以选择在 onDestroy() 中将 isDataInitiated 置为 false,这样每次 fragment 重新创建都会重新获取数据。当然前提是你使用的是 FragmentStatePagerAdapter ,因为如果使用 FragmentPagerAdapter ,不会每次都调用 onDestroy(),fragment 实例会被保存。而 SecondFragment 再次变得不可见,ThirdFragment 被销毁,过程与 3 中移动到 ThirdFragment 类似,这里就不截图了。

通过以上日志,验证了我们的想法是对的。

另外,如果是 ViewPager 嵌套 ViewPager 其实效果也是一样的,如果不做特殊处理,相邻的 fragment 的会被加载,导致该 fragment 中的 ViewPager 会去加载其中的 fragment。

1、通过intent将值传给即将跳转的activity

或者通过bundle

2、通过startActivityForResult的跳转方式将值传给跳转前的activity

在下面回调中获取传过来的值

在fragment1中通过getSupportFragmentManager()findFragmentByTag()获取fragment2的对象,调用fragment2中的方法将值传过去

Fragment1

Activity中创建fragment,并给fragment指定tag

Fragment2

1、通过setArguments传递bundle,通过getArguments获取bundle

activity

2、通过fragment中的attach()生命周期,将context转为Mainactivity,然后调用MainActivity中的方法

通过在fragment中实现接口的方式,Fragment向Activity传值的步骤 接口回调传递(5部曲)

1fragment中准备回调接口 接口中声明传值的回调方法

2在fragment中定义属性private MyListener myListener

3重写fragment中的onAttach()方法:listener = (MyLisener)getActivity();

4fragment触发事件时回传值

5Activity中实现回调接口 重写回调方法获取回传的值并显示

在MainActivity中:

(待完成)

public void intialiseViewPager()

{

List<Fragment> fragments = new Vector<Fragment>();

numberOfTabs = applicationcurrentReportgetODTabsList()size();

for (int i = 0; i < numberOfTabs; i++)

{

ODTab tempTab = applicationcurrentReportgetODTabsList()get(i);

if (tempTabgetTabType()equals(ODGridXML_GRID_ELEMENT))

{

GridFragment gridFragment = GridFragmentnewInstance(tempTabgetTabId());

fragmentsadd(gridFragment);

}

else if (tempTabgetTabType()equals(ODChartXML_CHART_ELEMENT))

{

NewChartFragment chartFragment = NewChartFragmentnewInstance(tempTabgetTabId());

fragmentsadd(chartFragment);

}

}

Logd(TAG, "Current report fragments set to adapter: "+fragmentstoString());

mPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), fragments);

mViewPager = (ViewPager)findViewById(Ridpager);

mViewPagersetAdapter(mPagerAdapter);

mViewPagersetOffscreenPageLimit(0);

mViewPagersetOnPageChangeListener(this);

}

你可以看到我将字符串传递到片段 ( tempTabgetTabId() ),在这一行:

GridFragment gridFragment = GridFragmentnewInstance(tempTabgetTabId());

当片段中它自已我这样做对其进行初始化:

public static final GridFragment newInstance(String tabId)

{

GridFragment f = new GridFragment();

Bundle bdl = new Bundle(2);

bdlputString(TAB_ID, tabId);

fsetArguments(bdl);

return f;

}

@Override

public void onCreate(Bundle savedInstanceState)

{

String tabId = getArguments()getString(TAB_ID);

if (applicationcurrentReport != null)

{

thisodTab = applicationcurrentReportgetODTabByTabId(tabId);

}

else

{

startActivity(new Intent(getActivity(), LoginScrActivityclass));

}

superonCreate(savedInstanceState);

}

所有这一切被执行,不会覆盖默认空的构造函数的片段,按照我的理解这非常不建议。

现在我需要获取实例的 odTab 对象放进这行中的当前可见片段:

thisodTab = applicationcurrentReportgetODTabByTabId(tabId);

可能有些人请给我解释如何做到这吗?所以我可以把它的 odTab 对象,如何获取当前可见片段实例

更新:这里提出的意见与我加入了 odTab 到调用的应用程序类的对象实例 currentVisibleTab 和我设置 odTab 这样的实例:

@Override

public void setUserVisibleHint(boolean isVisibleToUser)

{

Logd(TAG, "setUserVisibleHint invoked!");

supersetUserVisibleHint(isVisibleToUser);

if (isVisibleToUser)

{

if (odTab != null && getActivity() != null)

{

Logd(TAG, "Currently visable tab: "+odTabgetTabTitle());

applicationcurrentVisibleTab = odTab;

}

}

}

更新 2:我有一种 ViewPagerAdapter :

public class ViewPagerAdapter extends FragmentPagerAdapter

{

private List<Fragment> fragments;

/

@param fm

@param fragments

/

public ViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {

super(fm);

thisfragments = fragments;

}

/ (non-Javadoc)

@see androidsupportv4appFragmentPagerAdapter#getItem(int)

/

@Override

public Fragment getItem(int position) {

return thisfragmentsget(position);

}

/ (non-Javadoc)

@see androidsupportv4viewPagerAdapter#getCount()

/

@Override

public int getCount() {

return thisfragmentssize();

}

@Override

public int getItemPosition(Object object) {

return POSITION_NONE;

}

public void removeAllFragments()

{

thisfragmentsclear();

}

public void addFragmentsListToAdapter(List<Fragment> fragments)

{

thisfragmentsaddAll(fragments);

}

public List<Fragment> getFragments()

{

return fragments;

}

}

在它正如你看到的我有 List 的片段所示的 ViewPager ,此列表初始化像这样:

List<Fragment> fragments = new Vector<Fragment>();

我不明白的是如何获取对当前段落触发从该列表中的接口方法的引用。不相关的问题,但也许你知道答案,有什么区别之间List 和 Vector

我还查阅了此选项。

先谢谢了。

解决方法 1:

我找到的方法 visbile 片段当前正在使用 setUserVisibleHint 和回视图寻呼机的接口。此方法是在类中片段。重写它和使用回叫您查看传呼机的接口。如果该片段是可见-即它返回 true-只使用对您片段 (无论是从列表中备份您的适配器或一个您存储为一个实例变量) 的引用以获取您需要的任何从片段本身。我已经添加下面的代码。

声明有关的无论你想做一个接口。我的情况我用这来禁用 ViewPager 默认 x 方向侦听器时一个谷歌地图实例是可见的因此滚动地图没有触发片段的变化。

public interface OnMapFragmetnVisibleListener{

public void mapVisible(boolean visible);

}

在片段:

@Override

public void onAttach(Activity activity){

superonAttach(activity);

try{

mapFragmentVisibilityListener = (OnMapFragmentVisibleListener) activity;

isAttached = true; //flag for whether this framgnet is attache to pager

} catch (ClassCastException e){

throw new ClassCastException(activitytoString() + " must implement interface onAttach");

}

@Override

public void setUserVisibleHint(boolean isVisibleToUser){

if(isVisibleToUser && isAttached){ //if listner is called before fragment is attached will throw NPE

mapFragmentVisibilityListenermapVisible(true);

}

}

直到我试过这个是不明显的一件事是 isAttached 变量的加法。我也重写片段 onAttach 方法,并将其设置为 true,一旦它被称为。否则会发生什么是您片段将对用户是可见的和尝试调用的接口向您 ViewPager 接口初始化之前。

在我 ViewPager 我只是实现了 OnMapFragmentVisibleListener,然后添加所需的方法

@Override

public void mapVisible(boolean visible) {

if(visible)

viewPagerallowSwipe(false); //turn off viewPager intercept touch if map visible

}

在我的案子我用它来关闭 ViewPager 刷功能但是,我可以轻松地叫回来的公共方法中我 mapFragment。我碰巧有 3 片段我 ViewPager 我保持对的引用

更新

要查找当前的片段首先显示你需要找出哪个片段正在显示你可以通过多种方式。最简单的方法就是通过一些描述符返回到您ViewPager / FragmentActivity 与您之前创建的接口。

public interface OnFragmentVisibleListener{

public void fragmentVisible(boolean true, String tag);

}

那您设置的问题是您正在使用 Vector ,是真的同步 List 来存储您片段 rerefences。其结果是,没有通过,你可以找到你片段以后的密钥值。片段有确定的一个标签,创建在片段获利的能力,它们被添加到该片段活动期间。然而,在 ViewPager设置,您没有添加片段分别,所以无法添加标签在获利。

我的解决方案一直是回我 BasePagerAdapter 与 HashMap 在其中存储由一个唯一的密钥标识每个片段。

LinkedHashMap<String, Fragment> tabs = new LinkedHashMap<String, Fragment>();

tabsput("Frag1", fragment1);

tabsput("Frag2", fragment2);

tabsput("Frag3", fragment3);

然后用此适配器

private class BasePagerAdapter extends FragmentPagerAdapter{

private LinkedHashMap<String, Fragment> tabs;

public BasePagerAdapter(LinkedHashMap<String, Fragment> tabMap, FragmentManager fm)

{

super(fm);

thistabs = tabMap;

}

// other basic methods

因为您的引用存储到一个密钥值、 容易找到因为你只是可以通过从键值哈希映射和返回键搜索你要找的 framgment。

如果您不能更改设置要逃离 Vector 你需要到任一 (1) 跟踪的哪个片段你添加的顺序 (假设片段不添加和删除动态) 这样,你可以通过对每个片段 (2) 保持单独引用或索引

private Fragment x;

//in onCreate

x = new Fragment();

//in interface

if(tagequals("x"){

xdoSomething();

}

以上就是关于Fragment 数据懒加载及原理全部的内容,包括:Fragment 数据懒加载及原理、2、Activity 与 Fragment 之间常见的几种通信方式、如何获取ViewPager当前的Fragment等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9331591.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-27
下一篇 2023-04-27

发表评论

登录后才能评论

评论列表(0条)

保存