Android Instant App:如何基于基础功能导航抽屉实现Feature的URL Intent跳转
嘿,这个场景我帮不少开发者踩过坑,核心要抓住的一点是:不能让各个Feature直接启动自己的独立Activity,而是要让Base Feature里带导航抽屉的容器Activity来统一处理所有URL Intent,然后把Feature的页面作为内容嵌入进去。这样导航抽屉就能一直保留在界面上了。下面给你一步步拆解实现细节:
Base Feature作为所有模块的共享基础,要承担「导航抽屉宿主+URL入口处理器」的双重角色。所有Feature的页面都以Fragment(而非独立Activity)的形式存在,当用户点击抽屉项或外部触发URL Intent时,由Base的容器Activity接收并加载对应的Feature Fragment,整个过程中导航抽屉始终作为容器的一部分存在。
1. 搭建Base Feature的导航抽屉容器Activity
首先在Base里创建一个带导航抽屉的主Activity,比如BaseDrawerActivity,它是整个App/Instant App的核心入口,负责承载所有Feature的内容。
示例代码(Kotlin)
class BaseDrawerActivity : AppCompatActivity() { private lateinit var drawerLayout: DrawerLayout private lateinit var navView: NavigationView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_base_drawer) // 初始化导航抽屉组件 drawerLayout = findViewById(R.id.drawer_layout) navView = findViewById(R.id.nav_view) // 处理抽屉菜单项点击,触发对应Feature的URL Intent navView.setNavigationItemSelectedListener { menuItem -> when (menuItem.itemId) { R.id.nav_feature1 -> launchFeature("yourapp://feature1") R.id.nav_feature2 -> launchFeature("yourapp://feature2") } drawerLayout.closeDrawer(GravityCompat.START) true } // 处理启动时的URL Intent,加载对应Feature页面 intent?.let { handleIntent(it) } } // 处理新的Intent(比如从后台切回时的深层链接) override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) intent?.let { handleIntent(it) } } private fun launchFeature(url: String) { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) startActivity(intent) } // 解析URL,加载对应的Feature Fragment private fun handleIntent(intent: Intent) { intent.data?.let { uri -> when (uri.host) { "feature1" -> loadFeatureFragment(Feature1Fragment()) "feature2" -> loadFeatureFragment(Feature2Fragment()) } } } private fun loadFeatureFragment(fragment: Fragment) { supportFragmentManager.beginTransaction() .replace(R.id.content_container, fragment) .commit() } }
对应的布局文件(activity_base_drawer.xml)
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!-- 内容容器:所有Feature的Fragment都会在这里替换显示 --> <FrameLayout android:id="@+id/content_container" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- 导航抽屉菜单 --> <com.google.android.material.navigation.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/drawer_menu" /> </androidx.drawerlayout.widget.DrawerLayout>
2. 配置Feature模块的页面为可嵌入的Fragment
每个Feature模块不要创建独立的根Activity,而是提供Fragment作为内容页面,这样Base的容器Activity可以直接加载它们。
比如Feature1模块的Feature1Fragment:
class Feature1Fragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_feature1, container, false) } // 如果需要接收URL参数,可以添加静态创建方法 companion object { fun newInstance(param: String?): Feature1Fragment { val fragment = Feature1Fragment() param?.let { val args = Bundle() args.putString("param_key", it) fragment.arguments = args } return fragment } } }
3. 配置URL Intent过滤规则
在Base Feature的AndroidManifest.xml中,给BaseDrawerActivity添加Intent Filter,让它成为所有Feature URL的处理入口。不要在Feature模块的Manifest中配置独立的Activity Intent Filter,否则会直接启动Feature的Activity,跳过Base的导航抽屉。
<activity android:name=".BaseDrawerActivity" android:exported="true"> <!-- 作为App/Instant App的启动入口 --> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <!-- 处理Feature1的URL --> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="yourapp" android:host="feature1" /> </intent-filter> <!-- 处理Feature2的URL --> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="yourapp" android:host="feature2" /> </intent-filter> </activity>
4. 配置Instant App和完整App模块的依赖
在Instant App模块的build.gradle中,确保依赖Base Feature和所有Feature模块:
dependencies { implementation project(':base') implementation project(':feature1') implementation project(':feature2') }
完整App模块的配置类似,保证所有模块都被正确包含。
5. 处理URL参数传递(可选)
如果需要给Feature传递参数,可以通过URL的Query参数实现,在handleIntent方法中解析:
private fun handleIntent(intent: Intent) { intent.data?.let { uri -> val param = uri.getQueryParameter("item_id") when (uri.host) { "feature1" -> { val fragment = Feature1Fragment.newInstance(param) loadFeatureFragment(fragment) } // 处理其他Feature } } }
- 所有URL Intent必须由Base的容器Activity统一处理,禁止Feature模块的Activity直接接收Intent。
- 用Fragment作为Feature的内容载体,确保可以无缝嵌套在Base的导航抽屉布局中。
- 保证Base Feature是所有模块(Instant App和完整App)的依赖项,确保导航抽屉的代码共享。
内容的提问来源于stack exchange,提问作者user3833749

