Android性能优化—ViewPagers + Fragment缓存优化

news2024/10/5 15:34:08

大家看标题,可能会有点儿懵,什么是ViewPagers,因为在很久之前,我们使用的都是ViewPager,但是现在更多的是在用ViewPager2,因此用ViewPagers(ViewPager、ViewPager2)来代替两者,主要介绍两者的区别。

ViewPagers嵌套Fragment架构,在我们常用的App中随处可见,抖音的首页、各大电商app首页(淘宝、京东、拼多多)等,通过左右滑动切换Tab;但因为ViewPager的预加载机制存在,我们先看下ViewPager的源码:

public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                + DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
        mOffscreenPageLimit = limit;
        populate();
    }
}

当我们设置offscreenPageLimit(离屏加载)的数值时,我们可以看到limit的值是有限制,不能小于DEFAULT_OFFSCREEN_PAGES

private static final int DEFAULT_OFFSCREEN_PAGES = 1;

那么就意味着ViewPager默认支持预加载,我们看下面这张图

在这里插入图片描述

如需完整版Android性能优化文件 请点击免费领取

如果红色区域默认为首页,根据ViewPager默认预加载的阈值,那么左右两边的页面同样也会被加载,如果有网络请求,也就是说,我们没有打开左边的页面,它已经默认进行了网络请求,这种体验是非常差的,因为会在暗地里消耗流量。

理想情况下,我们需要的是打开某个页面的时候才去加载,这里就需要通过懒加载的方式优化。

1 ViewPager懒加载优化

1.1 ViewPager的缓存机制

很多时候,我们在使用Fragment的时候,发现打开过的页面再回来,页面没有重建刷新,很多人觉得是Fragment是有缓存的,其实并不是Fragment有缓存,而是ViewPager具备缓存能力;

如果有小伙伴使用过单Activity + 多Fragment架构的时候就会发现,打开过的页面再次返回的时候,Fragment会被重建,所以两种架构都有利弊,关键看我们怎么选择,下面我们看下ViewPager的缓存机制。

public void setAdapter(@Nullable PagerAdapter adapter) {
    if (mAdapter != null) {
        ①
        mAdapter.setViewPagerObserver(null);
        mAdapter.startUpdate(this);
        for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
            mAdapter.destroyItem(this, ii.position, ii.object);
        }
        mAdapter.finishUpdate(this);
        mItems.clear();
        removeNonDecorViews();
        mCurItem = 0;
        scrollTo(0, 0);
    }final PagerAdapter oldAdapter = mAdapter;
    mAdapter = adapter;
    mExpectedAdapterCount = 0;if (mAdapter != null) {
        if (mObserver == null) {
            mObserver = new PagerObserver();
        }
        mAdapter.setViewPagerObserver(mObserver);
        mPopulatePending = false;
        final boolean wasFirstLayout = mFirstLayout;
        mFirstLayout = true;
        mExpectedAdapterCount = mAdapter.getCount();
        if (mRestoredCurItem >= 0) {
            mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
            setCurrentItemInternal(mRestoredCurItem, false, true);
            mRestoredCurItem = -1;
            mRestoredAdapterState = null;
            mRestoredClassLoader = null;
        } else if (!wasFirstLayout) {populate();
        } else {requestLayout();
        }
    }

    // Dispatch the change to any listeners
    if (mAdapterChangeListeners != null && !mAdapterChangeListeners.isEmpty()) {
        for (int i = 0, count = mAdapterChangeListeners.size(); i < count; i++) {
            mAdapterChangeListeners.get(i).onAdapterChanged(this, oldAdapter, adapter);
        }
    }
}

核心方法就是setAdapter,像RecyclerView一样,因为会有缓存,所以当页面滑动的时候,如果缓存中存在页面,那么就会从缓存中取,如果没有,就需要去创建新的页面,所以我们先来关注一下PagerAdapter

public abstract class PagerAdapter {
    private final DataSetObservable mObservable = new DataSetObservable();
    private DataSetObserver mViewPagerObserver;

    public static final int POSITION_UNCHANGED = -1;
    public static final int POSITION_NONE = -2;

    public abstract int getCount();
    //开始更新
    public void startUpdate(@NonNull ViewGroup container) {
        startUpdate((View) container);
    }
    //初始化页面
    @NonNull
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        return instantiateItem((View) container, position);
    }
    //销毁页面
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        destroyItem((View) container, position, object);
    }
    //结束刷新
    public void finishUpdate(@NonNull ViewGroup container) {
        finishUpdate((View) container);
    }
}

PagerAdapter是一个抽象类,那么这些方法肯定是具体实现类实现,如果我们在使用ViewPager嵌套Fragment的时候,使用的是FragmentPageAdapter

接着回到setAdapter方法中

①:有一个全局变量 mAdapter,如果是第一个加载进来,那么mAdapter是空的,走到②

②:这里就是将我们传入的adapter给mAdapter赋值

③:这个时候mAdapter不为空,这里需要关注几个参数:

wasFirstLayout = true
mRestoredCurItem = -1

所以这里直接走到⑤,调用requestLayout方法,会执行到onMeasure,在这个方法中,会执行populate方法(这个大家自己去爬楼)

populate干了什么呢?代码太多了就不贴出来了,直接上图

在这里插入图片描述

如果是默认缓存(mOffscreenPageLimit = 1),那么在mItems就会缓存3个Fragment

private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();

当页面滑动时,page2成为了当前页,那么ViewPager的populate做了什么操作呢?

(1)首先page3会被预加载,这个时候调用了PagerAdapter的instantiateItem方法新建页面,并放在mItems集合中,并且设置为不可见的状态(setUserVisibleHint(false)),所有缓存中不可见的页面同理
(2)page1就会从缓存中移除,调用了PagerAdapter的destroyItem方法,curPage会成为mItems中第一个缓存对象;
(3)将page2设置为当前展示的Fragment

因此populate干的主要工作就是,随着页面的滑动,将Page从缓存中移除销毁,或者将新页面新建加入缓存中。

1.2 ViewPager懒加载方案

如上所述,ViewPager默认就是开启预加载的,而且默认最多能够缓存3个Fragment页面,那么为了避免流量的消耗,需要我们针对预加载这种情况进行页面懒加载,只有当页面可见的时候,才能加载数据。

class MainLazyLoadAdapter(
    fragmentManager: FragmentManager,
    val fragments:MutableList<Fragment>
) : FragmentPagerAdapter(fragmentManager) {
    override fun getCount(): Int {
        return fragments.size
    }

    override fun getItem(position: Int): Fragment {
        return fragments[position]
    }
}
class LazyFragment(val index:Int) : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e("TAG","LazyFragment $index onCreate")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.e("TAG","LazyFragment $index onCreateView")
        return inflater.inflate(R.layout.fragment_lazy, container, false)
    }

}
val fragments = mutableListOf<Fragment>()
for (index in 0..5) {
    fragments.add(LazyFragment(index))
}

vp_lazy_load.adapter = MainLazyLoadAdapter(supportFragmentManager, fragments)

首先我们先看默认预加载状态,验证之前源码中的原理

//第一次进来
2022-08-28 13:41:15.759 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 13:41:15.760 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 13:41:15.783 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 13:41:15.784 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView

我们看到第一次进来,第二个Fragment被加载进来,然后右滑,第三个Fragment被加载

2022-08-28 13:41:15.759 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 13:41:15.760 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 13:41:15.783 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 13:41:15.784 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
2022-08-28 13:48:45.248 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreate
2022-08-28 13:48:45.250 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreateView

当我们滑到第4个Fragment的时候,左滑回到第3个Fragment,发现并没有重建是因为缓存的原因,因为滑到第4个Fragment的时候,第2个Fragment已经被销毁了,再回到第3个Fragment的时候,第2个Fragment被重建,走了onCreateView方法

2022-08-28 13:41:15.759 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 13:41:15.760 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 13:41:15.783 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 13:41:15.784 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
2022-08-28 13:48:45.248 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreate
2022-08-28 13:48:45.250 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreateView
2022-08-28 13:50:00.439 12677-12677/com.lay.image_process E/TAG: LazyFragment 3 onCreate
2022-08-28 13:50:00.440 12677-12677/com.lay.image_process E/TAG: LazyFragment 3 onCreateView
2022-08-28 13:50:01.344 12677-12677/com.lay.image_process E/TAG: LazyFragment 4 onCreate
2022-08-28 13:50:01.345 12677-12677/com.lay.image_process E/TAG: LazyFragment 4 onCreateView
2022-08-28 13:50:03.315 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView

首先我们先看下,Adapter重建Fragment的时候的核心代码

public Object instantiateItem(@NonNull ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        } else {
            //关键代码
            fragment.setUserVisibleHint(false);
        }
    }

    return fragment;
}

我们可以看到,当前Fragment如果被创建但是没有在当前页面展示的时候,调用了fragment.setUserVisibleHint(false),也就是说setUserVisibleHint能够监听当前Fragment是否可见

所以我们对Fragment进行改造

class LazyFragment(val index:Int) : Fragment() {

    //判断当前页面是否可见
    private var isShow = false
    //判断页面是否创建成功
    private var isViewCreated = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e("TAG","LazyFragment $index onCreate")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.e("TAG","LazyFragment $index onCreateView")
        return inflater.inflate(R.layout.fragment_lazy, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        isViewCreated = true
        lazyLoad()
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        Log.e("TAG","LazyFragment $index isVisibleToUser $isVisibleToUser")
        if(isVisibleToUser){
            isShow = true
            //才有资格去懒加载
            lazyLoad()
        }else{
            isShow = false
        }
    }

    private fun lazyLoad() {
        if(isViewCreated && isShow){
            Log.e("TAG","Fragment $index loadData")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        isViewCreated = false
        isShow = false
    }
}

如果按照之前的方式,当调用onViewCreated方法的时候,我们就会加载数据;做了懒加载处理之后,重写了setUserVisibleHint方法,当前页面可见的时候,才有资格去加载数据,这样即便创建了Fragment,但是如果不可见就不会加载数据

2022-08-28 14:06:29.776 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 isVisibleToUser false
2022-08-28 14:06:29.776 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 isVisibleToUser false
2022-08-28 14:06:29.776 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 isVisibleToUser true
2022-08-28 14:06:29.782 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 14:06:29.783 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 14:06:29.796 25904-25904/com.lay.image_process E/TAG: Fragment 0 loadData
2022-08-28 14:06:29.805 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 14:06:29.805 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
2022-08-28 14:06:59.395 25904-25904/com.lay.image_process E/TAG: LazyFragment 2 isVisibleToUser false
2022-08-28 14:06:59.396 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 isVisibleToUser false
2022-08-28 14:06:59.396 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 isVisibleToUser true
2022-08-28 14:06:59.396 25904-25904/com.lay.image_process E/TAG: Fragment 1 loadData
2022-08-28 14:06:59.399 25904-25904/com.lay.image_process E/TAG: LazyFragment 2 onCreate
2022-08-28 14:06:59.400 25904-25904/com.lay.image_process E/TAG: LazyFragment 2 onCreateView

通过日志我们可以看到,当首次进入的时候,虽然Fragment 1 被创建了,但是并没有加载数据。

这里有个问题,既然可见之后就能加载数据,那么我只在setUserVisibleHint的时候,判断是否可见来去加载数据?

其实是不可以的,通过日志我们能够发现,setUserVisibleHint是早于onCreate方法调用的,也就是说在页面还没有创建时,去加载数据有可能导致页面元素找不到发生空指针异常。

2 ViewPager2与ViewPager的区别

上一小节,我们介绍了ViewPager的加载机制和缓存机制,那么我们把整套页面搬过来,唯一发生变化的就是将ViewPager转换为ViewPager2

class MainLazyLoadAdapter2(
    activity: FragmentActivity,
    val fragments: MutableList<Fragment>
) : FragmentStateAdapter(activity) {
    override fun getItemCount(): Int {
        return fragments.size
    }

    override fun createFragment(position: Int): Fragment {
        return fragments[position]
    }

}

ViewPager2的适配器使用的是FragmentStateAdapter,因为FragmentStateAdapter继承了RecyclerView.Adapter,因此支持了横向滑动和竖向滑动

val fragments = mutableListOf<Fragment>()
for (index in 0..5) {
    fragments.add(LazyFragment(index))
}


vp_lazy_load = findViewById(R.id.vp_lazy_load)
vp_lazy_load.adapter = MainLazyLoadAdapter2(this, fragments)

用同样的方式设置了适配器,我们看下日志输出,就会发现,咦?怎么跟ViewPager不一样了

2022-08-28 14:47:11.790 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 14:47:11.792 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView

刚进来的时候,只有Fragment 1 加载了页面,并没有新建缓存页面,当我滑动到下一页的时候,也只有下一页的页面进行了重建

2022-08-28 14:47:11.790 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 14:47:11.792 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 14:47:13.948 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 14:47:13.948 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreateView

ViewPager2没有预加载机制吗?这里我们就需要看源码了,直接奔向setOffscreenPageLimit方法,我们看到跟ViewPager的setOffscreenPageLimit方法是不一样的

public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
    if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
        throw new IllegalArgumentException(
                "Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
    }
    mOffscreenPageLimit = limit;
    // Trigger layout so prefetch happens through getExtraLayoutSize()
    mRecyclerView.requestLayout();
}
public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1;

这里的判断条件 limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT,有一个数值能够通过,就是-1,这就意味着,ViewPager2默认是不支持预加载的

但是ViewPager2的缓存策略还是存在,因为继承了RecyclerView的Adapter,所以缓存复用机制是跟RecyclerView一致的,默认mViewCaches缓存池的大小是3

2022-08-28 15:30:00.579 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 15:30:00.579 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 15:30:03.883 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 15:30:03.884 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
2022-08-28 15:30:05.064 15514-15514/com.lay.image_process E/TAG: LazyFragment 2 onCreate
2022-08-28 15:30:05.064 15514-15514/com.lay.image_process E/TAG: LazyFragment 2 onCreateView
2022-08-28 15:30:08.997 15514-15514/com.lay.image_process E/TAG: LazyFragment 3 onCreate
2022-08-28 15:30:08.997 15514-15514/com.lay.image_process E/TAG: LazyFragment 3 onCreateView
2022-08-28 15:30:20.005 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 15:30:20.005 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView

当我们滑动到第4个Fragment的时候,注意这里跟ViewPager不一样的是,ViewPager的缓存是缓存当前页的左右两边,但是ViewPager2就是RecyclerView的缓存机制,顺序缓存;

当滑动到第4个Fragment的时候,因为缓存池大小为3,因此LazyFragment 0 就会从缓存池中移除,当再次滑动到LazyFragment 0的时候,就会重建!

所以当我们还在思考如何针对ViewPager的预加载机制做懒加载操作时,请将项目中的ViewPager迁移至ViewPager2

附录:

当你的项目中还在使用ViewPager时,建议使用当前这个懒加载框架

abstract class BaseLazyFragment<VM : ViewModel, VB : ViewBinding> : Fragment() {

    private lateinit var viewModel: VM
    private lateinit var binding: VB

    private var isShow = false
    private var isViewCreated = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = getViewModelInstance()
        binding = getLayoutInflate(layoutInflater)
    }


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initView()
        isViewCreated = true
        lazyLoad()
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        if (isVisibleToUser) {
            isShow = true
            lazyLoad()
        } else {
            isShow = false
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        isShow = false
        isViewCreated = false
    }


    private fun lazyLoad() {
        if (isShow && isViewCreated) {
            initData()
        }
    }

    open fun initData() {}

    open fun initView() {}

    abstract fun getViewModelInstance(): VM
    abstract fun getLayoutInflate(layoutInflater: LayoutInflater): VB
}

使用方式

class LazyFragment(val index:Int) : BaseLazyFragment<MainVM,FragmentLazy2Binding>() {

    override fun initData() {
        super.initData()
        Log.e("TAG","LazyFragment $index initData -- ")
    }

    override fun getViewModelInstance(): MainVM {
        return MainVM()
    }

    override fun getLayoutInflate(layoutInflater: LayoutInflater): FragmentLazy2Binding {
        return FragmentLazy2Binding.inflate(layoutInflater)
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/464376.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第10届蓝桥杯省赛真题剖析-2019年3月24日Scratch编程初中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第126讲。 第10届蓝桥杯省赛&#xff0c;这是2019年3月24日举办的省赛Scratch考试真题&#xff0c;比赛是在线下举办的…

分组双轴图:揭示数据中的关联性和趋势变化

简介 分组双轴图是一种数据可视化图表&#xff0c;指有多个&#xff08;≥2&#xff09;Y轴的数据图表&#xff0c;多为分组柱状图折线图的结合&#xff0c;图表显示更为直观&#xff0c;可以很好地展示不同指标之间的关系&#xff0c;帮助用户更好地理解数据&#xff0c;做出…

深度学习 - 44.MMOE 与 Gate 之多目标学习

目录 一.引言 二.摘要 Abstract 三.介绍 Introduction 四.相关工作 RELATED WORK 1.DNN 中的多任务学习 2.SubNet 集成与 Expert 混合 3.多任务学习应用 五.建模方法 MODELING APPROACHES 1.Shared-bottom Multi-task Model 2.Mixture-of-Experts 3.Multi-gate Mixt…

美团B端“加速度”

配图来自Canva可画 一提起本地生活服务&#xff0c;相信绝大多数人并不会感到陌生&#xff0c;人们经常使用的餐饮外卖&#xff0c;便是本地生活服务的重要组成部分之一。而在消费者线上消费习惯逐渐养成、本地生活服务需求日渐增长等多方因素的共同影响下&#xff0c;本地生活…

BUUCTF pwn——picoctf_2018_rop chain

checksec && 运行 ida main函数调用vuln函数 名为vuln的函数存在溢出 名为flag的函数&#xff0c;通过校验可直接getflag 具体校验过程看图&#xff0c;只有win1和win2均为真&#xff0c;并且a1的值等于0xDEADBAAD才能getflag 变量win2的真假性通过win_function2函…

三个练手的软件测试实战项目(附全套视频跟源码)偷偷卷死他们

项目一&#xff1a;12306抢票项目 项目测试目的 学会Selenium定位web元素的方法 熟练浏览器调试工具使用 项目主体步骤 1&#xff09; 人工走一遍流程&#xff0c;对自动化的流程心中有数 2&#xff09; 按步骤拆分&#xff0c;然后对每一个小步骤编写自动化脚本 3&#xff…

FreeRTOS(三)——应用开发(一)

文章目录 0x01 FreeRTOS文件夹FreeRTOSConfig.h文件内容上面定义的宏决定FreeRTOS.h文件中的定义0x02 创建任务创建静态任务过程configSUPPORT_STATIC_ALLOCATION创建动态任务过程configSUPPORT_DYNAMIC_ALLOCATION 0x03 FreeRTOS启动流程启动流程概述 0x04 任务管理任务调度器…

python基于轻量级YOLOv5的生猪检测+状态识别分析系统

在我之前的一篇文章中有过生猪检测盒状态识别相关的项目实践&#xff0c;如下&#xff1a; 《Python基于yolov4实现生猪检测及状态识》 感兴趣的话可以自行移步阅读&#xff0c;这里主要是基于同样的技术思想&#xff0c;将原始体积较大的yolov4模型做无缝替换&#xff0c;使…

关于python异常的总结

Python异常是在程序执行时发生的错误&#xff0c;可能会导致程序终止运行。 在Python中&#xff0c;异常处理是一种机制&#xff0c;它允许开发人员在程序发生异常时捕获、处理和报告这些异常&#xff0c;以便程序可以继续运行或在出现异常时进行优雅的退出。 在Python中&…

大数据之入门开发流程介绍

目录&#xff1a; 1、大数据的开发大致流程2、技术导图 1、大数据的开发大致流程 1.1 数据收集 大数据处理的第一步是数据的收集。现在的中大型项目通常采用微服务架构进行分布式部署&#xff0c;所以数据的采集需要在多台服务器上进行&#xff0c;且采集过程不能影响正常业务的…

Domino的线程ID和操作系统的进程ID对应关系

大家好&#xff0c;才是真的好。 很多时候&#xff0c;在Domino中运行的任务出现一些错误提示&#xff0c;如果能够准确定位到和提示信息相关任务时&#xff0c;对我们排错有着巨大的帮助&#xff0c;也能节省很多时间。 例如&#xff0c;我们可能在Domino实时控制台上看到以…

RedHat8配置本地YUM源

目录&#xff1a; RedHat8配置本地YUM源1、创建规则文件2、创建挂载点3、挂载ISO镜像(1).将iso镜像连接到虚拟机再进行挂载a.将ISO镜像连接虚拟机b.挂载镜像到挂载点c.使用df -h查看当前系统设备挂载情况 (2)将iso镜像上传至服务器再进行挂载a.将ISO镜像通过ftp工具上传b.挂载镜…

Spring Boot——优雅的参数校验

&#x1f388; 概述 当我们想提供可靠的 API 接口&#xff0c;对参数的校验&#xff0c;以保证最终数据入库的正确性&#xff0c;是 必不可少 的活。比如下图就是 我们一个项目里 新增一个菜单校验 参数的函数&#xff0c;写了一大堆的 if else 进行校验&#xff0c;或者基础校…

C#简单向:textbox添加提示内容

项目场景&#xff1a; 向C#窗体项目的textbox内添加提示内容&#xff0c;如下图所示效果&#xff1a; 具体实现&#xff1a; 首先&#xff1a; 1.到所要操作的文件(/xx.cs/xx.Designer.cs),这里我是到Form3.cs/Form3.Designer.cs文件 2.找到你所要操作的textBox&#xff0c…

数据结构与算法(一):基础数据结构(算法概念、数组、链表、栈、队列)

算法概念、数组、链表、栈、队列 判断一个数是否是2的N次方&#xff1f; N & (N-1) 0 (N > 0)算题&#xff1a; 力扣 https://leetcode.cn/POJ http://poj.org/ 算法 算法概念 算法代表&#xff1a; 高效率和低存储 内存占用小、CPU占用小、运算速度快 算法的高…

C# HttpClient使用JWT请求token调用接口,解决返回HTML网页的异常信息

一.项目目的&#xff1a; 1.使用JWT获取token&#xff0c;调用外部提供的接口&#xff0c;解决返回HTML错误信息。 错误缘由&#xff0c;接口服务器未能识别token&#xff0c;token信息不准确。 二.项目工具&#xff1a; Visual Studio&#xff08;开发工具&#xff09;&…

【Java|golang】1031. 两个非重叠子数组的最大和---前缀和+滑动窗口

给你一个整数数组 nums 和两个整数 firstLen 和 secondLen&#xff0c;请你找出并返回两个非重叠 子数组 中元素的最大和&#xff0c;长度分别为 firstLen 和 secondLen 。 长度为 firstLen 的子数组可以出现在长为 secondLen 的子数组之前或之后&#xff0c;但二者必须是不重…

专为Windows电脑和服务器设计的磁盘管理软件

关于Windows磁盘管理 磁盘管理是Windows自带工具&#xff0c;允许你对磁盘进行一些基本操作&#xff0c;Windows个人用户和Windows Server用户可以使用它来&#xff1a; 1. 创建一个新驱动器&#xff0c;如“新建简单卷”功能。 2. 将一个卷扩展到当前未被同一磁盘…

STM32CubeMX配置I2C通讯

1.如上图所示点击New Project 2.如上图所示选择自己所开发的新品最后双击芯片型号 3.配置RCC&#xff0c;我的芯片使用的是外部高速晶振。这里如图所选。 4.配置一下串口 5.配置I2C 6.根据自己的硬件选择时钟源和主频 6.①填写项目名②选择项目路径③选择开发环境④获取代码 …

Android build.gradle配置详解

Android Studio是采用gradle来构建项目的&#xff0c;gradle是基于groovy语言的&#xff0c;如果只是用它构建普通Android项目的话&#xff0c;是可以不去学groovy的。当我们创建一个Android项目时会包含两个Android build.gradle配置详解文件&#xff0c;如下图&#xff1a; …