Jetpack Navigation -DeepLink(深度链接)

Lsposed中deeplink处理代码

        <activity
            android:name=".ui.activity.MainActivity"
            android:exported="true"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.intent.action.APPLICATION_PREFERENCES" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data
                android:name="android.app.shortcuts"
                android:resource="@xml/shortcuts" />
        </activity>

    private void handleIntent(Intent intent) {
        if (intent == null) {
            return;
        }
        NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
        if (navHostFragment == null) {
            return;
        }
        NavController navController = navHostFragment.getNavController();
        var nav = (NavigationBarView) binding.nav;
        if (intent.getAction() != null && intent.getAction().equals("android.intent.action.APPLICATION_PREFERENCES")) {
            nav.setSelectedItemId(R.id.settings_fragment);
        } else if (ConfigManager.isBinderAlive()) {
            if (!TextUtils.isEmpty(intent.getDataString())) {
                switch (intent.getDataString()) {
                    case "modules" -> nav.setSelectedItemId(R.id.modules_nav);
                    case "logs" -> nav.setSelectedItemId(R.id.logs_fragment);
                    case "repo" -> {
                        if (ConfigManager.isMagiskInstalled()) {
                            nav.setSelectedItemId(R.id.repo_nav);
                        }
                    }
                    case "settings" -> nav.setSelectedItemId(R.id.settings_fragment);
                    default -> {
                        var data = intent.getData();
                        if (data != null && Objects.equals(data.getScheme(), "module")) {
                            navController.navigate(
                                    new Uri.Builder().scheme("lsposed").authority("module").appendQueryParameter("modulePackageName", data.getHost()).appendQueryParameter("moduleUserId", String.valueOf(data.getPort())).build(),
                                    new NavOptions.Builder().setEnterAnim(R.anim.fragment_enter).setExitAnim(R.anim.fragment_exit).setPopEnterAnim(R.anim.fragment_enter_pop).setPopExitAnim(R.anim.fragment_exit_pop).setLaunchSingleTop(true).setPopUpTo(navController.getGraph().getStartDestinationId(), false, true).build());
                        }
                    }
                }
            }
        }
    }

要彻底搞懂 Jetpack Navigation 中的 DeepLink(深度链接),结合 LSPosed 代码场景,从「定义、核心作用、工作原理、配置方式、参数传递、与示例代码代码的关联」等方面做详细拆解,让你既能理解概念,又能对应到实际使用场景。
一、DeepLink 是什么?
DeepLink(深度链接)是 Jetpack Navigation 组件提供的一种 URI 路由机制,允许通过「自定义 URL」直接跳转到应用内的指定页面(Fragment/Activity),而无需从首页层层导航。
可以把它理解为:应用内页面的「专属网址」 —— 就像网页通过 https://xxx.com/detail/123 直接打开文章详情页一样,App 也能通过 lsposed://module?package=xxx 直接打开模块详情页。
核心价值:
外部唤起:其他应用、网页、桌面快捷方式、通知等,通过 URL 直接跳转到 App 内目标页面(比如LsposedManger代码中接收外部 module://xxx URL 跳转模块详情);
内部路由简化:App 内部页面跳转也可通过 URI 统一管理,无需手动传递复杂参数或维护跳转逻辑;
解耦:发起跳转的一方(如外部应用)无需知道目标页面的类名,只需知道 URL 规则,降低耦合。
二、DeepLink 的工作原理
Jetpack Navigation 的 DeepLink 本质是「URI 与页面(Fragment/Activity)的映射关系」,工作流程如下:
配置映射:在导航图(nav_graph.xml)中,给目标页面(如 ModulesFragment)绑定一个或多个 URL 规则(如 lsposed://module?modulePackageName={xxx}&moduleUserId={xxx});
发起跳转:通过 NavController.navigate(URI) 发起跳转(或外部应用通过 Intent(ACTION_VIEW, URI) 唤起 App);
匹配路由:Navigation 组件解析传入的 URI,对比导航图中所有页面的 DeepLink 规则,找到匹配的目标页面;
跳转并传参:导航到目标页面,并自动将 URI 中的参数(如 modulePackageName)传递给目标页面。

三、DeepLink 的 3 种类型(重点关注前两种)
Jetpack Navigation 支持 3 种 DeepLink,对应不同使用场景:
类型 适用场景 配置方式
显式 DeepLink App 内部页面跳转(如示例代码代码中的场景) 在 nav_graph.xml 中通过 <deepLink> 配置
隐式 DeepLink 外部应用 / 网页唤起 App 内页面(需系统注册) 除了导航图配置,还需在 AndroidManifest.xml 中注册 <intent-filter>
PendingIntent DeepLink 通知、快捷方式唤起页面(确保跳转栈正确) 结合 NavDeepLinkBuilder 构建 PendingIntent
示例代码中用到的是 显式 DeepLink(内部跳转),但如果要支持外部应用通过 module://xxx 唤起,还需要配合「隐式 DeepLink」的配置(后面会讲)。

四、DeepLink 核心配置详解(结合你的代码场景)
示例代码中,跳转 URI 是 lsposed://module?modulePackageName=xxx&moduleUserId=xxx,对应的 ModulesFragment必须在导航图(nav_graph.xml)中配置 DeepLink 规则。以下是完整配置步骤和说明:

  1. 导航图(nav_graph.xml)配置(核心)
    导航图是 DeepLink 的「路由表」,所有页面和 URL 的映射都在这里定义。假设 LSPosed 的导航图路径是 res/navigation/nav_graph.xml,配置如下:
<?xml version="1.0" encoding="utf-8"?>
<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/main_fragment"> <!-- 首页 -->

    <!-- 其他页面:首页、模块列表、仓库等 -->
    <fragment
        android:id="@+id/main_fragment"
        android:name="org.lsposed.manager.ui.fragment.MainFragment" />

    <fragment
        android:id="@+id/modules_nav"
        android:name="org.lsposed.manager.ui.fragment.ModulesFragment" />

    <!-- 目标页面:模块详情页 ModulesFragment -->
    <fragment
        android:id="@+id/ModulesFragment"
        android:name="org.lsposed.manager.ui.fragment.ModulesFragment">

        <!-- 关键:DeepLink 规则配置 -->
        <deepLink
            android:id="@+id/deepLink_module_detail"
            <!-- URI 模板:{参数名} 表示动态参数,会从跳转 URI 中提取 -->
            app:uri="lsposed://module?modulePackageName={modulePackageName}&moduleUserId={moduleUserId}"
            <!-- 可选:是否自动将参数写入目标页面的 SavedStateHandle(默认 true) -->
            app:autoVerify="false" /> 

    </fragment>

</navigation>

配置参数说明:
app:uri:DeepLink 的核心规则,支持 3 种匹配模式:
精确匹配:如 lsposed://module(仅匹配该 exact URL);
动态参数匹配:用 {参数名} 表示可变部分,如 {modulePackageName}(匹配任意字符串作为包名);
通配符匹配:用 * 匹配任意路径 / 参数,如 lsposed://module/*(匹配 lsposed://module/xxxlsposed://module/yyy?a=b 等);
app:autoVerify:Android 12+ 新增,用于「应用链接验证」(确保只有你的 App 能响应该 URL,避免被其他 App 劫持),普通 DeepLink 可设为 false。

  1. (可选)外部唤起配置(隐式 DeepLink)
    如果需要支持「外部应用 / 网页通过 module://xxx URL 唤起你的 App 并跳转到模块详情页」,还需要在 AndroidManifest.xml 中给 MainActivity 注册 <intent-filter>,告诉系统「你的 App 能处理 module 协议的 URL」:
<activity
    android:name="org.lsposed.manager.ui.activity.MainActivity"
    android:exported="true"> <!-- 必须设为 exported=true,允许外部唤起 -->

    <!-- 隐式 DeepLink 的 Intent-Filter 配置 -->
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" /> <!-- 允许网页唤起 -->
        <!-- 匹配 module 协议的 URL -->
        <data
            android:scheme="module" <!-- 协议名,对应示例代码中的 data.getScheme() -->
            android:host="*" /> <!-- 匹配任意 host(即模块包名) -->
    </intent-filter>

</activity>

五、参数传递与接收(对应示例代码)
示例代码中,跳转时通过 appendQueryParameter 传递了 modulePackageName 和 moduleUserId 两个参数,目标页面 ModulesFragment如何接收这些参数?

  1. 目标页面接收参数(ModulesFragment中)
    Navigation 会自动将 URI 中的参数存入 Fragment 的 SavedStateHandle(推荐方式),或通过 getArguments() 获取(兼容旧版本):
public class ModulesFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 方式 1:通过 SavedStateHandle 获取(Jetpack 推荐,支持 lifecycle 感知)
        SavedStateHandle handle = Navigation.findNavController(requireView()).getCurrentBackStackEntry().getSavedStateHandle();
        String modulePackageName = handle.get("modulePackageName"); // 对应 URI 中的参数名
        String moduleUserId = handle.get("moduleUserId"); // 注意是字符串类型,需转成 int
        
        // 方式 2:通过 arguments 获取(兼容旧逻辑)
        Bundle args = getArguments();
        if (args != null) {
            String pkg = args.getString("modulePackageName");
            int userId = args.getInt("moduleUserId"); // 自动转换类型
        }
        
        // 后续逻辑:根据包名和用户 ID 加载模块详情(查询模块信息、启用状态等)
    }
}
  1. 示例代码中参数传递逻辑
    原始 URL 是 module://com.example.my.module/0
    data.getHost() → com.example.my.module(作为 modulePackageName 参数);
    data.getPort() → 0(作为 moduleUserId 参数,转成字符串传递);
    转换后的跳转 URI 是 lsposed://module?modulePackageName=com.example.my.module&moduleUserId=0
    Navigation 解析该 URI 后,自动将参数传递给 ModulesFragment。
    六、与你代码的关联总结
    示例代码中的这段逻辑,正是「显式 DeepLink」的典型使用场景:
// 1. 接收外部传入的 module://xxx URL
Uri data = intent.getData();
if (data != null && Objects.equals(data.getScheme(), "module")) {
    // 2. 转换为导航图中配置的 DeepLink URI(lsposed://module?xxx)
    Uri deepLinkUri = new Uri.Builder()
            .scheme("lsposed")
            .authority("module")
            .appendQueryParameter("modulePackageName", data.getHost())
            .appendQueryParameter("moduleUserId", String.valueOf(data.getPort()))
            .build();
    
    // 3. 通过 NavController 发起 DeepLink 跳转,匹配到 ModulesFragment
    navController.navigate(deepLinkUri, navOptions);
}

关键对应关系:
代码部分 DeepLink 概念 作用
lsposed://module?xxx DeepLink URI 规则 匹配导航图中 ModulesFragment的配置
appendQueryParameter 动态参数传递 给目标页面传递模块包名和用户 ID
navController.navigate 发起 DeepLink 跳转 触发页面导航
nav_graph.xml 中的 <deepLink> 路由映射配置 告诉 Navigation 该 URI 对应哪个页面
七、常见问题与注意事项
跳转失败? 检查导航图中 <deepLink> 的 app:uri 规则是否与跳转 URI 完全一致(大小写敏感、参数名一致);
参数接收不到? 确保参数名与 appendQueryParameter 中的键一致,且目标页面在 onCreate 或 onViewCreated 中获取(避免过早获取导致为空);
外部唤起没反应? 检查 AndroidManifest.xml 中是否注册了对应的 <intent-filter>,且 activity 的 exported="true";
重复创建页面? 示例代码中 setLaunchSingleTop(true) 已解决该问题,避免重复创建 ModulesFragment实例。
总结
DeepLink 是 Jetpack Navigation 中用于「URI 路由」的核心功能,核心是「URL 与页面的映射」。示例代码中通过它实现了「外部 URL 唤起 → 转换为内部 DeepLink → 跳转到模块详情页」的完整流程,而导航图配置和参数传递是实现这一流程的关键。
掌握 DeepLink 后,你可以轻松实现:通知跳转详情页、网页唤起 App 内页面、桌面快捷方式直达功能页等常见需求,且逻辑统一、解耦性强。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容