{"id":20592070,"url":"https://github.com/bennyhuo/tieguanyin","last_synced_at":"2025-10-26T23:37:58.002Z","repository":{"id":57717770,"uuid":"119345142","full_name":"bennyhuo/TieGuanYin","owner":"bennyhuo","description":"Activity Builder.","archived":false,"fork":false,"pushed_at":"2022-06-02T02:56:41.000Z","size":935,"stargazers_count":185,"open_issues_count":0,"forks_count":26,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-05-08T21:13:10.251Z","etag":null,"topics":["activity","annotations","builder","fragment","fragments","router"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bennyhuo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-29T07:05:54.000Z","updated_at":"2025-03-23T14:09:13.000Z","dependencies_parsed_at":"2022-09-26T21:40:47.693Z","dependency_job_id":null,"html_url":"https://github.com/bennyhuo/TieGuanYin","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bennyhuo%2FTieGuanYin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bennyhuo%2FTieGuanYin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bennyhuo%2FTieGuanYin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bennyhuo%2FTieGuanYin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bennyhuo","download_url":"https://codeload.github.com/bennyhuo/TieGuanYin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253149617,"owners_count":21861739,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activity","annotations","builder","fragment","fragments","router"],"created_at":"2024-11-16T07:42:41.545Z","updated_at":"2025-10-26T23:37:52.953Z","avatar_url":"https://github.com/bennyhuo.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tieguanyin(铁观音)\n\n## 项目是做什么的？\n\n### 我们遇到了怎样的问题\n\n我们先来看个例子：\n\n```java\npublic class UserActivity extends Activity {\n\n    String name;\n    int age;\n    String title;\n    String company;\n    \n    ...\n}\n```\n我们有这样一个 `Activity`，启动它，我们需要传入四个参数，那么我们通常会怎么做呢？\n\n```java\nIntent intent = new Intent(this, UserActivity.class);\nintent.putExtra(\"age\", age);\nintent.putExtra(\"name\", name);\nintent.putExtra(\"company\", company);\nintent.putExtra(\"title\", title);\nstartActivity(intent);\n```\n\n仅仅是这样，还不够，所以我们还需要在 `UserActivity` 这个类当中去读取这些值：\n\n```java\nIntent intent = getIntent();\nthis.age = intent.getIntExtra( \"age\", 0);\nthis.name = intent.getStringExtra(\"name\");\nthis.company = intent.getStringExtra(\"company\" );\nthis.title = intent.getStringExtra(\"title\");\n```\n\n如果你只有这么一个 `Activity` 那倒也还好，可是如果你有十个这样的 `Activity` 呢？\n\n### 我们怎么去解决\n\n其实我们仔细观察前面的代码，就会发现这两大段传参和读参的代码，都是模式化的代码，我们只需要通过注解处理器来生成就可以了，因此我们给出的解决方法是：\n\n```java\n@Builder\npublic class UserActivity extends Activity {\n\n    @Required\n    String name;\n\n    @Required\n    int age;\n\n    @Optional\n    String title;\n\n    @Optional\n    String company;\n    \n    ...\n}\n```\n\n这样的话，对于 Java 代码，我们会生成 `UserActivityBuilder`，通过它启动 `UserActivity`：\n\n```java\nUserActivityBuilder.builder(30, \"bennyhuo\")\n        .company(\"Kotliner\")\n        .title(\"Kotlin Developer\")\n        .start(this);\n```\n注意到，我们的 `name` 和 `age` 都是 `Required`，因此我们生成的 Builder 在构造时必须对他们进行赋值，而其他两个因为是 `Optional`，用户可以根据实际情况选择性调用。\n\n而对于 Kotlin 来说，我们则选择为 `Context`、`View`、`Fragment` 生成扩展方法，所以我们只需要：\n\n```kotlin\nstartUserActivity(30, \"bennyhuo\", \"Kotliner\", \"Kotlin Developer\")\n```\n\n需要注意的是，对于 `company` 和 `title` 这两个可选的字段，我们的扩展方法提供了默认参数 `null`，因此我们可以选择性提供这些参数的值：\n\n```kotlin\nstartUserActivity(30, \"bennyhuo\",  title = \"Kotlin Developer\")\n```\n\n这些方便快捷的方法帮我们处理了 `Intent` 传递参数的过程，当然，我们也在运行时对 `Activity` 的声明周期进行了监听，在 `Activity` 的 `onCreate` 方法调用时，对这些参数进行了注入，因此：\n\n```java\n@Override\nprotected void onCreate(@Nullable Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    ...\n    nameView.setText(name);\n    ...\n}\n```\n\n在 `super.onCreate(savedInstanceState);` 之后，属性 `name` 就已经被合理的初始化了。\n\n对 `Fragment` 我们也提供了类似的逻辑。\n\n### 状态保存\n\n在一些特定的场景下，例如转屏时，`Activity` 或者 `Fragment` 会被销毁并重新创建，销毁前会调用 `onSaveInstanceState` 来保存状态。我们同样通过监听其生命周期来实现对用户配置好的属性的值进行保存，以保证这些属性在 `Activity` 或者 `Fragment` 重新创建时能够得以恢复。\n\n### `Activity` 转场\n\n除了提供参数传递功能外，还支持通过注解为 `Activity` 配置 `pendingTransition`，例如：\n\n```java\n@Builder(pendingTransition = PendingTransition(enterAnim = R.anim.fade_in, exitAnim = R.anim.fade_out))\nclass UserActivity : AppCompatActivity() {\n    ...\n}\n```\n\n这样每次启动 `UserActivity` 时，我们都会在相应的方法当中调用 `overridePendingTransition` 来设置这些转场动画。\n\n### `SharedElement` 元素动画\n\n从 Android 5.0 开始，系统在 Activity、Fragment、View 之间支持了共享元素动画，但接口使用起来略显复杂，因此我们通过对 `Activity` 或者 `Fragment` 添加注解，在启动或者显示相应的组件时，调用相应的方法来实现共享元素动画，让页面的跳转更加连贯。\n\n我们支持用户通过 `id`、`transitionName` 来实现元素的关联。\n\n```kotlin\n@Builder(\n        sharedElements = [SharedElement(sourceId = R.id.openJavaActivity, targetName = \"hello\")],\n        sharedElementsWithName = [(SharedElementWithName(\"button2\"))],\n        sharedElementsByNames= [(SharedElementByNames(source = \"button1\",target = \"button3\"))]\n)\nclass DetailsActivity : AppCompatActivity() {\n    ...\n}\n```\n\n### `Activity` 的结果\n\n有些情况下我们需要目标 `Activity` 在结束时回传一些结果给当前 `Activity`，例如我们为了修改用户信息，需要从 `UserActivity` 跳转到 `EditUserActivity`，编辑完成之后需要把修改后的结果返回给 `UserActivity`，我们只需要：\n\n```java\n@Builder(resultTypes = {@ResultEntity(name = \"name\", type = String.class),\n                @ResultEntity(name = \"age\", type = int.class),\n                @ResultEntity(name = \"title\", type = String.class),\n                @ResultEntity(name = \"company\", type = String.class)})\npublic class EditUserActivity extends Activity {\n    ...\n}\n```\n\n这样我们就可以这样启动 `EditUserActivity`：\n\n```java\nEditUserActivityBuilder.builder(30, \"Kotlin\", \"bennyhuo\", \"Kotlin Developer\")\n        .start(this, new EditUserActivityBuilder.OnEditUserActivityResultListener() {\n            @Override\n            public void onResult(int age, String company, String name, String title) {\n                ... // handle result\n            }\n        });\n```\n\n编辑之后这样返回：\n\n```java\nEditUserActivityBuilder.smartFinish(this, 36, \"Kotliner\",\"bennyhuo\", \"Kotlin Dev\");\n```\n\n如果是 Kotlin 代码，那么我们还可以使用 Lambda 表达式让代码变得简单：\n\n```kotlin\nstartEditUserActivity(36, \"Kotliner\", \"bennyhuo\", \"Kotlin Dev\"){\n    age, company, name, title -\u003e  \n    ... // handle result\n}\n```\n\n值得一提的是，对于在编辑用户信息时， `UserActivity` 的实例因各种原因（例如开发者选项中的”不保留活动“开启时）被销毁，从 `EditUserActivity` 返回时，`UserActivity` 被重新创建，导致之间的回调（匿名内部类、Lambda 表达式）持有的外部引用失效，进而使回调没有意义。为了解决这个问题，我会在页面返回，上一个页面被重新创建时尝试替换掉失效的实例以保证回调可以正常使用，其中主要包括：\n\n1. 外部 `Activity` 的实例，这个通常没有问题。\n2. 外部 `View` 的实例，通常也是回调所在的 `Activity` 当中的 `View`，在更新实例时，我们通过 `View` 的 id 来索引，因此如果布局当中有重复的 id，回调可能将无法更新到正确的实例而产生问题。因此请注意保持 `Activity` 的布局当中 `View` 的 id 的唯一性。\n3. 外部 `Fragment` 的实例，通常也是所在的 `Activity` 当中的 `Fragment`，为了保证 `Fragment` 的唯一性，我使用了 `Fragment` 未公开的属性 `mWho` 来进行索引。\n\n尽管从理论的角度，这个更新实例的方法较为可靠，但毕竟这个功能比较 Tricky，如果大家在使用过程中发现回调调用之后没有反应，那么请开 Issue 一起讨论解决方案。\n\n### 属性名常量\n\n有些情况下，大家在页面跳转时不是很方便调用我们生成的方法，那么这时候为了方便使用，我们也会生成以属性名为值的常量，方便使用，例如：\n\n```java\npublic final class UserActivityBuilder {\n  public static final String REQUIRED_age = \"age\";\n  public static final String REQUIRED_name = \"name\";\n  public static final String OPTIONAL_company = \"company\";\n  public static final String OPTIONAL_title = \"title\";\n  ...\n}\n```\n\n### `Fragment` 支持\n\n由于从 API 28 开始，Android 废弃了 `android.app.Fragment` 相关的 API，转而推荐使用 `support-fragment`，同时由于框架本身也需要监听 `Fragment` 的生命周期，因此我们对于 `android.app.Fragment` 不予支持，请谅解。\n\n## 项目如何接入？\n\n仓库配置：\n\n```\n// snapshot\nrepositories {\n    ...\n    maven {\n        url \"https://oss.sonatype.org/content/repositories/snapshots/\"\n    }\n    ...\n}\n\n// release\nrepositories {\n    ...\n    mavenCentral()\n    ...\n}\n```\n\n依赖配置：\n\n```\nplugins {\n    id 'com.android.application'\n    id 'kotlin-android'\n\n    // for ksp\n    id(\"com.google.devtools.ksp\").version(\"1.5.31-1.0.1\")\n    // for kapt\n    id \"kotlin-kapt\"\n}\n\ndependencies {\n    // for android support\n    api \"com.bennyhuo.tieguanyin:runtime:$latest_version\"\n    // for androidx\n    api \"com.bennyhuo.tieguanyin:runtime-androidx:$latest_version\"\n    // for kapt\n    kapt \"com.bennyhuo.tieguanyin:compiler:$latest_version\"\n    // for ksp\n    ksp \"com.bennyhuo.tieguanyin:compiler-ksp:$latest_version\"\n}\n```\n\n当前版本：kapt 2.0.1/ksp 2.1.0\n\n注意，kapt 和 ksp 选一个即可；如果你不用 Kotlin，那么 kapt 替换成 annotationProcessor。\n\n最后在 `Application` 的 `onCreate` 当中调用：\n\n```java\nTieguanyin.init(this);\n```\n即可。\n\n\n### NewIntent\n\n由于 `onNewIntent` 没有相应的回调，我们无法在框架内部做到用户无感的数据注入，因此如果你需要处理这种情况，请主动调用 `processNewIntent` 方法：\n\n在 Java  中：\n\n```java\n@Override\npublic void onNewIntent(Intent intent) {\n    super.onNewIntent(intent);\n    MyActivityBuilder.processNewIntent(this, intent);\n}\n```\n\n在 Kotlin 中：\n\n```kotlin\noverride fun onNewIntent(intent: Intent?) {\n    super.onNewIntent(intent)\n    processNewIntent(intent)\n}\n```\n\n我们也提供了参数 `updateIntent`，如果你不希望在注入数据的时候同时也调用 `setIntent(intent)` 来更新 `activity` 的 `intent`，请将它置为 `false`。\n\n## 更新日志\n\n### compiler-ksp \u0026 annotations 2.1.0\n\n1. 废弃 Optional 注解当中的默认值字段，数值、字符串的默认值可以直接在声明处指定。\n2. 为生成的代码添加字段文档\n3. 优化生成的函数，统一调用路径\n4. 为 Builder 类型添加 onIntent 回调，方便调用者自定义 intent\n5. 参数和返回值的常量字段名改为全大写\n\n## 其他相关\n\n* **[Apt-Utils](https://github.com/enbandari/Apt-Utils)**：解决了类型在 Java 和 Kotlin 之间的统一性和兼容性问题，提供了注解处理器一些常用的工具方法，尤其适合同时生成 Java 和 Kotlin 代码的注解处理器项目。\n* **[Apt-Tutorials](https://github.com/enbandari/Apt-Tutorials)**：基于本项目简化后并录制的一套**注解处理器**的教学视频。\n\n## License\n\n\u003e [Apache License 2.0](https://github.com/enbandari/TieGuanYin/blob/master/LICENSE)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbennyhuo%2Ftieguanyin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbennyhuo%2Ftieguanyin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbennyhuo%2Ftieguanyin/lists"}