Router — kiến thức cơ bản về mẫu định tuyến trong iOS và Android

Tác giả: IT Sectr Đã đăng: 2026-02-19 Thời gian đọc: 9 phút

Router (bộ định tuyến) — một mẫu kiến trúc tập trung hóa logic điều hướng giữa các màn hình ứng dụng. Router xác định điều hướng đến đâu và như thế nào khi một sự kiện xảy ra. Trong iOS, Router trừu tượng hóa UINavigationController và segue; trong Android — FragmentManager và NavController từ Jetpack Navigation. Router có thể hoạt động như một mẫu độc lập hoặc như một thành phần của Coordinator (Router-per-Coordinator) và VIPER (Router-per-module). Chi tiết thêm — trong Android Navigation Component Guide.

Những điểm chính

  • Router — mẫu định tuyến tập trung hóa điều hướng giữa các màn hình
  • Hai cấp độ — Router có thể là thành phần của Coordinator hoặc mẫu độc lập
  • iOS Router — trừu tượng hóa trên UINavigationController với các phương thức push/present/pop
  • Android Router — Jetpack Navigation Component với NavController và deep links
  • URL Routing — định tuyến dựa trên lược đồ URL (deeplink, universal link)

Router là gì: bản chất của mẫu định tuyến

Router — một mẫu đóng gói logic chuyển đổi giữa các màn hình. Ý tưởng chính: ViewController (hoặc Presenter/ViewModel) không gọi trực tiếp navigationController.pushViewController, mà thông báo cho Router về ý định điều hướng. Router quyết định ViewController nào sẽ tạo và cách hiển thị nó. Router có thể đơn giản (một tập hợp các phương thức điều hướng) hoặc phức tạp (với định tuyến lược đồ URL, deep links và thông báo push).

Ba cách tiếp cận Router — Router như dịch vụ (Navigator singleton toàn cục), Router như thành phần mô-đun (cách tiếp cận VIPER), Router như giao thức (trừu tượng hóa cho Coordinator). Router toàn cục — đơn giản (một phiên bản cho mỗi ứng dụng) — phổ biến trong các dự án nhỏ. Router mô-đun — mỗi tính năng có Router riêng — tiêu chuẩn VIPER và Clean Architecture. Router giao thức — được sử dụng trong Coordinator: RouterProtocol với các phương thức push/present/pop/dismiss.

Cách tiếp cậnCấu trúcKhi nào sử dụng
Navigator toàn cụcSingleton với các phương thức điều hướngDự án nhỏ, nguyên mẫu
Router mô-đunRouter bên trong mỗi mô-đunVIPER, Clean Architecture
Router giao thứcGiao thức + triển khai cho CoordinatorMVVM-C, Coordinator pattern

Lịch sử của mẫu — Router xuất hiện trong các framework web (URL Router — Rails Routes, Express.js) và được điều chỉnh cho phát triển di động. Trong iOS, Router trở nên phổ biến với sự phát triển của VIPER (2015-2017). Trong Android, Router là một phần của Jetpack Navigation (2018), giải pháp điều hướng tiêu chuẩn của Google. Ngày nay, Router là một phần không thể thiếu của bất kỳ kiến trúc ứng dụng di động mô-đun nào.

Router trong iOS: triển khai bằng Swift

iOS Router — một giao thức với các phương thức cho nhiều loại điều hướng khác nhau. Triển khai cơ bản bao bọc UINavigationController. Router có thể xử lý các trình bày phương thức (present/dismiss), push/pop trong ngăn xếp điều hướng, showDetail cho Split View Controller và trình bày toàn màn hình (iOS 13+). Router không tạo ViewControllers — nó nhận một phiên bản sẵn sàng và hiển thị nó. Việc tạo ViewController được xử lý bởi một nhà máy (Assembly, vùng chứa DI) hoặc Coordinator.

swift
// RouterProtocol cho iOS
protocol RouterProtocol {
    func push(_ viewController: UIViewController, animated: Bool)
    func pop(animated: Bool)
    func popToRoot(animated: Bool)
    func present(_ viewController: UIViewController, animated: Bool)
    func dismiss(animated: Bool)
    func setViewControllers(_: [UIViewController], animated: Bool)
}

// Triển khai Router qua UINavigationController
class NavigationRouter: RouterProtocol {
    private let navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func push(_ vc: UIViewController, animated: Bool) {
        if navigationController.presentedViewController {
            navigationController.dismiss(animated: false)
        }
        navigationController.pushViewController(vc, animated: animated)
    }

    func pop(animated: Bool) {
        navigationController.popViewController(animated: animated)
    }

    func popToRoot(animated: Bool) {
        navigationController.popToRootViewController(animated: animated)
    }

    func present(_ vc: UIViewController, animated: Bool) {
        navigationController.present(vc, animated: animated)
    }

    func dismiss(animated: Bool) {
        navigationController.dismiss(animated: animated)
    }

    func setViewControllers(_ vcs: [UIViewController], animated: Bool) {
        navigationController.setViewControllers(vcs, animated: animated)
    }
}

// Assembly — tạo mô-đun với Router
protocol ProfileModuleFactory {
    func makeProfileView() -> UIViewController
}

Router trong VIPER — mỗi mô-đun VIPER có Router riêng, biết màn hình nào cần mở từ mô-đun hiện tại. Router tạo ViewController của mô-đun tiếp theo thông qua một nhà máy và chuyển quyền điều khiển. Router trong VIPER là thành phần duy nhất chứa import UIKit (trừ View). Điều này đơn giản hóa việc kiểm thử Presenter và Interactor: chúng không phụ thuộc vào UIKit.

Router trong Android: Jetpack Navigation Component

Jetpack Navigation Component — bộ định tuyến tiêu chuẩn cho Android, được Google khuyến nghị từ năm 2018. Điều hướng được định nghĩa trong một đồ thị XML (nav_graph.xml): các nút (fragments, activities, destinations) và các cạnh (actions). NavController quản lý các chuyển đổi: gọi FragmentManager.replace/add và quản lý ngăn xếp quay lại. Navigation Component hỗ trợ deep links, đối số, hoạt ảnh chuyển tiếp và Safe Args để truyền dữ liệu type-safe.

kotlin
// nav_graph.xml — đồ thị điều hướng
<!-- @xml/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/loginFragment">

    <fragment android:id="@+id/loginFragment"
        android:name=".ui.login.LoginFragment">
        <action android:id="@+id/to_home"
            app:destination="@id/homeFragment" />
    </fragment>

    <fragment android:id="@+id/homeFragment"
        android:name=".ui.home.HomeFragment">
        <argument android:name="userId"
            android:defaultValue="0"
            app:argType="integer" />
        <deepLink app:uri="myapp://home/{userId}" />
    </fragment>
</navigation>

// Kotlin — điều hướng qua NavController
class LoginFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.loginButton.setOnClickListener {
            val action = LoginFragmentDirections.toHome(userId = 42)
            findNavController().navigate(action)
        }
    }
}

// Safe Args — đối số type-safe
class HomeFragmentArgs : NavArgs {
    val userId: Int get() = arguments?.getInt("userId") ?: 0
}

// NavigationUI — tích hợp với Toolbar, BottomNav, Drawer
class MainActivity : AppCompatActivity() {
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val navHost = supportFragmentManager
            .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        navController = navHost.navController
        NavigationUI.setupActionBarWithNavController(this, navController)
    }
}

Deep links trong Android Navigation — Navigation Component hỗ trợ deep links rõ ràng (PendingIntent + NavDeepLinkBuilder) và ngầm định (thông qua intent-filter trong tệp kê khai). Một deep link có thể dẫn đến bất kỳ nút nào của nav_graph. Xử lý thông báo push: Notification tạo PendingIntent với NavDeepLinkBuilder, khôi phục ngăn xếp quay lại và mở màn hình mong muốn. Navigation Component tự động xử lý các nút Up/Back và đưa người dùng trở lại màn hình trước đó.

URL Routing: định tuyến Deeplink và Universal Link

URL Routing — định tuyến dựa trên lược đồ URL, deep links và universal links. Ứng dụng đăng ký một lược đồ (myapp://profile/42) hoặc universal link (https://example.com/profile/42). Router phân tích cú pháp URL, trích xuất tham số (userId=42) và mở màn hình tương ứng. URL Routing cho phép mở ứng dụng từ bên ngoài: qua liên kết từ trang web, email, thông báo push, mã QR. iOS sử dụng NSUserActivity + universal link, Android — intent-filter + deep link.

swift
// URL Router — phân tích cú pháp deep link và điều hướng
protocol URLRoute {
    var pattern: String { get }
    func navigate(parameters: [String: String], router: RouterProtocol)
}

struct ProfileRoute: URLRoute {
    let pattern = "myapp://profile/{userId}"

    func navigate(parameters: [String: String], router: RouterProtocol) {
        guard let userId = parameters["userId"] else { return }
        let profileVC = ProfileViewController(userId: userId)
        router.push(profileVC, animated: true)
    }
}

// URL Router — ánh xạ URL với các tuyến đường
class URLRouter {
    private var routes: [URLRoute] = []

    func handle(_ url: URL, router: RouterProtocol) {
        for route in routes {
            if let params = matchPattern(route.pattern, url: url) {
                route.navigate(parameters: params, router: router)
                return
            }
        }
    }
}

Định tuyến deep link trong thực tế — ứng dụng phải xử lý deep links ở ba trạng thái: chưa chạy (khởi chạy với deep link), trong nền (khôi phục từ nền), đang hoạt động (onNewIntent/SceneDelegate). URL Router phải khôi phục chính xác ngăn xếp quay lại: khi deep link đến màn hình hồ sơ, người dùng phải có thể nhấn Quay lại và trở về màn hình trước đó. iOS và Android xử lý ngăn xếp quay lại khác nhau — Router phải tính đến các đặc điểm riêng của từng nền tảng.

So sánh Router với Coordinator và Navigator

Router vs Coordinator — Router trả lời câu hỏi "làm thế nào để hiển thị màn hình?" (push/present), Coordinator — "hiển thị màn hình nào?" (luồng nào để bắt đầu). Router là công cụ điều hướng, Coordinator là người tổ chức luồng. Coordinator sử dụng Router. Trong VIPER, Router thực hiện cả hai chức năng: quyết định đi đâu và hiển thị như thế nào. Trong MVVM-C, Router là một giao thức riêng biệt được Coordinator sử dụng. Các mẫu không loại trừ nhau mà bổ sung cho nhau.

Đặc điểmRouterCoordinatorNavigator
Trọng tâmCơ chế điều hướngLuồng điều hướngTruy cập toàn cục
Cấp độFramework UI (push/present)Logic kinh doanh (luồng)Cơ sở hạ tầng
Tạo màn hìnhNhận VC sẵn sàngTạo VC qua nhà máyTạo VC
Vòng đờiMột cho mỗi điều hướngchildCoordinatorsSingleton

Router vs Navigator — Navigator là tên cũ hơn của Router trong cộng đồng iOS. Trong các triển khai ban đầu, Navigator là một lớp Singleton với các phương thức điều hướng. Navigator là một Router singleton toàn cục. Navigator đơn giản hơn nhưng tạo ra các phụ thuộc ẩn. Router dựa trên giao thức có thể kiểm thử và mô-đun hơn. Navigator phù hợp cho các dự án không có kiến trúc chặt chẽ, Router — cho Clean Architecture và VIPER.

Các câu hỏi thường gặp

Sự khác biệt giữa Router và Coordinator là gì?

Router là một sự trừu tượng hóa trên UINavigationController (push/present/pop). Coordinator là một lớp trên Router quản lý luồng (màn hình nào sẽ hiển thị tiếp theo). Router xử lý cơ chế, Coordinator xử lý logic điều hướng. Coordinator sử dụng Router để thực hiện các chuyển đổi. Trong VIPER, Router kết hợp cả hai chức năng; trong MVVM-C, chúng được tách biệt.

Có bắt buộc phải sử dụng Router trong ứng dụng di động không?

Không. Đối với ứng dụng có 3-5 màn hình, gọi trực tiếp navigationController.pushViewController là một giải pháp hoàn toàn chấp nhận được. Router trở nên hữu ích với 10+ màn hình, deep links, thông báo push và kiến trúc mô-đun. Router đơn giản hóa việc kiểm thử điều hướng và cho phép xử lý tập trung các chuyển đổi từ bất kỳ nguồn nào (nút bấm, deep link, thông báo push).

Router liên quan đến Deep Links như thế nào?

URL Router là một phần mở rộng của Router phân tích cú pháp deep links và universal links. URL Router xác định ánh xạ giữa URL và màn hình ứng dụng. Ví dụ: tuyến đường myapp://profile/42 → ProfileViewController(userId: 42). URL Router tập trung hóa việc xử lý deep links từ các nguồn khác nhau: thông báo push, mã QR, liên kết email, liên kết phổ quát.

Có thể sử dụng Router mà không có Coordinator không?

Có. Router có thể hoạt động độc lập — như một Navigator toàn cục hoặc như một thành phần mô-đun VIPER. Router không có Coordinator chỉ đơn giản là đóng gói UINavigationController đằng sau một giao thức. Coordinator không có Router phức tạp hơn (ViewController gọi push/present trực tiếp), nhưng có thể. Thực hành tốt nhất là sử dụng Router + Coordinator cùng nhau.

Router trong Android có giống với iOS Router không?

Về mặt chức năng — có, cả hai đều tập trung hóa điều hướng. Triển khai khác nhau: iOS Router — giao thức + lớp bao quanh UINavigationController; Android — Jetpack Navigation Component với đồ thị XML và NavController. Android Router được tích hợp vào framework (Navigation Component), iOS Router là triển khai của bên thứ ba. Android Router hỗ trợ Safe Args, iOS Router thì không (cần generics hoặc Codable).

Tổng kết

  • Router — mẫu định tuyến đóng gói logic chuyển đổi giữa các màn hình
  • iOS Router — giao thức với các phương thức push/present/pop bao quanh UINavigationController
  • Android Router — Jetpack Navigation Component với XML nav_graph và NavController
  • URL Routing — định tuyến qua deep links và universal links
  • Router vs Coordinator — Router xử lý cơ chế, Coordinator xử lý luồng điều hướng
  • Ứng dụng — kiến trúc mô-đun, deep links, kiểm thử điều hướng

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm