现代安卓开发之 Jetpack Compose、Xposed Hook 与 Kotlin Multiplatform

grtsinry43
4/23/2025(更新于 4/23/2025
2 views
预计阅读时长 35 分钟

AI Summary

Powered By DeepSeek-R1
|

emm上来堆三个技术名词太劝退,每个部分开始之前我都会写一段简短的介绍,用很通俗的语言讲解下这是个什么东西。

写这篇文章起源于我最近悲惨的生活和精神状态,因此适当摸摸鱼写写有趣的东西,尤其是写kotlin真的很爽,最近是真的写了很多
wakatime


Jetpack Compose

Jetpack Compose 是 Google 推出的用于构建原生 Android 界面的现代化声明式 UI 工具包。它从传统的命令式 UI(手动查找并更新 View)转变为声明式 UI,说人话就是你现在只需要描述你的UI在不同状态下是什么样子的,至于渲染和更新放心交给Compose就好。另外他完全基于 Kotlin 构建,充分利用了 Kotlin 的语言特性(如 Lambda、DSL、协程等),如果你会Kotlin,上手这个是相当舒服的。

前情提要(bushi):
app

声明式

声明式UI首先是在web前端领域广泛应用的,Jetpack Compose受到启发,将它作为安卓平台的全新UI构建工具,它简化了原命令式UI复杂的操作过程find set等等

函数式

首先它是函数式的,函数是一等公民,而这些描述UI的函数被称为Composable函数,其具有@Composable注解,他们不是返回特定的对象,而是类似“发出UI”,它们之间相互调用可以组成复杂的UI结构

状态驱动

这里就和 React 什么的很像了,当State 对象改变时,Compose 会智能地“重组”(Recomposition)受影响的 Composable 函数,更新 UI。其中mutableStateOf() 创建一个可观察的可变状态持有者,而remember { ... } 用于在重组过程中“记住”状态或对象,避免每次重组都重新创建。

注:remember 本身是为了在多次重组之间保持实例不变,避免重复创建开销。与 mutableStateOf 结合使用 (remember { mutableStateOf(...) }) 才是创建既能被记住又能触发重组的状态的标准方式。

当然,类似于 React/Vue 的 diff,Compose 的优化也是相当好的,当 Composable 函数依赖的状态发生变化时,Compose 运行时会自动重新调用该函数及其可能受影响的子函数,以更新 UI。Compose 会进行智能优化,只重组必要的部分。

副作用和单向数据流

副作用(Side Effects): 我们都知道不会影响状态的叫做纯函数,而改变状态的是副作用函数,实际开发中,还需要处理副作用(如网络请求、数据库操作、启动协程等)。Compose 提供了 LaunchedEffect, SideEffect, DisposableEffect等 API 来安全地处理这些与 Composable 生命周期相关的副作用。

单向数据流 (Unidirectional Data Flow - UDF): Compose 强烈推荐遵循 UDF 模式:状态向下流动(父组件传给子组件),事件向上传递(子组件通过回调通知父组件)。这有助于构建更可预测、更易于维护的 UI。

浅浅上手

在之前摸鱼的时候我写了个小小简陋的app:传送门

我们就以这个为例来快速上手 Jetpack Compose

as

我们以这个卡片组件为例,如你所见,在Android Studio中,工作区分为两部分,代码和Compose Preview,你可以通过添加@Preview注解来启动对应组件的预览,当然也可以添加主题来查看对应主题的效果,比如默认的Material3

kotlin
1@Preview(showBackground = true)
2@Composable
3fun ArticleCardPreview() {
4    MaterialTheme{
5        // 你的组件
6    }
7}

我们重心还是回到上面的Composable函数上:

它从外向内分别是Box Row Column,类似三层flex div,modifier是样式修饰符,类比css,而里面也是由一个个封装好的Composable函数嵌套而成的(其实就是调用函数,组合UI)

我们可以根据传入的参数描述不同的UI,进而渲染不同的元素,例如:

kotlin
1// AsyncImage 加载封面
2            if (!cover.isNullOrEmpty()) {
3                // 构建完整URL
4                val fullImageUrl = if (cover.startsWith("http")) cover else "$baseUrl$cover"
5
6                AsyncImage(
7                    model = ImageRequest.Builder(LocalContext.current)
8                        .data(fullImageUrl)
9                        .crossfade(true)
10                        .build(),

当涉及到副作用时,我们可以以简单的网络加载为例

kotlin
1@Composable
2fun ArticlesScreen(navController: NavController = rememberNavController()) {
3    // 获取LocalContext
4    val context = LocalContext.current
5
6    // 创建ViewModel工厂
7    val factory = remember {
8        object : ViewModelProvider.Factory {
9            override fun <T : ViewModel> create(modelClass: Class<T>): T {
10                return ArticleViewModel(context.applicationContext as Application) as T

注:这里我手动创建了 ViewModel Factory 来传递 Application Context,但在实际项目中,通常会使用 Hilt 或 Koin 等依赖注入库来简化这个过程。别感觉复杂,koin很简单的。

当然,想要深入使用还是得先浅浅学学Android中Activity的生命周期,这样才能会用ViewModel和各个Effect,不过这里只是简单介绍啦,它的思想和前端框架还是蛮像的。

Xposed Hooks

这个如果是搞机大佬肯定不会陌生,它是 一个运行于 Android 系统底层的框架,允许用户和开发者在不修改应用程序 APK 文件的情况下,实时地修改(“Hook”)系统和应用程序的行为,当然现在用的更多的还是LSPosed(老**框架

Hook意为钩子,可以理解为钩住对应的函数,拦截下来,进而完成你想要的操作,它在应用程序启动时加载 Xposed Bridge,从而获得在 Zygote 进程中 Hook 任意 Java 方法的能力。

emm还是一个摸鱼时候随便写的小项目:传送门

作者自评:我看你小子没少摸鱼

核心实践

我们首先要确定一个模块入口类,让他继承IXposedHookLoadPackage,这样我们就有了修改程序的能力。

代码结构

在这个重载方法里,我们可以针对加载模块做一些自定义,比如因为我后续需要广播信息,这里就获取了一下context。

然后hook的方法就是这样,提供具体的类,方法名,随后我们可以自定义自己的前钩子函数和后钩子函数。 为了便于调试,我们可以添加一些日志。

kotlin
1// 2. Hook 这个特定类的 onCreate 方法
2XposedHelpers.findAndHookMethod(
3    appClass, // *** 修改点:使用找到的特定类 appClass ***,真是被气炸
4    "onCreate",
5    object : XC_MethodHook() {
6        override fun beforeHookedMethod(param: MethodHookParam?) {
7            XposedBridge.log("[justforfun] Custom App ($TARGET_APPLICATION_CLASS_NAME) onCreate: BEFORE")
8        }
9
10        override fun afterHookedMethod(param: MethodHookParam) {

这里有一个param,我们可以通过它的args(Arr)和result(Object)来拿到所hook方法的参数和结果,整体效果大概这样:

kotlin
1package com.example.myxposedmodule // 替换成你的包名
2
3import de.robv.android.xposed.IXposedHookLoadPackage
4import de.robv.android.xposed.XC_MethodHook
5import de.robv.android.xposed.XposedBridge
6import de.robv.android.xposed.XposedHelpers
7import de.robv.android.xposed.callbacks.XC_LoadPackage
8
9class HookEntry : IXposedHookLoadPackage {
10

完整简要流程

当然,为了大家复现,我也贴了一段由AI生成的创建模块的讲解

开发流程:

  1. 环境准备:

    • IDE: Android Studio (最新稳定版推荐)。
    • 构建系统: Gradle (Android Studio 内置)。
    • 编程语言: Java 或 Kotlin (Kotlin 因其简洁性和现代特性更受欢迎)。
    • LSPosed API: 需要在项目中引入 LSPosed 提供的 API 库。
    • 测试设备:
      • 一台已 Root 的 Android 设备或模拟器。
      • 已安装 Magisk (用于 Zygisk) 或 Riru (较旧)。
      • 已安装 LSPosed Manager (通过 Magisk Manager 或 Riru 安装 LSPosed Zygisk/Riru 版本,然后安装管理器 APK)。
  2. 创建 Android 项目:

    • 在 Android Studio 中,选择 "File" -> "New" -> "New Project..."。
    • 选择一个模板,通常 "Empty Activity" 或 "No Activity" 都可以,因为模块本身不一定需要界面 (除非你想提供配置界面)。
    • 配置项目名称、包名、保存位置、语言 (Java/Kotlin) 和最低 SDK 版本。最低 SDK 通常可以设置得较低 (如 API 21 或更高),但要确保你的代码兼容。
  3. 配置项目依赖和 Manifest:

    • 添加 LSPosed API 依赖:

      • 打开项目级别的 build.gradlebuild.gradle.kts 文件 (通常是 app 模块下的那个)。
      • dependencies 代码块中添加 LSPosed API 依赖。关键: 使用 compileOnlyprovided 作用域,因为这个 API 在运行时由 LSPosed 框架提供,不需要打包进你的模块 APK 中。

      Gradle

      kotlin
      1// build.gradle (Groovy)
      2dependencies {
      3    // ... 其他依赖
      4    compileOnly 'de.robv.android.xposed:api:82' // 使用官方 Xposed API 版本号 82
      5    // 或者,如果你想用 LSPosed 提供的包含一些额外帮助类的 API (不常用,一般用官方 82 就行)
      6    // compileOnly 'org.lsposed.lsparanoid:lsparanoid:+' // '+ ' 会获取最新版,建议指定版本
      7}
      8
      9// build.gradle.kts (Kotlin)
      10dependencies {
      • 注意: API 版本 82 是 Xposed 原始 API 的最后一个正式版本,LSPosed 完全兼容它,通常使用这个即可。

        提示:这里别忘加源

        kotlin
        1dependencyResolutionManagement {
        2    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        3    repositories {
        4        google()
        5        mavenCentral()
        6
        7        maven { url = uri("https://api.xposed.info/") }
        8    }
        9}
    • 配置 AndroidManifest.xml:

      • 打开 app/src/main/AndroidManifest.xml
      • <application> 标签内添加以下 <meta-data> 标签,以声明这是一个 LSPosed 模块:

      XML

      xml
      1<application ...>
      2    <meta-data
      3        android:name="lsposedmodule"
      4        android:value="true" />
      5    <meta-data
      6        android:name="lsposeddescription"
      7        android:value="这里写模块的描述,会显示在 LSPosed 管理器中" />
      8    <meta-data
      9        android:name="lsposedminversion"
      10        android:value="1" /> <activity .../>
  4. 分析目标应用和寻找 Hook 点:

    • 确定目标: 你想修改哪个应用或系统功能的行为?
    • 反编译: 使用反编译工具 (如 JADX-GUI) 打开目标应用的 APK 文件。
    • 代码分析: 浏览反编译后的 Java 代码,找到你想要修改的功能对应的类和方法。记下完整的类名、方法名、参数类型和返回类型。
    • 注意事项:
      • 目标应用可能经过混淆 (ProGuard/R8),类名和方法名可能变成无意义的字母 (如 a.b.c)。这会增加定位难度,并且 Hook 点可能在应用更新后失效。
      • 优先寻找逻辑清晰、不易变动的方法进行 Hook。
      • 注意方法的访问修饰符 (public, private, protected, package-private)。
  5. 编写 Hook 代码:

    • 创建 Hook 入口类: 创建一个新的 Java 或 Kotlin 类,实现 de.robv.android.xposed.IXposedHookLoadPackage 接口。这个接口只有一个方法需要实现:handleLoadPackage

    • 实现 handleLoadPackage 方法:

      这个方法会在每个应用加载时被 LSPosed 调用。

      • 过滤目标应用: 在方法内部,首先检查 LoadPackageParam (通常命名为 lpparam) 的 packageName 字段,判断当前加载的是否是你的目标应用。如果不是,直接 return

      • 执行 Hook: 如果是目标应用,使用 de.robv.android.xposed.XposedHelpers 类提供的静态方法来查找并 Hook 目标方法。最常用的是 findAndHookMethod

      • XC_MethodHook 回调:findAndHookMethod 的最后一个参数是一个XC_MethodHook的匿名内部类 (或 Lambda 表达式) 实例。你需要重写它的beforeHookedMethod和/或afterHookedMethod方法。

        • beforeHookedMethod(MethodHookParam param): 在原始方法执行 之前 调用。你可以读取/修改方法的输入参数 (param.args),或者直接阻止原始方法执行并设置返回值/抛出异常 (param.setResult(), param.setThrowable())。
        • afterHookedMethod(MethodHookParam param): 在原始方法执行 之后 调用。你可以读取/修改方法的返回值 (param.getResult(), param.setResult()),或者读取原始方法的执行结果和参数,然后执行其他操作。

Kotlin Multiplatform

Kotlin Multiplatform (以前称为 Kotlin Multiplatform Mobile 或 KMM,现在范围更广) 是 JetBrains 提供的一种技术,允许开发者使用 Kotlin 编写代码,并将其共享到多个平台,如 Android, iOS, Web (JS), Desktop (JVM, Native), Server (JVM) 等。

简单来说就是逻辑代码一次编写处处运行,尽管现在 Compose Multiplatform 正在探索跨平台 UI,但是目前还不是production ready。kmp还主要是共享业务逻辑、数据层、网络请求、数据模型等非 UI 代码。

它大概是这样的:

  • Common Code (commonMain): 编写平台无关的 Kotlin 代码。

  • Platform-Specific Code (androidMain, iosMain, jsMain 等): 编写需要调用特定平台 API 的代码。

  • expect/actual 机制:commonMain 中声明预期的功能 (expect class/function/property),然后在每个平台源集 (androidMain, iosMain 等) 中提供具体的实现 (actual class/function/property)。

    kotlin
    1// commonMain
    2expect fun getPlatformName(): String
    3
    4// androidMain
    5actual fun getPlatformName(): String = "Android"
    6
    7// iosMain
    8actual fun getPlatformName(): String = "iOS"

其实就是能共用的就共用,平台有关的就通过expect/actual在各个平台分别实现。

~~当然这个我也摸鱼...~~额,是有的,只不过这个想认真写写,写的差不多再开源。

emm这个怎么说,学习曲线有点陡,因为得各个平台开发比较熟悉,整体是:

  • 编写 Common Logic:commonMain 中用纯 Kotlin 编写大部分共享逻辑非常顺畅,尤其是利用 Kotlin Coroutines 处理异步、Serialization 处理 JSON 等,共用的网络库,拦截管道和错误处理,共用的viewstate什么的真的非常爽

  • 处理平台差异 (expect/actual): 对于需要平台 API 的功能(如文件系统访问、特定硬件交互、日期格式化等),使用 expect/actual 模式。这部分需要同时理解 Kotlin 和目标平台的 API。当平台差异很大时,actual 实现可能会变得复杂,这个就得每个平台都得会了。

还有,kotlin和swift互操作是真的难受

这里先不贴代码了,因为我感觉自己写的不是很行啊@_@

简要说说

好了,这次的“摸鱼心得”就先到这里。(误)

从 Jetpack Compose 带来的 UI 编写新范式,到 Xposed (LSPosed) 赋予我们的“魔改”能力,再到 Kotlin Multiplatform 对“一次编写,多端运行”的探索,不难看出安卓(以及更广阔的移动开发领域)正经历着快速的演进。

这三者或许代表了现代安卓开发的不同切面:面向未来的 UI 构建深入底层的系统定制、以及跨平台代码复用的探索。它们都基于 Kotlin 的强大能力。对我来说,折腾这些新技术,也确是“悲惨生活”中的一点短暂休息了。

讲的不深,简单说说,希望这篇小文章让你对这些技术产生兴趣,那就下次摸鱼再见吧!

COPYRIGHT
作者grtsinry43
版权年份© 2025
许可协议

现代安卓开发之 Jetpack Compose、Xposed Hook 与 Kotlin Multiplatform》采用知识共享署名 4.0 国际许可协议

转载请注明出处并遵循 CC BY 许可协议条款

相关推荐

使用 pf4j-spring 实现插件注入和 api 接口动态注册 | 插件系统构建(上)

使用 pf4j-spring 实现插件注入和 api 接口动态注册 | 插件系统构建(上)

哪个男孩不想拥有一个自己的插件系统?(x)话说回来,这个我已经计划好久了,不过一直在学其他的东西,刚...

grtsinry43
1/26/2025
581
0
5

使用 Spring Boot + MyBatisPlus 提高效率,简化开发

最近一段时间,由于一些项目的需求,于是被迫用很快的速度学完了 Spring Framework,...

grtsinry43
8/11/2024
125
0
0
使用 BeautifulSoup 配合请求库实现简单的爬虫程序

使用 BeautifulSoup 配合请求库实现简单的爬虫程序

事情起源于课内的课程实验作业...因为要求要用爬虫,~~不必说课内讲的一言难尽,更不必说就算讲了我也...

grtsinry43
1/7/2025
175
0
1

折腾记录|使用 Nuxt.js 重写个人主页,使用 SSR 优化 SEO ,实现一些期待已久的效果

在 22 年刚创建个人主页的时候,由于我的技术水平不够,只能用一些 wordpress type...

grtsinry43
9/19/2024
233
0
1
用一个月的时间写一个自己的博客系统——Grtblog的技术介绍

用一个月的时间写一个自己的博客系统——Grtblog的技术介绍

终于,历时一个多月的开发 ~~bug~~ 和测试,这个目前问题很多很不成熟很难用的系统终于上线了.....

grtsinry43
12/14/2024
1151
9
9
COMMENT 7320820876801347584

发表评论

来这里畅所欲言吧!
支持 Markdown 语法 0 / 3000

网站运行时间

0
0
0
0

在风雨飘摇之中

感谢陪伴与支持

愿我们不负热爱,继续前行