形形色色的Fragment生命周期

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

Fragment生命周期在不同的组件下使用具有不同的生命周期,如Activity中,viewpager2viewpager.


相关类声明


class FragmentOne(val logTag: String) : Fragment() {
    

  override fun onAttach(context: Context) {
    
    super.onAttach(context)
    Log.e(logTag, "onAttach")
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    
    super.onCreate(savedInstanceState)
    Log.e(logTag, "onCreate")
  }

  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View? {
    
    Log.e(logTag, "onCreateView")

    return super.onCreateView(inflater, container, savedInstanceState)
  }

  override fun onActivityCreated(savedInstanceState: Bundle?) {
    
    super.onActivityCreated(savedInstanceState)
    Log.e(logTag, "onActivityCreated")
  }

  override fun onStart() {
    
    super.onStart()

    Log.e(logTag, "onStart ")
  }

  override fun onResume() {
    
    super.onResume()
    Log.e(logTag, "onResume")
  }

  override fun onPause() {
    
    super.onPause()
    Log.e(logTag, "onPause")
  }

  override fun onStop() {
    
    super.onStop()
    Log.e(logTag, "onStop")
  }

  override fun onDestroyView() {
    
    super.onDestroyView()
    Log.e(logTag, "onDestroyView")
  }

  override fun onDestroy() {
    
    Log.e(logTag, "onDestroy")
    super.onDestroy()
  }

  override fun onDetach() {
    
    super.onDetach()

    Log.e(logTag, "onDetach")
  }

  override fun onHiddenChanged(hidden: Boolean) {
    
    super.onHiddenChanged(hidden)
    Log.e(logTag, "onHiddenChanged ${
      hidden}")
  }

}
class MainActivity : AppCompatActivity() {
    

  val logTag = "MainActivity"

  override fun onCreate(savedInstanceState: Bundle?) {
    
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    Log.e(logTag, "onCreate")

  }

  override fun onRestart() {
    
    super.onRestart()
    Log.e(logTag, "onRestart")
  }

  override fun onStart() {
    
    super.onStart()
    Log.e(logTag, "onStart")
  }

  override fun onResume() {
    
    super.onResume()
    Log.e(logTag, "onResume")
  }

  override fun onPause() {
    
    super.onPause()
    Log.e(logTag, "onPause")
  }

  override fun onStop() {
    
    super.onStop()
    Log.e(logTag, "onStop")
  }

  override fun onDestroy() {
    
    super.onDestroy()
    Log.e(logTag, "onDestroy")
  }
}

一、Activity下的各类生命周期

一、单纯FragmentManager添加或隐藏


class MainActivity : AppCompatActivity() {
    
   //.......
   //....省略生命周期打印代码
   //.......
   
  val fragmentOne = FragmentOne("FragmentOne")

  fun submitClick(view: View) {
    

    Log.e(logTag, "提交FragmentOne到事物")
    supportFragmentManager.beginTransaction()
        .add(R.id.fl_container, fragmentOne)
        .commitNowAllowingStateLoss()
  }

  fun hideClick(view: View) {
    
    Log.e(logTag, "隐藏FragmentOne到事物")
    supportFragmentManager.beginTransaction()
        .hide( fragmentOne)
        .commitNowAllowingStateLoss()

  }
}

(1)调用submitClick函数添加fragment正常生命周期如下:

这个生命周期是基础业务知识所以不做例子讲解

在这里插入图片描述
(2)调用hideClick函数隐藏fragment不影响生命周期,但是会回调onHiddenChanged函数,传入参数为true.

简单来说隐藏fragment操作后回调onHiddenChanged,但是不影响跟随Activity生命周期.比如调用隐藏函数后,按下Home键返回主页,在返回应用.

在这里插入图片描述

二、FragmentManager回退栈

提交代码

fun submitClick(view: View) {
    
    Log.e(logTag, "提交FragmentOne-one")
    supportFragmentManager.beginTransaction()
        .addToBackStack(null)
        .add(R.id.fl_container, fragmentOne)
        .commit()
    thread {
    
      //之所以加上休眠时间间隔 为了区区分打印两个生命周期方便
      TimeUnit.SECONDS.sleep(1)
      runOnUiThread {
    

        Log.e(logTag, "提交FragmentOne-two")
        supportFragmentManager.beginTransaction()
            .addToBackStack(null)
            .add(R.id.fl_container, fragmenttwo)
            .commit()
      }
    }

  }

日志输出:

MainActivity: 提交FragmentOne-one
FragmentOne-one: onAttach
FragmentOne-one: onCreate
FragmentOne-one: onCreateView
FragmentOne-one: onActivityCreated
FragmentOne-one: onStart
FragmentOne-one: onResume
MainActivity: 提交FragmentOne-two
FragmentOne-two: onAttach
FragmentOne-two: onCreate
FragmentOne-two: onCreateView
FragmentOne-two: onActivityCreated
FragmentOne-two: onStart
FragmentOne-two: onResume

生命周期没有太多特殊地方.

弹出:

  fun hideClick(view: View) {
    
    Log.e(logTag, "弹出FragmentOne-two")
    supportFragmentManager.popBackStack()

    thread {
    
      //之所以加上休眠时间间隔 为了区区分打印两个生命周期方便
      TimeUnit.SECONDS.sleep(1)
      runOnUiThread {
    

        Log.e(logTag, "弹出FragmentOne-one")
        supportFragmentManager.popBackStack()
      }
    }
  }

输出:

MainActivity: 弹出FragmentOne-two
FragmentOne-two: onPause
FragmentOne-two: onStop
FragmentOne-two: onDestroyView
FragmentOne-two: onDestroy
FragmentOne-two: onDetach
MainActivity: 弹出FragmentOne-one
FragmentOne-one: onPause
FragmentOne-one: onStop
FragmentOne-one: onDestroyView
FragmentOne-one: onDestroy
FragmentOne-one: onDetach

三、FragmentManager 替换(replace)

  fun submitClick(view: View) {
    
    Log.e(logTag, "提交FragmentOne-one")
    supportFragmentManager.beginTransaction()
        .replace(R.id.fl_container, fragmentOne)
        .commit()
    thread {
    
      //之所以加上休眠时间间隔 为了区区分打印两个生命周期方便
      TimeUnit.SECONDS.sleep(1)
      runOnUiThread {
    

        Log.e(logTag, "提交FragmentOne-two")
        supportFragmentManager.beginTransaction()
            .replace(R.id.fl_container, fragmenttwo)
            .commit()
      }
    }
  }

输出:

  MainActivity: 提交FragmentOne-one
  FragmentOne-one: onAttach
  FragmentOne-one: onCreate
  FragmentOne-one: onCreateView
  FragmentOne-one: onActivityCreated
  FragmentOne-one: onStart
  FragmentOne-one: onResume
  MainActivity: 提交FragmentOne-two
  FragmentOne-two: onAttach
  FragmentOne-two: onCreate
  FragmentOne-one: onPause
  FragmentOne-one: onStop
  FragmentOne-one: onDestroyView
  FragmentOne-one: onDestroy
  FragmentOne-one: onDetach
  FragmentOne-two: onCreateView
  FragmentOne-two: onActivityCreated
  FragmentOne-two: onStart
  FragmentOne-two: onResume

如果配合回退栈情况:

  fun submitClick(view: View) {
    
    Log.e(logTag, "提交FragmentOne-one")
    supportFragmentManager.beginTransaction()
        .addToBackStack(null)
        .replace(R.id.fl_container, fragmentOne)
        .commit()
    thread {
    
      //之所以加上休眠时间间隔 为了区区分打印两个生命周期方便
      TimeUnit.SECONDS.sleep(1)
      runOnUiThread {
    
        Log.e(logTag, "提交FragmentOne-two")
        supportFragmentManager.beginTransaction()
            .addToBackStack(null)
            .replace(R.id.fl_container, fragmenttwo)
            .commit()
      }
    }
  }

输出:

MainActivity: 提交FragmentOne-one
FragmentOne-one: onAttach
FragmentOne-one: onCreate
FragmentOne-one: onCreateView
FragmentOne-one: onActivityCreated
FragmentOne-one: onStart
FragmentOne-one: onResume
MainActivity: 提交FragmentOne-two
FragmentOne-two: onAttach
FragmentOne-two: onCreate
FragmentOne-one: onPause
FragmentOne-one: onStop
FragmentOne-one: onDestroyView
FragmentOne-two: onCreateView
FragmentOne-two: onActivityCreated
FragmentOne-two: onStart
FragmentOne-two: onResume

点击回退后的弹出信息:

FragmentOne-two: onPause
FragmentOne-two: onStop
FragmentOne-two: onDestroyView
FragmentOne-two: onDestroy
FragmentOne-two: onDetach
FragmentOne-one: onCreateView
FragmentOne-one: onActivityCreated
FragmentOne-one: onStart
FragmentOne-one: onResume

总结:
添加回退栈后生命周期如果被replace 那么生命周期会结束在onDestroyView,返回后重建视图从onCreateView开始

四、FragmentManager 分离/附加(detach,attach)


  fun submitClick(view: View) {
    
    Log.e(logTag, "提交FragmentOne-one")
    supportFragmentManager.beginTransaction()
        .add(R.id.fl_container, fragmentOne)
        .commit()
  }

  fun hideClick(view: View) {
    
    Log.e(logTag, "-----------------------" )
    Log.e(logTag, "fragmentOne detach" )
    supportFragmentManager.beginTransaction()
        .detach( fragmentOne)
        .commitNowAllowingStateLoss()
    Log.e(logTag, "-----------------------" )
    Log.e(logTag, "fragmentOne attch" )
    supportFragmentManager.beginTransaction()
        .attach( fragmentOne)
        .commitNowAllowingStateLoss()
    Log.e(logTag, "-----------------------" )

  }

输出:

MainActivity: 提交FragmentOne-one         
FragmentOne-one: onAttach               
FragmentOne-one: onCreate               
FragmentOne-one: onCreateView           
FragmentOne-one: onActivityCreated      
FragmentOne-one: onStart                
FragmentOne-one: onResume               
MainActivity: -----------------------   
MainActivity: fragmentOne detach        
FragmentOne-one: onPause                
FragmentOne-one: onStop                 
FragmentOne-one: onDestroyView          
MainActivity: -----------------------   
MainActivity: fragmentOne attch         
FragmentOne-one: onCreateView           
FragmentOne-one: onActivityCreated      
FragmentOne-one: onStart                
FragmentOne-one: onResume               
MainActivity: -----------------------   

tip:如果fragment已经被attach/detach 那么再次调用对应的supportFragmentManagerattach/detach将不会有任何反应.

五、FragmentManager setMaxLifecycle

setMaxLifecycle我也是在近期阅读源码时才发现,此函数用于设置某个fragmentFragmentManager最大的生命周期.

  fun submitClick(view: View) {
    
    Log.e(logTag, "提交FragmentOne-one")
    supportFragmentManager.beginTransaction()
        .add(R.id.fl_container, fragmentOne)
        .setMaxLifecycle(fragmentOne,Lifecycle.State.CREATED)
        .commit()
  }
MainActivity: 提交FragmentOne-one
FragmentOne-one: onAttach
FragmentOne-one: onCreate
MainActivity: onPause
MainActivity: onStop
MainActivity: onRestart
MainActivity: onStart
MainActivity: onResume

可见设置最大生命周期后哪怕Activity生命周期变化fragment也必须遵循setMaxLifecycle.

二、ViewPager下的生命周期

首先需要明白viewpager2两种适配类别

adapter两种类型
(1)FragmentStatePagerAdapter
(2)FragmentPagerAdapter

两个适配最大的区别在于函数destroyItem中如何处理销毁fragment

FragmentStatePagerAdapter

  @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
        //...
        Fragment fragment = (Fragment) object;
        mCurTransaction.remove(fragment);       
    	//...
    }

FragmentPagerAdapter

  @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
        Fragment fragment = (Fragment) object;
        
        mCurTransaction.detach(fragment);
    }

可以简单理解FragmentStatePagerAdapter会完全移除fragment,FragmentPagerAdapter仅会detach,如果对于内存比较紧张且不考虑再次使用可以使用FragmentStatePagerAdapter.

本文仅探究FragmentPagerAdapter(知道这个完全意会FragmentStatePagerAdapter)

构造函数

FragmentStatePagerAdapter(@NonNull FragmentManager fm,
            @Behavior int behavior)

我们看下第二参数参数枚举:

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
    BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
    private @interface Behavior {
     }

BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:
被加载的且对用户可见的fragment同步activity生命周期到onResume函数,其余对用户不可见的fragment不会回调到onResume.

BEHAVIOR_SET_USER_VISIBLE_HINT:
所有已经被viewpager加载且未移除的fragmentActivity同步生命周期,但是会回调setUserVisibleHint判断是否对用户可见,或者获取userVisibleHint属性进行判断.这选项已弃用.注意onHiddenChanged并不回调

BEHAVIOR_SET_USER_VISIBLE_HINT

举例子说明:

class MyFragmentAdapter constructor(
    fm: FragmentManager,
    behavior: Int
) : FragmentPagerAdapter(fm, behavior) {
    
//) : FragmentPagerAdapter(fm, behavior) {
    

    val fragmentList =
        listOf<FragmentOne>(
            FragmentOne("FragmentOne-one"),
            FragmentOne("FragmentOne-two"),
            FragmentOne("FragmentOne-three"),
            FragmentOne("FragmentOne-four"),
            FragmentOne("FragmentOne-five"),
        )

    override fun getCount(): Int {
    
        return fragmentList.size
    }

    override fun getItem(position: Int): Fragment {
    
        return fragmentList[position]
    }

}
//vp是viewpager对象
 vp = findViewById(R.id.viewpager)
        //初始化
        val myFragmentAdapter = MyFragmentAdapter(
            supportFragmentManager,
            FragmentPagerAdapter.BEHAVIOR_SET_USER_VISIBLE_HINT
        )
        vp.adapter = myFragmentAdapter
        vp.offscreenPageLimit = 1
        myFragmentAdapter.notifyDataSetChanged()

vp显示第一个fragment(FragmentOne-one)

MainActivity: onCreate
MainActivity: onStart
MainActivity: onResume
FragmentOne-one: setUserVisibleHint false
FragmentOne-two: setUserVisibleHint false
FragmentOne-one: setUserVisibleHint true
FragmentOne-one: onAttach
FragmentOne-one: onCreate
FragmentOne-two: onAttach
FragmentOne-two: onCreate
FragmentOne-one: onCreateView
FragmentOne-one: onActivityCreated
FragmentOne-one: onStart
FragmentOne-one: onResume
FragmentOne-two: onCreateView
FragmentOne-two: onActivityCreated
FragmentOne-two: onStart
FragmentOne-two: onResume

注意点:
(1)FragmentOne-two被初始化是因为我们设offscreenPageLimit为1,所以预加载显示fragment两侧各1个fragment.
(2)FragmentOne-two setUserVisibleHint 被回调 且传入参数为true 标识当前不可以对用可见
(3)BEHAVIOR_SET_USER_VISIBLE_HINT会让所有被加载的fragment同步Activity生命周期

vp滑动到第二个fragment(FragmentOne-two)

FragmentOne-three: setUserVisibleHint false
FragmentOne-one: setUserVisibleHint false
FragmentOne-two: setUserVisibleHint true
FragmentOne-three: onAttach
FragmentOne-three: onCreate
FragmentOne-three: onCreateView
FragmentOne-three: onActivityCreated
FragmentOne-three: onStart
FragmentOne-three: onResume

在第二个fragment(FragmentOne-two)后 此时按下home键 (可见被加载的所有fragment同步生命周期)

FragmentOne-one: onPause
FragmentOne-two: onPause
FragmentOne-three: onPause
MainActivity: onPause
FragmentOne-one: onStop
FragmentOne-two: onStop
FragmentOne-three: onStop
MainActivity: onStop

滑动到第三个fragment(FragmentOne-three),第一个fragment(FragmentOne-one)被销毁.(FragmentPagerAdapter销毁操作只执行detach)

FragmentOne-four: setUserVisibleHint false
FragmentOne-two: setUserVisibleHint false
FragmentOne-three: setUserVisibleHint true
FragmentOne-four: onAttach
FragmentOne-four: onCreate
FragmentOne-one: onPause
FragmentOne-one: onStop
FragmentOne-one: onDestroyView
FragmentOne-four: onCreateView
FragmentOne-four: onActivityCreated
FragmentOne-four: onStart
FragmentOne-four: onResume

BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT

被加载的且对用户可见的fragment同步activity生命周期到onResume函数,其余对用户不可见的函数不会回调到onResume.

比如,当前显示第一个fragment,已经预加载第二个fragment,此时第二个fragment生命周期只会执行到onpuase.而第一个fragment会执行到onresume.假设用户按下home按键返回主页,第一个fragment会跟随Activity执行onpuase onstop.第二个fragment不会有任何回调.

  vp = findViewById(R.id.viewpager)
        //初始化
        val myFragmentAdapter = MyFragmentAdapter(
            supportFragmentManager,
            FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
        )
        vp.adapter = myFragmentAdapter
        vp.offscreenPageLimit = 1
        myFragmentAdapter.notifyDataSetChanged()

class MyFragmentAdapter constructor(
    fm: FragmentManager,
    behavior: Int
) : FragmentPagerAdapter(fm, behavior) {
    
//) : FragmentPagerAdapter(fm, behavior) {
    

    val fragmentList =
        listOf<FragmentOne>(
            FragmentOne("FragmentOne-one"),
            FragmentOne("FragmentOne-two"),
            FragmentOne("FragmentOne-three"),
            FragmentOne("FragmentOne-four"),
            FragmentOne("FragmentOne-five"),
        )
    override fun getCount(): Int {
    
        return fragmentList.size
    }
    override fun getItem(position: Int): Fragment {
    
        return fragmentList[position]
    }
}

显示第一个fragment(FragmentOne-one)

输出:

MainActivity: onCreate
MainActivity: onStart
MainActivity: onResume
FragmentOne-one: onAttach
FragmentOne-one: onCreate
FragmentOne-two: onAttach
FragmentOne-two: onCreate
FragmentOne-one: onCreateView
FragmentOne-one: onActivityCreated
FragmentOne-one: onStart
FragmentOne-two: onCreateView
FragmentOne-two: onActivityCreated
FragmentOne-two: onStart
FragmentOne-one: onResume

可以看到第二个fragment(FragmentOne-two)只能处于onstart状态,并没有到onresume.

此时我们按下home按键

FragmentOne-one: onPause
MainActivity: onPause
FragmentOne-one: onStop
FragmentOne-two: onStop
MainActivity: onStop

此时在从后台返回返回到应用

MainActivity: onRestart      
FragmentOne-one: onStart     
FragmentOne-two: onStart     
MainActivity: onStart        
MainActivity: onResume       
FragmentOne-one: onResume    

vp滑动到第二个fragment(FragmentOne-two)

  FragmentOne-three: onAttach
  FragmentOne-three: onCreate
  FragmentOne-three: onCreateView
  FragmentOne-three: onActivityCreated
  FragmentOne-three: onStart 
  FragmentOne-one: onPause

总结:
(1) BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT只有对用户显示的fragment才能回调onresume函数
(2) 被滑出不可见且未被销毁会走到onpuase
(3) Activity返回主页执行到后台或者销毁,所有被加载的fragment也会同步
(4) Activity从后台恢复到前台,对用户不显示的fragment会走到onstart函数,对用户可见的fragment会走到onresume函数
(5) …

三、ViewPager2下的生命周期

ViewPager2基于RecycleView实现,所以三级缓存机制会影响到fragment的销毁(三级缓存默认为5个).
如果offscreenPageLimit小于5,那么vp2滑动到第五个fragment将会对第一个fragment执行remove操作.
所有被加载的fragment生命周期同viewpagerBEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT一样,除了被销毁机制之外.


        vp2 = findViewById(R.id.viewpager2)
        //初始化
        val myFragmentAdapter = MyFragmentAdapter(supportFragmentManager, lifecycle)
        vp2.adapter = myFragmentAdapter
        vp2.offscreenPageLimit = 1
        myFragmentAdapter.notifyDataSetChanged()
class MyFragmentAdapter constructor(
    fm: FragmentManager,
    lifecycle: Lifecycle
) : androidx.viewpager2.adapter.FragmentStateAdapter(fm, lifecycle) {
    
    val fragmentList =
        listOf(
            FragmentOne("FragmentOne-one"),
            FragmentOne("FragmentOne-two"),
            FragmentOne("FragmentOne-three"),
            FragmentOne("FragmentOne-four"),
            FragmentOne("FragmentOne-five"),
            FragmentOne("FragmentOne-six"),
            FragmentOne("FragmentOne-seven"),
        )

    override fun getItemCount(): Int {
    
        return fragmentList.size
    }

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

}

显示第一个fragment时

MainActivity: onCreate
MainActivity: onStart
MainActivity: onResume
FragmentOne-one: onAttach
FragmentOne-one: onCreate
FragmentOne-one: onCreateView
FragmentOne-one: onActivityCreated
FragmentOne-one: onStart
FragmentOne-one: onResume
FragmentOne-two: onAttach
FragmentOne-two: onCreate
FragmentOne-two: onCreateView
FragmentOne-two: onActivityCreated
FragmentOne-two: onStart 

按下home按键

FragmentOne-one: onPause     
MainActivity: onPause        
FragmentOne-one: onStop      
FragmentOne-two: onStop      
MainActivity: onStop         

从后台返回到前台

MainActivity: onRestart
FragmentOne-one: onStart
FragmentOne-two: onStart
MainActivity: onStart
MainActivity: onResume
FragmentOne-one: onResume

其它类似BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT所以不再举例.

Viewpager2销毁逻辑举例:

还是上面的例子我们直接滑动第五个Fragment(FragmentOne-five).


/*显示第一个Fragment*/
MainActivity: onCreate
MainActivity: onStart
MainActivity: onResume
FragmentOne-one: onAttach
FragmentOne-one: onCreate
FragmentOne-one: onCreateView
FragmentOne-one: onActivityCreated
FragmentOne-one: onStart
FragmentOne-one: onResume
FragmentOne-two: onAttach
FragmentOne-two: onCreate
FragmentOne-two: onCreateView
FragmentOne-two: onActivityCreated
FragmentOne-two: onStart


/*显示第二个Fragment*/
FragmentOne-three: onAttach
FragmentOne-three: onCreate
FragmentOne-three: onCreateView
FragmentOne-three: onActivityCreated
FragmentOne-three: onStart
FragmentOne-one: onPause
FragmentOne-two: onResume


/*显示第三个Fragment*/
FragmentOne-four: onAttach
FragmentOne-four: onCreate
FragmentOne-four: onCreateView
FragmentOne-four: onActivityCreated
FragmentOne-four: onStart
FragmentOne-two: onPause
FragmentOne-three: onResume

/*显示第四个Fragment*/
FragmentOne-five: onAttach
FragmentOne-five: onCreate
FragmentOne-five: onCreateView
FragmentOne-five: onActivityCreated
FragmentOne-five: onStart
FragmentOne-three: onPause
FragmentOne-four: onResume

/*显示第五个Fragment*/
FragmentOne-six: onAttach
FragmentOne-six: onCreate
FragmentOne-six: onCreateView
FragmentOne-six: onActivityCreated
FragmentOne-six: onStart
FragmentOne-one: onStop
FragmentOne-one: onDestroyView
FragmentOne-one: onDestroy
FragmentOne-one: onDetach
FragmentOne-four: onPause
FragmentOne-five: onResume

可以看到滑动到第五个时FragmentOne-one完全被移除销毁.如果你的业务不需要销毁只需要扩大offscreenPageLimit大小

猜你喜欢

转载自blog.csdn.net/qfanmingyiq/article/details/108495723