Lifecycle:生命周期感知型组件的基础 —— Jetpack 系列(1)
2022/7/17 4:15:02
本文主要是介绍Lifecycle:生命周期感知型组件的基础 —— Jetpack 系列(1),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
请点赞,你的点赞对我意义重大,满足下我的虚荣心。
🔥 Hi,我是小彭。本文已收录到 GitHub · Android-NoteBook 中。这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] 跟我一起成长。
前言
- 生命周期是 Activity 的核心特性之一,也是 Android 视图开发无法规避的重要问题。 为了更加健壮地处理生命周期问题,Google 的解决方案是将生命周期定义为一套标准的行为模式,即 Lifecycle 框架。 这种方式不仅简化了在 Activity / Fragment 等生命周期宿主中分发生命周期事件的复杂度,还提供了自定义生命周期宿主的标准模板。
- Lifecycle 是多个 Jetpack 组件的基础,例如我们熟悉的 LiveData 就是以 Lifecycle 为基础实现的生命周期感知型数据容器,因此我们选择将 Lifecycle 放在 Jetpack 系列的第一篇。
1. 认识 Lifecycle
1.1 为什么要使用 Lifecycle?
Lifecycle 的主要作用是简化实现生命周期感知型组件的复杂度。 在传统的方式中,需要手动从外部宿主(如 Activity、Fragment 或自定义宿主)中将生命周期事件分发到功能组件内部,这势必会造成宿主代码复杂度增加。例如:
MyActivity.kt
// Activity 宿主 class MyActivity : AppCompatActivity() { private val myWorker = MyWorker() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 分发生命周期事件 myWorker.init() } override fun onStart(){ super.onStart() // 分发生命周期事件 myWorker.onStart() } override fun onStop() { super.onStop() // 分发生命周期事件 myWorker.onStop() } }
而使用 Lifecycle 组件后,能够将分发宿主生命周期事件的方法迁移到功能组件内部,宿主不再需要直接参与调整功能组件的生命周期。例如:
MyActivity.kt
// Activity 宿主 class MyActivity : AppCompatActivity() { private val myWorker = MyWorker() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 注册观察者 lifecycle.addObserver(myWorker) } }
MyWorker.kt
class MyWorker : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { // 分发生命周期事件 when (event) { Lifecycle.Event.ON_CREATE -> init() Lifecycle.Event.ON_START -> onStart() Lifecycle.Event.ON_STOP -> onStop() } } private fun init() { ... } private fun onStart() { ... } private fun onStop() { ... } }
1.2 Lifecycle 的设计思路
Lifecycle 整体上采用了观察者模式,核心的 API 是 LifecycleObserver 和 LifecycleOwner:
- LifecycleObserver: 观察者 API;
- LifecycleOwner: 被观察者 API,生命周期宿主需要实现该接口,并将生命周期状态分发 Lifecycle,从而间接分发给被观察者;
- Lifecycle: 定义了生命周期的标准行为模式,属于 Lifecycle 框架的核心类,另外框架还提供了一个默认实现 LifecycleRegistry。
LifecycleObserver.java
public interface LifecycleObserver { }
LifecycleOwner.java
public interface LifecycleOwner { @NonNull Lifecycle getLifecycle(); }
1.3 Lifecycle 的使用方法
- 添加依赖: 在 build.gradle 中添加 Lifecycle 依赖,需要注意区分过时的方式:
模块 build.gradle
// 过时方式(lifecycle-extensions 不再维护) implementation "androidx.lifecycle:lifecycle-extensions:2.4.0" // 目前的方式: def lifecycle_version = "2.5.0" // Lifecycle 核心类 implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" // Lifecycle 注解处理器(用于处理 @OnLifecycleEvent 注解) kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" // 应用进程级别 Lifecycle implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
-
注册观察者: Lifecycle 通过 addObserver(LifecycleObserver) 接口注册观察者,支持通过注解或非注解的方式注册观察者,共分为 3 种:
1、LifecycleObserver(注解方式 ,不推荐):在这个场景使用注解处理有种杀鸡用牛刀的嫌疑,并没有比其他两种方式有优势。注解方式存在注解处理过程,并且如果在依赖时遗漏注解处理器的话,还会退化为使用反射回调,因此不推荐使用。
lifecycle.addObserver(object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun create() = {} @OnLifecycleEvent(Lifecycle.Event.ON_START) fun start() = {} @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun resume() = {} @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun pause() = {} @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun stop() = {} @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun destroy() = {} })
- 2、LifecycleEventObserver(非注解方式,推荐)
lifecycle.addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { when (event) { ON_CREATE -> {} ON_START -> {} ON_RESUME -> {} ON_PAUSE -> {} ON_STOP -> {} ON_DESTROY -> {} ON_ANY -> {} } } })
- 3、DefaultLifecycleObserver(非注解方式,推荐)
// DefaultLifecycleObserver 是 FullLifecycleObserver 接口的空实现 lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) {} override fun onStart(owner: LifecycleOwner) {} override fun onResume(owner: LifecycleOwner) {} override fun onPause(owner: LifecycleOwner) {} override fun onStop(owner: LifecycleOwner) {} override fun onDestroy(owner: LifecycleOwner) {} })
注意: Lifecycle 内部会禁止一个观察者注册到多个宿主上。这很好理解,要是绑定了多个宿主的话,Lifecycle 就不知道以哪个宿主的生命周期为准了。
1.4 预定义的宿主
目前,Android 预定义的 Lifecycle 宿主有 3 个:Activity、Fragment 和应用进程级别的宿主 ProcessLifecycleOwner:
- 1、Activity(具体实现在 androidx.activity.ComponentActivity)
- 2、Fragment
- 3、ProcessLifecycleOwner
前两个宿主大家都很熟悉了,第 3 个宿主 ProcessLifecycleOwner 则提供整个应用进程级别 Activity 的生命周期,能够支持非毫秒级别精度监听应用前后台切换的场景。
- Lifecycle.Event.ON_CREATE: 在应用进程启动时分发,只会分发一次;
- Lifecycle.Event.ON_START:在应用进程进入前台(STARTED)时分发,可能分发多次;
- Lifecycle.Event.ON_RESUME:在应用进程进入前台(RESUMED)时分发,可能分发多次;
- Lifecycle.Event.ON_PAUSE:在应用退出前台(PAUSED)时分发,可能分发多次;
- Lifecycle.Event.ON_STOP:在应用退出前台(STOPPED)时分发,可能分发多次;
- Lifecycle.EVENT.ON_DESTROY:注意,不会被分发。
使用示例
ProcessLifecycleOwner.get().lifecycle.addObserver(object: LifecycleEventObserver{ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { ... } })
1.5 自定义宿主
观察者必须绑定到宿主 LifecycleOwner 上,你可以使用系统预定义的宿主,或根据需要自定义宿主。主要步骤是实现 LifecycleOwner 并在内部将生命周期事件分发给调度器 LifecycleRegistry。模板如下:
LifecycleOwner.java
public interface LifecycleOwner { Lifecycle getLifecycle(); }
MyLifecycleOwner.kt
/** * 自定义宿主模板 */ class MyLifecycleOwner : LifecycleOwner { private val mLifecycleRegistry = LifecycleRegistry(this) override fun getLifecycle() = mLifecycleRegistry fun create() { // 并将生命周期状态分发给被观察者 mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) } fun start() { // 并将生命周期状态分发给被观察者 mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START) } fun stop() { // 并将生命周期状态分发给被观察者 mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) } ... }
2. Lifecycle 实现原理分析
2.1 注册观察者的执行过程
Lifecycle#addObserver() 最终会分发到调度器 LifecycleRegistry 中,其中会将观察者和观察者持有的状态包装为一个节点,并且在注册时将观察者状态同步推进到与宿主相同的状态中。
LifecycleRegistry.java
private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap = new FastSafeIterableMap<>(); private State mState; @Override public void addObserver(LifecycleObserver observer) { // 观察者的初始状态:要么是 DESTROYED,要么是 INITIALIZED,确保观察者可以介绍到完整的事件流 State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED; ObserverWithState statefulObserver = new ObserverWithState(observer, initialState); ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver); ... // 将观察者推进到宿主最新的状态 State targetState = calculateTargetState(observer); while ((statefulObserver.mState.compareTo(targetState) < 0 && mObserverMap.contains(observer))) { pushParentState(statefulObserver.mState); statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState)); popParentState(); // mState / subling may have been changed recalculate targetState = calculateTargetState(observer); } ... } @Override public void removeObserver(@NonNull LifecycleObserver observer) { mObserverMap.remove(observer); } // ObserverWithState:观察者及其观察状态 static class ObserverWithState { State mState; LifecycleEventObserver mLifecycleObserver; ObserverWithState(LifecycleObserver observer, State initialState) { // 用适配器包装观察者,实现对不同形式观察者的统一分发 mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer); mState = initialState; } }
2.2 Lifecycle 如何适配不同类型的观察者
为了适配上面提到的不同类型的观察者,LifecycleRegistry 还为它们提供了一个适配层:非注解的方式会包装为一个 LifecycleEventObserver 的适配器对象,对于注解的方式,如果项目中引入了 annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
,会在编译时生成工具类 MyObserver_LifecycleAdapter
,否则会使用反射回调注解方法。
LifecycleRegistry.java
// ObserverWithState:观察者及其观察状态 static class ObserverWithState { State mState; // 适配器 LifecycleEventObserver mLifecycleObserver; ObserverWithState(LifecycleObserver observer, State initialState) { // 用适配器包装观察者,实现对不同形式观察者的统一分发 mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer); mState = initialState; } void dispatchEvent(LifecycleOwner owner, Event event) { // 通过事件获得下一个状态 State newState = getStateAfter(event); mState = min(mState, newState); // 回调 onStateChanged() 方法 mLifecycleObserver.onStateChanged(owner, event); mState = newState; } }
Lifecycling.java
@NonNull static LifecycleEventObserver lifecycleEventObserver(Object object) { boolean isLifecycleEventObserver = object instanceof LifecycleEventObserver; boolean isFullLifecycleObserver = object instanceof FullLifecycleObserver; // 1. 观察者同时实现 LifecycleEventObserver 和 FullLifecycleObserver if (isLifecycleEventObserver && isFullLifecycleObserver) { return new FullLifecycleObserverAdapter((FullLifecycleObserver) object, (LifecycleEventObserver) object); } // 2. 观察者只实现 FullLifecycleObserver if (isFullLifecycleObserver) { return new FullLifecycleObserverAdapter((FullLifecycleObserver) object, null); } // 3. 观察者只实现 LifecycleEventObserver if (isLifecycleEventObserver) { return (LifecycleEventObserver) object; } // 4. 观察者使用注解方式: final Class<?> klass = object.getClass(); int type = getObserverConstructorType(klass); if (type == GENERATED_CALLBACK) { // APT 自动生成的 MyObserver_LifecycleAdapter List<Constructor<? extends GeneratedAdapter>> constructors = sClassToAdapters.get(klass); if (constructors.size() == 1) { GeneratedAdapter generatedAdapter = createGeneratedAdapter( constructors.get(0), object); return new SingleGeneratedAdapterObserver(generatedAdapter); } GeneratedAdapter[] adapters = new GeneratedAdapter[constructors.size()]; for (int i = 0; i < constructors.size(); i++) { adapters[i] = createGeneratedAdapter(constructors.get(i), object); } return new CompositeGeneratedAdaptersObserver(adapters); } // 反射调用 return new ReflectiveGenericLifecycleObserver(object); }
FullLifecycleObserverAdapter.java
class FullLifecycleObserverAdapter implements LifecycleEventObserver { private final FullLifecycleObserver mFullLifecycleObserver; private final LifecycleEventObserver mLifecycleEventObserver; FullLifecycleObserverAdapter(FullLifecycleObserver fullLifecycleObserver,LifecycleEventObserver lifecycleEventObserver) { mFullLifecycleObserver = fullLifecycleObserver; mLifecycleEventObserver = lifecycleEventObserver; } @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { // 分发到 mFullLifecycleObserver 和 mLifecycleEventObserver } }
2.3 Lifecycle 如何感知 Activity 生命周期
宿主的生命周期事件需要分发到调度器 LifecycleRegistry 中,在高版本有直接观察 Activity 生命周期的 API,而在低版本使用无界面的 Fragment 间接观察 Activity 的生命周期。
androidx.activity.ComponentActivity.java
public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner ...{ private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); @NonNull @Override public Lifecycle getLifecycle() { return mLifecycleRegistry; } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... ReportFragment.injectIfNeededIn(this); ... } }
ReportFragment.java
// 空白 Fragment public class ReportFragment extends Fragment { public static void injectIfNeededIn(Activity activity) { if (Build.VERSION.SDK_INT >= 29) { // 在高版本有直接观察 Activity 生命周期的 API activity.registerActivityLifecycleCallbacks(new LifecycleCallbacks()); } // 在低版本使用无界面的 Fragment 间接观察 Activity 的生命周期 android.app.FragmentManager manager = activity.getFragmentManager(); if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) { manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit(); // Hopefully, we are the first to make a transaction. manager.executePendingTransactions(); } } // 从 registerActivityLifecycleCallbacks() 或 Fragment 回调回来 static void dispatch(Activity activity, Lifecycle.Event event) { ... // 分发声明周期事件 activity.getLifecycle().handleLifecycleEvent(event); } }
2.4 Lifecycle 分发生命周期事件的过程
当宿主的生命周期发生变化时,会分发到 LifecycleRegistry#handleLifecycleEvent(Lifecycle.Event)
,将观察者的状态回调到最新的状态上。
LifecycleRegistry.java
private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap =new FastSafeIterableMap<>(); private final WeakReference<LifecycleOwner> mLifecycleOwner; public LifecycleRegistry(@NonNull LifecycleOwner provider) { mLifecycleOwner = new WeakReference<>(provider); mState = INITIALIZED; } // 分发生命周期事件 public void handleLifecycleEvent(Lifecycle.Event event) { // 通过事件获得下一个状态 State next = getStateAfter(event); // 执行状态转移 moveToState(next); } private void moveToState(State next) { if (mState == next) { return; } mState = next; if (mHandlingEvent || mAddingObserverCounter != 0) { mNewEventOccurred = true; // we will figure out what to do on upper level. return; } mHandlingEvent = true; sync(); mHandlingEvent = false; } private void sync() { // isSynced() 判断所有观察者状态是否同步到最新状态 while (!isSynced()) { mNewEventOccurred = false; if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) { // 生命周期回退,最终调用 ObserverWithState#dispatchEvent() 分发事件 backwardPass(lifecycleOwner); } Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest(); if (!mNewEventOccurred && newest != null && mState.compareTo(newest.getValue().mState) > 0) { // 生命周期前进,最终调用 ObserverWithState#dispatchEvent() 分发事件 forwardPass(lifecycleOwner); } } mNewEventOccurred = false; }
3. Lifecycle 实践案例
3.1 使用 Lifecycle 解决 Dialog 内存泄漏
在 Activity 结束时,如果 Activity 上还存在未关闭的 Dialog,则会导致内存泄漏:
WindowLeaked: Activtiy MainActivity has leaked window DecorView@dfxxxx[MainActivity] thas was originally added here
解决方法:
- 方法 1:在 Activity#onDestroy() 中手动调用 Dialog#dismiss();
- 方法 2:替换为 DialogFragment,内部会在 Fragment#onDestroyView() 时关闭 Dialog;
- 方法 3:自定义 BaseDialog,使用 Lifecycle 监听宿主 DESTROYED 生命周期关闭 Dialog:
BaseDialog.kt
class BaseDialog(context: Context) : Dialog(context), LifecycleEventObserver { init { if (context is ComponentActivity) { context.lifecycle.addObserver(this) } } override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (Lifecycle.Event.ON_DESTROY == event) { if (isShowing) { dismiss() } } } }
3.2 生命周期感知型协程
Lifecycle 也加强了对 Kotlin 协程的支持 LifecycleCoroutineScope,我们可以构造出与生命周期相关联的协程作用域,主要支持 2 个特性:
- 1、在宿主消亡(DESTROYED)时,自动取消协程;
- 2、在宿主离开指定生命周期状态时挂起,在宿主重新进入指定生命周期状态时恢复协程(例如 launchWhenResumed)。
使用示例
// 示例 1 lifecycleScope.launch { } // 示例 2(内部等价于示例 3) lifecycleScope.launchWhenResumed { } // 示例 3 lifecycleScope.launch { whenResumed { } }
1、自动取消协程实现原理分析: 核心在于 LifecycleCoroutineScopeImpl 中,内部在初始化时会注册一个观察者到宿主生命周期上,并在宿主进入 DESTROYED 时取消(cancel)协程。
LifecycleOwner.kt
// 基于 LifecycleOwner 的扩展函数 public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope get() = lifecycle.coroutineScope
Lifecycle.kt
public val Lifecycle.coroutineScope: LifecycleCoroutineScope get() { // 已简化 val newScope = LifecycleCoroutineScopeImpl( this, SupervisorJob() + Dispatchers.Main.immediate ) newScope.register() return newScope } public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope { internal abstract val lifecycle: Lifecycle ... // 开启协程再调用 whenResumed public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch { lifecycle.whenResumed(block) } } // 实现类 internal class LifecycleCoroutineScopeImpl( override val lifecycle: Lifecycle, override val coroutineContext: CoroutineContext ) : LifecycleCoroutineScope(), LifecycleEventObserver { init { // 立即取消协程 if (lifecycle.currentState == Lifecycle.State.DESTROYED) { coroutineContext.cancel() } } fun register() { // 绑定宿主生命周期 launch(Dispatchers.Main.immediate) { if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) { lifecycle.addObserver(this@LifecycleCoroutineScopeImpl) } else { coroutineContext.cancel() } } } override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { // 分发宿主生命周期事件 if (lifecycle.currentState <= Lifecycle.State.DESTROYED) { // 取消协程 lifecycle.removeObserver(this) coroutineContext.cancel() } } }
2、关联指定生命周期实现原理分析: 实现原理也是类似的,launchWhenResumed() 内部在 LifecycleContro 中注册观察者,最终通过协程调度器 PausingDispatcher
挂起(pause)或恢复(resume)协程。
PausingDispatcher.kt
public suspend fun <T> LifecycleOwner.whenResumed(block: suspend CoroutineScope.() -> T): T = lifecycle.whenResumed(block) public suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T { return whenStateAtLeast(Lifecycle.State.RESUMED, block) } public suspend fun <T> Lifecycle.whenStateAtLeast( minState: Lifecycle.State, block: suspend CoroutineScope.() -> T ): T = withContext(Dispatchers.Main.immediate) { val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job") // 分发器,内部持有一个分发队列,用于支持暂停协程 val dispatcher = PausingDispatcher() val controller = LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job) try { withContext(dispatcher, block) } finally { controller.finish() } }
LifecycleController.kt
@MainThread internal class LifecycleController( private val lifecycle: Lifecycle, private val minState: Lifecycle.State, private val dispatchQueue: DispatchQueue, parentJob: Job ) { private val observer = LifecycleEventObserver { source, _ -> // 分发宿主生命周期事件 if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) { // 取消协程 parentJob.cancel() lifecycle.removeObserver(observer) dispatchQueue.finish() } else if (source.lifecycle.currentState < minState) { // 暂停协程 dispatchQueue.pause() } else { // 恢复协程 dispatchQueue.resume() } } init { // 直接取消协程 if (lifecycle.currentState == Lifecycle.State.DESTROYED) { // 取消协程 parentJob.cancel() lifecycle.removeObserver(observer) dispatchQueue.finish() } else { lifecycle.addObserver(observer) } } }
3.3 安全地观察 Flow 数据流
我们知道,Kotlin Flow 不具备生命周期感知的能力(当然了,Flow 是 Kotlin 生态的组件,不是仅针对 Android 生态的组件),那么 Flow 观察者如何保证在安全的生命周期订阅数据呢?
- 方法 1:使用生命周期感知型协程(不推荐)
- 方法 2:使用 Flow#flowWithLifecycle() API(推荐)
具体分析在 [4、Flow:LiveData 的替代方案]这篇文章里都讲过,这里不重复。
4. 总结
到这里,Jetpack 中最基础的 Lifecycle 组件就讲完了,下几篇文章我们将讨论基于 Lifecycle 实现的其他 Jetpack 组件,你知道是什么吗?关注我,带你了解更多。
这篇关于Lifecycle:生命周期感知型组件的基础 —— Jetpack 系列(1)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享