发布网友 发布时间:2024-10-04 08:44
共1个回答
热心网友 时间:2024-10-21 12:45
如何实现图片懒加载?懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的src属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出真实路径赋值给图片的src属性,以此来实现图片的延迟加载。
kotlin—lazy及其原理lazy是属性委托的一种,是有kotlin标准库实现。它是属性懒加载的一种实现方式,在对属性使用时才对属性进行初始化,并且支持对属性初始化的操作时进行加锁,使属性的初始化在多线程环境下线程安全。lazy默认是线程安全的。
lazy既然是属性委托的一种,那么其语法也遵循属性委托的语法:
对应的lazy的语法为:
由于lazy为函数,其最后一个参数是函数,那么可以使用lamda的语法代替最后一个函数参数:
通过2中的语法,我们知道lazy是kotlin标准库中的重载函数,我们先从标准库lazy的函数的分析其原理:
lazy函数默认情况下是同步安全锁模式,其可以指定线程同步模式、线程公有模式、非线程安全模式,也在同步模式时指定使用的锁对象,lazy函数会创建懒加载类的实现类,通过懒加载类的实现类实现不同模式的懒加载。
我们依次分析SynchronizedLazyImpl、SafePublicationLazyImpl、UnsafeLazyImpl这三种模式是怎么实现懒加载的:
SynchronizedLazyImpl是同步模式的懒加载,它是lazy的默认实现,其在多线程环境下进行初始化是线程安全的,我们看看其源码实现:
同步模式的懒加载SynchronizedLazyImpl的实现原理其实是使用两个属性,一个是公有属性value—对外代表属性的值,一个是私有属性_value——是真正的值。value的get内部对_value进行初始化,如果_value已初始化则直接返回,如果没有初始化过则加锁并调用初始化函数把返回值赋值给_value。
SafePublicationLazyImpl是多线程环境下的公共线程安全模式,我们从其源码分析其原理:
公共线程安全模式SafePublicationLazyImpl与同步模式SynchronizedLazyImpl的区别在于,SafePublicationLazyImpl使用自旋锁进行初始化操作,而SynchronizedLazyImpl是要同步锁的方式进行初始化操作,其他与SynchronizedLazyImpl的实现一样。
SafePublicationLazyImpl是非线程安全的懒加载实现模式,在单线程下进行初始化是没啥问题,但是多线程下是进行初始化是不安全的,我们从其源码分析其原理:
SafePublicationLazyImpl与前面的两种模式的实现方式不一样就是初始化时没有加任何锁,其它是一样的。
上面分析了使用lazy函数之后返回了不同的懒加载实现类及各懒加载实现类的原理,所以lazy的语句最终语句的是:
valpropertyNameby[SynchronizedLazyImpl|SafePublicationLazyImpl|UnsafeLazyImpl]
但具体是怎么个懒加载实现类的value的get方法呢?——下面我们举例,然后通过编译后的字节码分析器实现原理:
举例:
编译后的字节码文件:
通过对生成的字节码的分析,lazy的原理:
注意lazy实现了懒加载,达到在使用时才进行初始化的目的,但是也为此增加了一个懒加载类,如果一个类的初始化操作不耗时却使用lazy进行懒加载是不明智的,lazy的适合场景是类的初始化操作比较耗时占资源。
Fragment数据懒加载及原理
最近据后台同事反馈说,某些接口调用的频率有点高,而这块业务还没完全开放,照理说很少会用到,于是让我查查怎么回事。
我看了下日志,把网络请求日志过滤出来,发现的确有问题,每次打开首页后都有许多那块业务相关的网络请求。于是马上联想到可能是因为首页改版之后嵌套使用了ViewPager,业务未完全开放的那个fragment里嵌套了一个ViewPager,里面有多个fragment,这样每次打开首页都会去加载该page,然后是一连串的fragment初始化以及网络请求,所以为了解决该问题就不得不使用懒加载。
最终想要实现的效果是:1)当fragment不可见的时候不加载数据;2)当数据已经加载过之后,除非手动刷新否则不重新请求数据。
首先,默认情况下,由于ViewPager会预加载左右两边相邻的至少1个fragment,通过setOffscreenPageLimit()设置预加载page数为0并不会起作用,这点从ViewPager的源码中可以看到:
从以上源码可以看出相邻fragment的加载是必然的,但是我们如果可以得知fragment可见性,那么就可以在fragment可见时才去加载数据。这样虽然不是完全的懒加载,只是数据懒加载,但是同样也可以满足我们的需求了。
那么fragment中有没有可以获取当前fragment是否可见的方法呢,当然是有的,它就是setUserVisibleHint(booleanisVisibleToUser)。
无论你使用的是FragmentPagerAdapter还是FragmentStatePagerAdapter,当它们初始化fragment的时候,该方法都会被调用两次。
一次是在实例化的时候,也就是在instantioateItem()方法中:
一次是在用户滑动到当前fragment的时候,在setPrimaryItem()方法中:
另外,当用户从当前fragment滑出的时候,setPrimaryItem()方法也会被调用。
来看下setUserVisibleHint()的注释:
系统正是通过该方法来判断当前fragment的UI是否对用户可见,而该方法被暴露出来的主要目的也是让我们可以提醒系统当前fragment已经不可见了,是时候重新更新fragment的生命周期了。
不过如果只是实现数据懒加载,我们不需要直接去调用该方法,只要覆写它并实现控制数据加载的逻辑就可以了。
这里我参考了一种比较简便的做法,原文来自尹star的ViewPager+FragmentLazyLoad最优解。
实现效果: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。
热心网友 时间:2024-10-21 12:43
如何实现图片懒加载?懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的src属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出真实路径赋值给图片的src属性,以此来实现图片的延迟加载。
kotlin—lazy及其原理lazy是属性委托的一种,是有kotlin标准库实现。它是属性懒加载的一种实现方式,在对属性使用时才对属性进行初始化,并且支持对属性初始化的操作时进行加锁,使属性的初始化在多线程环境下线程安全。lazy默认是线程安全的。
lazy既然是属性委托的一种,那么其语法也遵循属性委托的语法:
对应的lazy的语法为:
由于lazy为函数,其最后一个参数是函数,那么可以使用lamda的语法代替最后一个函数参数:
通过2中的语法,我们知道lazy是kotlin标准库中的重载函数,我们先从标准库lazy的函数的分析其原理:
lazy函数默认情况下是同步安全锁模式,其可以指定线程同步模式、线程公有模式、非线程安全模式,也在同步模式时指定使用的锁对象,lazy函数会创建懒加载类的实现类,通过懒加载类的实现类实现不同模式的懒加载。
我们依次分析SynchronizedLazyImpl、SafePublicationLazyImpl、UnsafeLazyImpl这三种模式是怎么实现懒加载的:
SynchronizedLazyImpl是同步模式的懒加载,它是lazy的默认实现,其在多线程环境下进行初始化是线程安全的,我们看看其源码实现:
同步模式的懒加载SynchronizedLazyImpl的实现原理其实是使用两个属性,一个是公有属性value—对外代表属性的值,一个是私有属性_value——是真正的值。value的get内部对_value进行初始化,如果_value已初始化则直接返回,如果没有初始化过则加锁并调用初始化函数把返回值赋值给_value。
SafePublicationLazyImpl是多线程环境下的公共线程安全模式,我们从其源码分析其原理:
公共线程安全模式SafePublicationLazyImpl与同步模式SynchronizedLazyImpl的区别在于,SafePublicationLazyImpl使用自旋锁进行初始化操作,而SynchronizedLazyImpl是要同步锁的方式进行初始化操作,其他与SynchronizedLazyImpl的实现一样。
SafePublicationLazyImpl是非线程安全的懒加载实现模式,在单线程下进行初始化是没啥问题,但是多线程下是进行初始化是不安全的,我们从其源码分析其原理:
SafePublicationLazyImpl与前面的两种模式的实现方式不一样就是初始化时没有加任何锁,其它是一样的。
上面分析了使用lazy函数之后返回了不同的懒加载实现类及各懒加载实现类的原理,所以lazy的语句最终语句的是:
valpropertyNameby[SynchronizedLazyImpl|SafePublicationLazyImpl|UnsafeLazyImpl]
但具体是怎么个懒加载实现类的value的get方法呢?——下面我们举例,然后通过编译后的字节码分析器实现原理:
举例:
编译后的字节码文件:
通过对生成的字节码的分析,lazy的原理:
注意lazy实现了懒加载,达到在使用时才进行初始化的目的,但是也为此增加了一个懒加载类,如果一个类的初始化操作不耗时却使用lazy进行懒加载是不明智的,lazy的适合场景是类的初始化操作比较耗时占资源。
Fragment数据懒加载及原理
最近据后台同事反馈说,某些接口调用的频率有点高,而这块业务还没完全开放,照理说很少会用到,于是让我查查怎么回事。
我看了下日志,把网络请求日志过滤出来,发现的确有问题,每次打开首页后都有许多那块业务相关的网络请求。于是马上联想到可能是因为首页改版之后嵌套使用了ViewPager,业务未完全开放的那个fragment里嵌套了一个ViewPager,里面有多个fragment,这样每次打开首页都会去加载该page,然后是一连串的fragment初始化以及网络请求,所以为了解决该问题就不得不使用懒加载。
最终想要实现的效果是:1)当fragment不可见的时候不加载数据;2)当数据已经加载过之后,除非手动刷新否则不重新请求数据。
首先,默认情况下,由于ViewPager会预加载左右两边相邻的至少1个fragment,通过setOffscreenPageLimit()设置预加载page数为0并不会起作用,这点从ViewPager的源码中可以看到:
从以上源码可以看出相邻fragment的加载是必然的,但是我们如果可以得知fragment可见性,那么就可以在fragment可见时才去加载数据。这样虽然不是完全的懒加载,只是数据懒加载,但是同样也可以满足我们的需求了。
那么fragment中有没有可以获取当前fragment是否可见的方法呢,当然是有的,它就是setUserVisibleHint(booleanisVisibleToUser)。
无论你使用的是FragmentPagerAdapter还是FragmentStatePagerAdapter,当它们初始化fragment的时候,该方法都会被调用两次。
一次是在实例化的时候,也就是在instantioateItem()方法中:
一次是在用户滑动到当前fragment的时候,在setPrimaryItem()方法中:
另外,当用户从当前fragment滑出的时候,setPrimaryItem()方法也会被调用。
来看下setUserVisibleHint()的注释:
系统正是通过该方法来判断当前fragment的UI是否对用户可见,而该方法被暴露出来的主要目的也是让我们可以提醒系统当前fragment已经不可见了,是时候重新更新fragment的生命周期了。
不过如果只是实现数据懒加载,我们不需要直接去调用该方法,只要覆写它并实现控制数据加载的逻辑就可以了。
这里我参考了一种比较简便的做法,原文来自尹star的ViewPager+FragmentLazyLoad最优解。
实现效果: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。