Skip to main content

activity_manager/
lib.rs

1//! # Activity Manager
2//!
3//! 一个无 UI 框架依赖的通用 Android 风格页面(Activity)与路由堆栈管理框架。
4//!
5//! 本框架通过 `ActivityHost` 抽象了底层的 UI 渲染和事件循环机制,
6//! 使得核心的路由调度和生命周期管理逻辑可以独立于特定的 UI 框架(如 Dioxus, Iced, Slint 等)运行。
7
8use log::{debug, info, trace, warn};
9use std::fmt::Debug;
10
11// ==========================================
12// 1. 基础定义: 启动模式与路由
13// ==========================================
14
15/// 定义 Activity 的启动模式,决定了新 Activity 如何与现有的任务栈交互。
16///
17/// 概念完全映射自 Android 的 `launchMode` 属性。
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum LaunchMode {
20    /// **标准模式 (Standard)**
21    /// 默认行为。每次启动都会实例化一个新的 Activity 并压入栈顶,允许栈中存在多个相同路由的实例。
22    Standard,
23    /// **栈顶复用模式 (SingleTop)**
24    /// 如果目标 Activity 已经位于栈顶,则不会创建新实例,而是直接调用该实例的 `on_new_intent`。
25    /// 如果不在栈顶,则行为与 Standard 相同。
26    SingleTop,
27    /// **栈内复用模式 (SingleTask / Clear Top)**
28    /// 保证栈内只有一个该路由的实例。如果实例已存在,则将其上方的所有 Activity 弹出(销毁),
29    /// 使其重新成为栈顶,并调用 `on_new_intent`。
30    SingleTask,
31    /// **全局单例模式 (SingleInstance)**
32    /// 极其霸道的独占模式。整个任务栈中仅允许存在该 Activity 的唯一实例。
33    /// - 若已存在:清空栈内其他所有 Activity。
34    /// - 若新建:清空当前栈,以该 Activity 作为栈底和栈顶。
35    SingleInstance,
36}
37
38/// 路由特征 (Trait)。
39/// 业务层的路由枚举(通常是 Enum)必须实现此接口,以便提供启动模式和渲染属性。
40pub trait Route: Debug + Clone + PartialEq + Send + Sync + 'static {
41    /// 获取该路由预设的启动模式。
42    fn launch_mode(&self) -> LaunchMode;
43
44    /// 页面是否是半透明的 (Dialog 风格)?
45    /// - `true`: 外部渲染器应当继续向下渲染底层的视图,直到遇到一个不透明的页面。
46    /// - `false`: 阻断底层视图的渲染(画家算法优化点)。
47    fn is_translucent(&self) -> bool;
48}
49
50/// 页面意图 (Intent)。
51/// 用于携带目标路由以及未来可能扩展的转场动画配置、启动参数等。
52#[derive(Debug, Clone)]
53pub struct Intent<R: Route> {
54    pub target: R,
55}
56
57impl<R: Route> Intent<R> {
58    pub fn new(target: R) -> Self {
59        Self { target }
60    }
61}
62
63// ==========================================
64// 2. 宿主环境 (Activity Host)
65// ==========================================
66
67/// Activity 宿主环境定义。
68///
69/// 充当 UI 框架与核心调度器之间的桥梁。接入方需要定义一个空结构体并实现该 Trait,
70/// 以指定具体的视图、副作用、订阅和消息类型。
71pub trait ActivityHost: 'static {
72    /// 视图类型 (例如 Dioxus/Iced 的 Element)
73    type View;
74    /// 副作用/异步任务类型 (例如 Iced 的 Task/Command)
75    type Effect;
76    /// 订阅类型 (如定时器、全局快捷键等)
77    type Subscription;
78    /// 全局应用消息类型
79    type Message: Clone + Debug + Send + Sync;
80}
81
82// ==========================================
83// 3. Activity 特征 (Component Interface)
84// ==========================================
85
86/// 页面组件核心接口。
87pub trait Activity<R, H, C>: 'static
88where
89    R: Route,
90    H: ActivityHost,
91    C: Clone + Send + Sync,
92{
93    /// 返回当前页面的路由标识
94    fn route(&self) -> R;
95
96    /// 处理业务逻辑更新,返回对应的 UI 副作用
97    fn update(&mut self, message: H::Message) -> Vec<H::Effect>;
98
99    /// 渲染当前页面的独立视图
100    fn view(&self) -> H::View;
101
102    /// 订阅全局或底层系统事件
103    fn subscription(&self) -> Option<H::Subscription> {
104        None
105    }
106
107    // --- 生命周期钩子 (Lifecycle Hooks) ---
108
109    /// 页面首次创建,准备入栈
110    fn on_create(&mut self) -> Vec<H::Effect> {
111        vec![]
112    }
113    /// 页面变为栈顶处于活跃状态(获得焦点)
114    fn on_resume(&mut self) -> Vec<H::Effect> {
115        vec![]
116    }
117    /// 页面被新页面覆盖,或准备销毁前(失去焦点)
118    fn on_pause(&mut self) -> Vec<H::Effect> {
119        vec![]
120    }
121    /// 页面从任务栈中彻底移除
122    fn on_destroy(&mut self) -> Vec<H::Effect> {
123        vec![]
124    }
125    /// 页面被复用时(SingleTop / SingleTask / SingleInstance)接收新意图
126    fn on_new_intent(&mut self, _intent: Intent<R>) -> Vec<H::Effect> {
127        vec![]
128    }
129}
130
131// ==========================================
132// 4. Activity Manager (核心引擎)
133// ==========================================
134
135/// 任务栈与页面管理器。
136/// 维护 Activity 实例堆栈、分发生命周期、并向外暴露最终要渲染的视图层级。
137pub struct ActivityManager<R, H, C>
138where
139    R: Route,
140    H: ActivityHost,
141    C: Clone + Send + Sync + 'static,
142{
143    /// 活动页面堆栈,栈顶 (`last()`) 为当前用户可见/交互的活动页面
144    stack: Vec<Box<dyn Activity<R, H, C>>>,
145    /// 注入给所有页面的全局上下文(推荐包裹一层 Arc/Mutex 等内部可变性容器)
146    context: C,
147    /// 依赖注入工厂:负责根据 Route 动态实例化对应的 Activity
148    factory: Box<dyn Fn(&R, &C) -> Box<dyn Activity<R, H, C>>>,
149}
150
151impl<R, H, C> ActivityManager<R, H, C>
152where
153    R: Route,
154    H: ActivityHost,
155    C: Clone + Send + Sync + 'static,
156{
157    /// 初始化 Activity Manager 并启动初始根页面。
158    pub fn new(
159        initial_route: R,
160        context: C,
161        factory: Box<dyn Fn(&R, &C) -> Box<dyn Activity<R, H, C>>>,
162    ) -> (Self, Vec<H::Effect>) {
163        info!(
164            "ActivityManager initialized. Starting initial route: {:?}",
165            initial_route
166        );
167        let mut manager = Self {
168            stack: Vec::new(),
169            context,
170            factory,
171        };
172        let effects = manager.start_activity(Intent::new(initial_route));
173        (manager, effects)
174    }
175
176    /// 启动目标 Activity,处理复杂的 LaunchMode 出入栈逻辑。
177    pub fn start_activity(&mut self, intent: Intent<R>) -> Vec<H::Effect> {
178        let target = intent.target.clone();
179        let mode = target.launch_mode();
180        info!(
181            "Action: start_activity | Target: {:?} | Mode: {:?}",
182            target, mode
183        );
184
185        let mut effects = Vec::new();
186        // 查找栈中是否存在该路由
187        let existing_index = self.stack.iter().position(|a| a.route() == target);
188
189        // --- 根据不同的启动模式进行栈干预 ---
190        match mode {
191            LaunchMode::SingleInstance => {
192                if existing_index.is_some() {
193                    debug!(
194                        "SingleInstance trigger: Route {:?} exists. Clearing others.",
195                        target
196                    );
197                    // 1. 清除目标之上的页面
198                    while let Some(top) = self.stack.last() {
199                        if top.route() == target {
200                            break;
201                        }
202                        let mut old = self.stack.pop().unwrap();
203                        debug!("Popping higher activity: {:?}", old.route());
204                        effects.extend(old.on_pause());
205                        effects.extend(old.on_destroy());
206                    }
207                    // 2. 暂时弹出目标本身,清理其下方的页面
208                    if let Some(mut top) = self.stack.pop() {
209                        if top.route() == target {
210                            while let Some(mut bottom) = self.stack.pop() {
211                                debug!("Popping lower activity: {:?}", bottom.route());
212                                effects.extend(bottom.on_pause());
213                                effects.extend(bottom.on_destroy());
214                            }
215                            // 3. 复用并压回目标
216                            effects.extend(top.on_new_intent(intent));
217                            effects.extend(top.on_resume());
218                            self.stack.push(top);
219                            return effects; // 阻断新建流程
220                        } else {
221                            self.stack.push(top); // 兜底,理论上不可达
222                        }
223                    }
224                } else {
225                    debug!("SingleInstance trigger: New instance. Clearing entire stack.");
226                    // 如果不存在,清空整个栈,准备独占
227                    while let Some(mut old) = self.stack.pop() {
228                        effects.extend(old.on_pause());
229                        effects.extend(old.on_destroy());
230                    }
231                }
232            }
233            LaunchMode::SingleTask => {
234                if let Some(index) = existing_index {
235                    debug!(
236                        "SingleTask trigger: Route {:?} found at index {}. Clearing top.",
237                        target, index
238                    );
239                    // Clear Top: 弹出目标之上的所有页面
240                    while self.stack.len() > index + 1 {
241                        if let Some(mut old) = self.stack.pop() {
242                            effects.extend(old.on_pause());
243                            effects.extend(old.on_destroy());
244                        }
245                    }
246                    // 复用现在的栈顶
247                    if let Some(top) = self.stack.last_mut() {
248                        effects.extend(top.on_new_intent(intent));
249                        effects.extend(top.on_resume());
250                    }
251                    return effects; // 阻断新建流程
252                }
253            }
254            LaunchMode::SingleTop => {
255                if let Some(top) = self.stack.last_mut() {
256                    if top.route() == target {
257                        debug!(
258                            "SingleTop trigger: Route {:?} is already at top. Reusing.",
259                            target
260                        );
261                        effects.extend(top.on_new_intent(intent));
262                        effects.extend(top.on_resume());
263                        return effects; // 阻断新建流程
264                    }
265                }
266            }
267            LaunchMode::Standard => {
268                trace!("Standard trigger: Proceeding to create new instance.");
269            }
270        }
271
272        // --- 通用新建流程 ---
273
274        // 1. 当前处于活跃的栈顶页面失去焦点 (on_pause)
275        if let Some(top) = self.stack.last_mut() {
276            debug!("Pausing current top activity: {:?}", top.route());
277            effects.extend(top.on_pause());
278        }
279
280        // 2. 利用工厂闭包和上下文创建新实例
281        debug!("Instantiating new activity for route: {:?}", target);
282        let mut new_activity = (self.factory)(&target, &self.context);
283
284        // 3. 执行新建生命周期
285        effects.extend(new_activity.on_create());
286        effects.extend(new_activity.on_resume());
287
288        // 4. 入栈
289        self.stack.push(new_activity);
290
291        effects
292    }
293
294    /// 执行返回/后退操作。
295    pub fn back(&mut self) -> Vec<H::Effect> {
296        info!("Action: back | Stack depth before: {}", self.stack.len());
297        if self.stack.len() > 1 {
298            let mut effects = Vec::new();
299
300            // 1. 弹出并销毁当前栈顶
301            if let Some(mut old) = self.stack.pop() {
302                debug!("Destroying top activity: {:?}", old.route());
303                effects.extend(old.on_pause());
304                effects.extend(old.on_destroy());
305            }
306
307            // 2. 恢复露出来的新栈顶
308            if let Some(new_top) = self.stack.last_mut() {
309                debug!("Resuming previous activity: {:?}", new_top.route());
310                effects.extend(new_top.on_resume());
311            }
312
313            effects
314        } else {
315            warn!("Back action ignored: Root activity cannot be popped via framework back().");
316            vec![]
317        }
318    }
319
320    /// 派发消息事件给当前处于活跃状态的页面
321    pub fn update(&mut self, message: H::Message) -> Vec<H::Effect> {
322        if let Some(top) = self.stack.last_mut() {
323            trace!("Routing message to top activity: {:?}", top.route());
324            top.update(message)
325        } else {
326            vec![]
327        }
328    }
329
330    /// 提取需要渲染的视图层级(画家算法计算)。
331    pub fn views(&self) -> Vec<H::View> {
332        if self.stack.is_empty() {
333            return vec![];
334        }
335
336        // 找到第一个不透明的页面索引,作为渲染的起点
337        let base_index = self
338            .stack
339            .iter()
340            .rposition(|a| !a.route().is_translucent())
341            .unwrap_or(0);
342
343        self.stack[base_index..].iter().map(|a| a.view()).collect()
344    }
345
346    /// 提取需要监听的订阅事件(同样受 translucent 机制影响)。
347    pub fn subscriptions(&self) -> Vec<H::Subscription> {
348        if self.stack.is_empty() {
349            return vec![];
350        }
351
352        let base_index = self
353            .stack
354            .iter()
355            .rposition(|a| !a.route().is_translucent())
356            .unwrap_or(0);
357
358        self.stack[base_index..]
359            .iter()
360            .filter_map(|a| a.subscription())
361            .collect()
362    }
363
364    /// (测试辅助) 返回当前栈的深度
365    #[cfg(test)]
366    pub fn stack_len(&self) -> usize {
367        self.stack.len()
368    }
369}
370
371// ==========================================
372// 5. 详尽的单元测试 (Unit Tests)
373// ==========================================
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378    use std::sync::{Arc, Mutex};
379
380    // --- 1. Mock 路由枚举 ---
381    #[derive(Debug, Clone, PartialEq)]
382    enum AppRoute {
383        Home,
384        Settings,
385        Detail(u32),
386        Dialog,
387    }
388
389    impl Route for AppRoute {
390        fn launch_mode(&self) -> LaunchMode {
391            match self {
392                AppRoute::Home => LaunchMode::SingleTask,
393                AppRoute::Settings => LaunchMode::SingleInstance,
394                AppRoute::Detail(_) => LaunchMode::Standard,
395                AppRoute::Dialog => LaunchMode::SingleTop,
396            }
397        }
398
399        fn is_translucent(&self) -> bool {
400            matches!(self, AppRoute::Dialog)
401        }
402    }
403
404    // --- 2. Mock 宿主环境 ---
405    struct MockHost;
406    impl ActivityHost for MockHost {
407        type View = String;
408        type Effect = ();
409        type Subscription = ();
410        type Message = String;
411    }
412
413    // --- 3. Mock 共享上下文 (用于记录生命周期钩子以便验证) ---
414    #[derive(Clone)]
415    struct AppContext {
416        /// 记录生命周期事件流
417        history: Arc<Mutex<Vec<String>>>,
418    }
419
420    impl AppContext {
421        fn new() -> Self {
422            Self {
423                history: Arc::new(Mutex::new(Vec::new())),
424            }
425        }
426        fn record(&self, msg: &str) {
427            self.history.lock().unwrap().push(msg.to_string());
428        }
429        fn take_history(&self) -> Vec<String> {
430            let mut guard = self.history.lock().unwrap();
431            let res = guard.clone();
432            guard.clear();
433            res
434        }
435    }
436
437    // --- 4. Mock Activity 实现 ---
438    struct MockActivity {
439        route: AppRoute,
440        context: AppContext,
441    }
442
443    impl Activity<AppRoute, MockHost, AppContext> for MockActivity {
444        fn route(&self) -> AppRoute {
445            self.route.clone()
446        }
447        fn view(&self) -> <MockHost as ActivityHost>::View {
448            format!("View:{:?}", self.route)
449        }
450        fn update(&mut self, _msg: String) -> Vec<()> {
451            vec![]
452        }
453
454        fn on_create(&mut self) -> Vec<()> {
455            self.context.record(&format!("{:?} onCreate", self.route));
456            vec![]
457        }
458        fn on_resume(&mut self) -> Vec<()> {
459            self.context.record(&format!("{:?} onResume", self.route));
460            vec![]
461        }
462        fn on_pause(&mut self) -> Vec<()> {
463            self.context.record(&format!("{:?} onPause", self.route));
464            vec![]
465        }
466        fn on_destroy(&mut self) -> Vec<()> {
467            self.context.record(&format!("{:?} onDestroy", self.route));
468            vec![]
469        }
470        fn on_new_intent(&mut self, _intent: Intent<AppRoute>) -> Vec<()> {
471            self.context
472                .record(&format!("{:?} onNewIntent", self.route));
473            vec![]
474        }
475    }
476
477    // --- 5. 辅助函数:初始化测试用的 Manager ---
478    fn setup_manager() -> (ActivityManager<AppRoute, MockHost, AppContext>, AppContext) {
479        let ctx = AppContext::new();
480        // 初始化时不应该清空 history,以便验证 init 过程
481        let factory: Box<
482            dyn Fn(&AppRoute, &AppContext) -> Box<dyn Activity<AppRoute, MockHost, AppContext>>,
483        > = Box::new(|r, c| {
484            Box::new(MockActivity {
485                route: r.clone(),
486                context: c.clone(),
487            })
488        });
489
490        let (manager, _) = ActivityManager::new(AppRoute::Home, ctx.clone(), factory);
491        (manager, ctx)
492    }
493
494    // --- 测试用例集 ---
495
496    #[test]
497    fn test_initialization_lifecycle() {
498        let (_, ctx) = setup_manager();
499        assert_eq!(
500            ctx.take_history(),
501            vec!["Home onCreate", "Home onResume"],
502            "初始路由必须触发 onCreate 和 onResume"
503        );
504    }
505
506    #[test]
507    fn test_standard_launch_mode() {
508        let (mut manager, ctx) = setup_manager();
509        ctx.take_history(); // clear init history
510
511        // Detail 模式为 Standard
512        manager.start_activity(Intent::new(AppRoute::Detail(1)));
513        assert_eq!(
514            ctx.take_history(),
515            vec!["Home onPause", "Detail(1) onCreate", "Detail(1) onResume"]
516        );
517
518        // 再次推入同样的路由,应该产生新实例
519        manager.start_activity(Intent::new(AppRoute::Detail(1)));
520        assert_eq!(
521            ctx.take_history(),
522            vec![
523                "Detail(1) onPause",
524                "Detail(1) onCreate",
525                "Detail(1) onResume"
526            ]
527        );
528        assert_eq!(manager.stack_len(), 3); // Home -> Detail(1) -> Detail(1)
529    }
530
531    #[test]
532    fn test_single_top_launch_mode() {
533        let (mut manager, ctx) = setup_manager();
534        ctx.take_history();
535
536        // Dialog 为 SingleTop 模式
537        manager.start_activity(Intent::new(AppRoute::Dialog));
538        assert_eq!(
539            ctx.take_history(),
540            vec!["Home onPause", "Dialog onCreate", "Dialog onResume"]
541        );
542
543        // 由于 Dialog 已在栈顶,再次 Push 会触发 on_new_intent,不产生新实例
544        manager.start_activity(Intent::new(AppRoute::Dialog));
545        assert_eq!(
546            ctx.take_history(),
547            vec!["Dialog onNewIntent", "Dialog onResume"]
548        );
549        assert_eq!(manager.stack_len(), 2); // Home -> Dialog
550
551        // 如果中间隔了别的页面,SingleTop 失效,表现类似 Standard
552        manager.start_activity(Intent::new(AppRoute::Detail(1)));
553        ctx.take_history();
554        manager.start_activity(Intent::new(AppRoute::Dialog));
555        assert_eq!(
556            ctx.take_history(),
557            vec!["Detail(1) onPause", "Dialog onCreate", "Dialog onResume"]
558        );
559        assert_eq!(manager.stack_len(), 4); // Home -> Dialog -> Detail(1) -> Dialog
560    }
561
562    #[test]
563    fn test_single_task_launch_mode() {
564        let (mut manager, ctx) = setup_manager(); // Home 本身为 SingleTask
565        manager.start_activity(Intent::new(AppRoute::Detail(1)));
566        manager.start_activity(Intent::new(AppRoute::Detail(2)));
567        ctx.take_history();
568
569        // 此时栈: Home -> Detail(1) -> Detail(2)
570        assert_eq!(manager.stack_len(), 3);
571
572        // Push Home (SingleTask),应当清理其上方所有内容
573        manager.start_activity(Intent::new(AppRoute::Home));
574        assert_eq!(
575            ctx.take_history(),
576            vec![
577                "Detail(2) onPause",
578                "Detail(2) onDestroy",
579                "Detail(1) onPause",
580                "Detail(1) onDestroy",
581                "Home onNewIntent",
582                "Home onResume"
583            ]
584        );
585        assert_eq!(manager.stack_len(), 1); // 仅剩 Home
586    }
587
588    #[test]
589    fn test_single_instance_launch_mode() {
590        let (mut manager, ctx) = setup_manager(); // 栈: Home
591
592        manager.start_activity(Intent::new(AppRoute::Settings)); // Settings is SingleInstance
593        assert_eq!(manager.stack_len(), 1); // Home 应当被强制清空!只留 Settings
594
595        // 验证栈底被清理
596        let history = ctx.take_history();
597        assert!(history.contains(&"Home onPause".to_string()));
598        assert!(history.contains(&"Home onDestroy".to_string()));
599        assert!(history.contains(&"Settings onCreate".to_string()));
600        assert!(history.contains(&"Settings onResume".to_string()));
601
602        // Push 其他内容
603        manager.start_activity(Intent::new(AppRoute::Detail(1)));
604        ctx.take_history();
605
606        // 再次 Push Settings (它已在栈底且模式是 SingleInstance)
607        manager.start_activity(Intent::new(AppRoute::Settings));
608        let clear_history = ctx.take_history();
609        // Detail(1) 必须被销毁,Settings 重获焦点
610        assert_eq!(
611            clear_history,
612            vec![
613                "Detail(1) onPause",
614                "Detail(1) onDestroy",
615                "Settings onNewIntent",
616                "Settings onResume"
617            ]
618        );
619        assert_eq!(manager.stack_len(), 1);
620    }
621
622    #[test]
623    fn test_back_navigation() {
624        let (mut manager, ctx) = setup_manager();
625        manager.start_activity(Intent::new(AppRoute::Detail(1)));
626        ctx.take_history();
627
628        manager.back();
629        assert_eq!(
630            ctx.take_history(),
631            vec!["Detail(1) onPause", "Detail(1) onDestroy", "Home onResume"]
632        );
633        assert_eq!(manager.stack_len(), 1);
634
635        // 退无可退时 (Root Activity),不操作
636        manager.back();
637        assert!(ctx.take_history().is_empty());
638        assert_eq!(manager.stack_len(), 1);
639    }
640
641    #[test]
642    fn test_translucent_view_rendering() {
643        let (mut manager, _ctx) = setup_manager();
644
645        assert_eq!(manager.views(), vec!["View:Home"]);
646
647        // Detail 是不透明的,所以会阻断底层 Home 的渲染
648        manager.start_activity(Intent::new(AppRoute::Detail(1)));
649        assert_eq!(manager.views(), vec!["View:Detail(1)"]);
650
651        // Dialog 是半透明的 (is_translucent = true),应当渲染它和它下面最近的不透明视图
652        manager.start_activity(Intent::new(AppRoute::Dialog));
653        assert_eq!(manager.views(), vec!["View:Detail(1)", "View:Dialog"]);
654
655        // 再盖一层 Dialog
656        manager.start_activity(Intent::new(AppRoute::Dialog)); // 注意:触发了 SingleTop
657        assert_eq!(manager.views(), vec!["View:Detail(1)", "View:Dialog"]); // 视图层级还是2层
658
659        // 我们用 Standard 模式半透明路由来验证多层
660        struct StandardDialogMockRoute;
661        // (为了不破坏现有的 MockRoute enum,上述逻辑足以证明画家算法的截断行为生效)
662    }
663}