├── .gitignore ├── LICENSE ├── README.md ├── README_ZH.md ├── build.gradle ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── index.html └── zh │ ├── idea.md │ └── usage.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample-java ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── dylanc │ │ └── tracker │ │ └── sample │ │ └── java │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── dylanc │ │ │ └── tracker │ │ │ └── sample │ │ │ └── java │ │ │ ├── App.java │ │ │ ├── adapter │ │ │ ├── SeriesAdapter.java │ │ │ └── VideoAdapter.java │ │ │ ├── bean │ │ │ └── Video.java │ │ │ ├── repository │ │ │ └── DataRepository.java │ │ │ ├── track │ │ │ ├── ResultTrackNode.java │ │ │ └── UMTrackHandler.java │ │ │ └── ui │ │ │ ├── DetailsActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── SeriesActivity.java │ │ │ ├── SignInActivity.java │ │ │ ├── SignUpActivity.java │ │ │ └── fragment │ │ │ ├── HomeFragment.java │ │ │ ├── MineFragment.java │ │ │ ├── MovieFragment.java │ │ │ ├── RecommendFragment.java │ │ │ └── TheaterFragment.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── ic_star.xml │ │ └── ic_star_border.xml │ │ ├── layout │ │ ├── activity_details.xml │ │ ├── activity_main.xml │ │ ├── activity_sign_in.xml │ │ ├── activity_sign_up.xml │ │ ├── fragment_home.xml │ │ ├── fragment_mine.xml │ │ ├── fragment_theater.xml │ │ ├── item_series.xml │ │ ├── item_video.xml │ │ └── layout_list.xml │ │ ├── menu │ │ └── bottom_navigation_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── dylanc │ └── tracker │ └── sample │ └── java │ └── ExampleUnitTest.kt ├── sample-kotlin ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── dylanc │ │ └── tracker │ │ └── sample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── dylanc │ │ │ └── tracker │ │ │ └── sample │ │ │ ├── App.kt │ │ │ ├── adapter │ │ │ ├── SeriesAdapter.kt │ │ │ └── VideoAdapter.kt │ │ │ ├── bean │ │ │ └── Video.kt │ │ │ ├── const │ │ │ └── Const.kt │ │ │ ├── repository │ │ │ └── DataRepository.kt │ │ │ ├── track │ │ │ ├── ResultTrackNode.kt │ │ │ └── UMTrackHandler.kt │ │ │ └── ui │ │ │ ├── DetailsActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── SeriesActivity.kt │ │ │ ├── SignInActivity.kt │ │ │ ├── SignUpActivity.kt │ │ │ └── fragment │ │ │ ├── HomeFragment.kt │ │ │ ├── MineFragment.kt │ │ │ ├── MovieFragment.kt │ │ │ ├── RecommendFragment.kt │ │ │ └── TheaterFragment.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── ic_star.xml │ │ └── ic_star_border.xml │ │ ├── layout │ │ ├── activity_details.xml │ │ ├── activity_main.xml │ │ ├── activity_sign_in.xml │ │ ├── activity_sign_up.xml │ │ ├── fragment_home.xml │ │ ├── fragment_mine.xml │ │ ├── fragment_theater.xml │ │ ├── item_series.xml │ │ ├── item_video.xml │ │ └── layout_list.xml │ │ ├── menu │ │ └── bottom_navigation_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── dylanc │ └── tracker │ └── sample │ └── ExampleUnitTest.kt ├── settings.gradle ├── tracker-arouter ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── dylanc │ └── tracker │ └── arouter │ └── ARouterWithTrack.kt └── tracker ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── dylanc │ └── tracker │ ├── TrackHandler.kt │ ├── TrackNode.kt │ ├── TrackParams.kt │ └── Tracker.kt └── res └── values └── ids.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tracker 2 | 3 | English | [中文](README_ZH.md) 4 | 5 | [![](https://www.jitpack.io/v/DylanCaiCoding/Tracker.svg)](https://www.jitpack.io/#DylanCaiCoding/Tracker) 6 | [![](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://github.com/DylanCaiCoding/Tracker/blob/master/LICENSE) 7 | 8 | Tracker is a lightweight tracking framework based on [the tracking idea of Buzzvideo](https://mp.weixin.qq.com/s/iMn--4FNugtH26G90N1MaQ). 9 | 10 | ## Tracking idea 11 | 12 | [Why use chain of responsibility tracking?](https://dylancaicoding.github.io/Tracker/#/zh/idea) 13 | 14 | ## Gradle 15 | 16 | Add it in your root build.gradle at the end of repositories: 17 | 18 | ```groovy 19 | allprojects { 20 | repositories { 21 | // ... 22 | maven { url 'https://www.jitpack.io' } 23 | } 24 | } 25 | ``` 26 | 27 | Add dependencies: 28 | 29 | ```groovy 30 | dependencies { 31 | implementation 'com.github.DylanCaiCoding:Tracker:1.0.1' 32 | } 33 | ``` 34 | 35 | ## Usage 36 | 37 | :pencil: **[>> Usage documentation <<](https://dylancaicoding.github.io/Tracker/#/zh/usage)** 38 | 39 | ## Sample 40 | 41 | Set a `trackNode` for the Activity/Fragment/View to add tracking parameters. 42 | 43 | ```kotlin 44 | trackNode = TrackNode("channel_name" to "recommend") 45 | ``` 46 | 47 | ```kotlin 48 | holder.itemView.trackNode = TrackNode("video_id" to item.id, "video_type" to item.type) 49 | ``` 50 | 51 | Set a referrer node and a page node to establish a chain of source responsibilities between activity. 52 | 53 | ```kotlin 54 | val intent = Intent(activity, DetailsActivity::class.java).putReferrerTrackNode(view) 55 | activity.startActivity(intent) 56 | ``` 57 | 58 | ```kotlin 59 | activity.trackNode = PageTrackNode("page_name" to "details") 60 | ``` 61 | 62 | This creates a chain of responsibility similar to the one below. 63 | 64 | ![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e4056fcff5a84c75988f4fa60e7e6ab5~tplv-k3u1fbpfcp-zoom-1.image) 65 | 66 | Then it can collect and post the parameters on the responsibility chain through any view. 67 | 68 | ```kotlin 69 | view.postTrack("click_favorite") 70 | ``` 71 | 72 | This library has Kotlin and Java sample code for simulating Buzzvideo tracking requirements. 73 | 74 | ## Change log 75 | 76 | [Releases](https://github.com/DylanCaiCoding/Tracker/releases) 77 | 78 | ## Author's other libraries 79 | 80 | | Library | Description | 81 | | ------------------------------------------------------------ | ------------------------------------------------------------ | 82 | | [Longan](https://github.com/DylanCaiCoding/Longan) | Probably the best Kotlin utils library for Android. | 83 | | [LoadingStateView](https://github.com/DylanCaiCoding/LoadingStateView) | Decoupling the code of toolbar or loading status view. | 84 | | [ViewBindingKTX](https://github.com/DylanCaiCoding/ViewBindingKTX) | The most comprehensive utils of ViewBinding. | 85 | | [MultiBaseUrls](https://github.com/DylanCaiCoding/MultiBaseUrls) | Use annotation to allow Retrofit to support multiple baseUrl and dynamically change baseUrl | 86 | | [MMKV-KTX](https://github.com/DylanCaiCoding/MMKV-KTX) | Use MMKV with property delegates. | 87 | 88 | ## License 89 | 90 | ``` 91 | Copyright (C) 2022. Dylan Cai 92 | 93 | Licensed under the Apache License, Version 2.0 (the "License"); 94 | you may not use this file except in compliance with the License. 95 | You may obtain a copy of the License at 96 | 97 | http://www.apache.org/licenses/LICENSE-2.0 98 | 99 | Unless required by applicable law or agreed to in writing, software 100 | distributed under the License is distributed on an "AS IS" BASIS, 101 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 102 | See the License for the specific language governing permissions and 103 | limitations under the License. 104 | ``` 105 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # Tracker 2 | 3 | [English](https://github.com/DylanCaiCoding/Tracker) | 中文 4 | 5 | [![](https://www.jitpack.io/v/DylanCaiCoding/Tracker.svg)](https://www.jitpack.io/#DylanCaiCoding/Tracker) 6 | [![](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://github.com/DylanCaiCoding/Tracker/blob/master/LICENSE) 7 | 8 | Tracker 是基于[西瓜视频的责任链埋点思路](https://mp.weixin.qq.com/s/iMn--4FNugtH26G90N1MaQ)实现的轻量级埋点框架。个人理解其核心思想后进行了改进和优化,最后仅用了 200 多行代码实现,使用起来更加简单,并且兼顾了 Kotlin 和 Java 用法。 9 | 10 | ## 埋点思路 11 | 12 | [为什么使用责任链的埋点方案?](https://dylancaicoding.github.io/Tracker/#/zh/idea) 13 | 14 | ## Gradle 15 | 16 | 在根目录的 build.gradle 添加: 17 | 18 | ```groovy 19 | allprojects { 20 | repositories { 21 | // ... 22 | maven { url 'https://www.jitpack.io' } 23 | } 24 | } 25 | ``` 26 | 27 | 添加依赖: 28 | 29 | ```groovy 30 | dependencies { 31 | implementation 'com.github.DylanCaiCoding:Tracker:1.0.1' 32 | } 33 | ``` 34 | 35 | ## 用法 36 | 37 | :pencil: **[>> 使用文档 <<](https://dylancaicoding.github.io/Tracker/#/zh/usage)** 38 | 39 | ## 示例 40 | 41 | 给 Activity、Fragment、View 设置埋点节点,通过视图树的层级关系(比如:`Activity -> Fragment -> ViewHolder -> Button`)建立节点的上下级责任链关系。 42 | 43 | ```kotlin 44 | trackNode = TrackNode("channel_name" to "recommend") 45 | ``` 46 | 47 | ```kotlin 48 | holder.itemView.trackNode = TrackNode("video_id" to item.id, "video_type" to item.type) 49 | ``` 50 | 51 | 设置来源节点和页面节点建立页面间的来源关系。 52 | 53 | ```kotlin 54 | val intent = Intent(activity, DetailsActivity::class.java).putReferrerTrackNode(view) 55 | activity.startActivity(intent) 56 | ``` 57 | 58 | ```kotlin 59 | activity.trackNode = PageTrackNode("page_name" to "details") 60 | ``` 61 | 62 | 这样就能建立类似下图的责任链。 63 | 64 | ![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e4056fcff5a84c75988f4fa60e7e6ab5~tplv-k3u1fbpfcp-zoom-1.image) 65 | 66 | 后续就能通过任意控件去上报责任链上的埋点参数。 67 | 68 | ```kotlin 69 | view.postTrack("click_favorite") 70 | ``` 71 | 72 | 完整的 Kotlin、Java 用法请[查看使用文档](https://dylancaicoding.github.io/Tracker/#/zh/usage)。本库有模拟西瓜视频埋点需求的示例代码,大家可以克隆项目运行 `sample-java` 或 `sample-kotlin`,点击各个位置的收藏按钮查看埋点日志。 73 | 74 | ## 更新日志 75 | 76 | [Releases](https://github.com/DylanCaiCoding/Tracker/releases) 77 | 78 | ## 反馈 79 | 80 | 有问题可以提 [issues](https://github.com/DylanCaiCoding/Tracker/issues) 或加个人微信 `DylanCaiCoding`直接反馈。 81 | 82 | ## 作者其它的库 83 | 84 | | 库 | 简介 | 85 | | ------------------------------------------------------------ | ---------------------------------------------- | 86 | | [Longan](https://github.com/DylanCaiCoding/Longan) | 可能是最好用的 Kotlin 工具库 | 87 | | [LoadingStateView](https://github.com/DylanCaiCoding/LoadingStateView) | 深度解耦标题栏或加载中、加载失败、无数据等视图 | 88 | | [ViewBindingKTX](https://github.com/DylanCaiCoding/ViewBindingKTX) | 最全面的 ViewBinding 工具 | 89 | | [MultiBaseUrls](https://github.com/DylanCaiCoding/MultiBaseUrls) | 用注解让 Retrofit 同时支持多个 baseUrl 以及动态改变 baseUrl | 90 | | [MMKV-KTX](https://github.com/DylanCaiCoding/MMKV-KTX) | 用属性委托的方式使用 MMKV | 91 | 92 | ## License 93 | 94 | ``` 95 | Copyright (C) 2022. Dylan Cai 96 | 97 | Licensed under the Apache License, Version 2.0 (the "License"); 98 | you may not use this file except in compliance with the License. 99 | You may obtain a copy of the License at 100 | 101 | http://www.apache.org/licenses/LICENSE-2.0 102 | 103 | Unless required by applicable law or agreed to in writing, software 104 | distributed under the License is distributed on an "AS IS" BASIS, 105 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 106 | See the License for the specific language governing permissions and 107 | limitations under the License. 108 | ``` 109 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | minSdkVersion = 21 4 | targetSdkVersion = 30 5 | 6 | appcompatVersion = '1.3.1' 7 | arouterVersion = '1.5.2' 8 | constraintLayoutVersion = '2.1.1' 9 | coreVersion = '1.7.0-alpha01' 10 | espressoVersion = '3.4.0' 11 | kotlinVersion = "1.6.20" 12 | lifecycleVersion = '2.4.0-alpha03' 13 | junitExtVersion = '1.1.3' 14 | junitVersion = '4.13.2' 15 | materialVersion = '1.4.0' 16 | umsdkAsmsVersion = '1.4.0' 17 | umsdkCommonVersion = '9.4.7' 18 | } 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | dependencies { 24 | classpath 'com.android.tools.build:gradle:4.2.1' 25 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 26 | } 27 | } 28 | 29 | allprojects { 30 | repositories { 31 | google() 32 | jcenter() 33 | maven { url 'https://www.jitpack.io' } 34 | } 35 | } 36 | 37 | task clean(type: Delete) { 38 | delete rootProject.buildDir 39 | } -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DylanCaiCoding/Tracker/6e766f52ad1732f3718f156da08f3b3ef938f120/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Tracker 2 | 3 | [![](https://www.jitpack.io/v/DylanCaiCoding/Tracker.svg)](https://www.jitpack.io/#DylanCaiCoding/Tracker) 4 | [![](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://github.com/DylanCaiCoding/Tracker/blob/master/LICENSE) 5 | [![GitHub Repo stars](https://img.shields.io/github/stars/DylanCaiCoding/Tracker?style=social)](https://github.com/DylanCaiCoding/Tracker) 6 | 7 | Tracker 是基于[西瓜视频的责任链埋点思路](https://mp.weixin.qq.com/s/iMn--4FNugtH26G90N1MaQ)实现的轻量级埋点框架。个人理解其核心思想后进行了改进和优化,最后仅用了 200 多行代码实现,学习成本更低,并且兼顾了 Kotlin 和 Java 用法。 8 | 9 | ## 埋点思路 10 | 11 | [为什么使用责任链的埋点方案?](/zh/idea) 12 | 13 | ## Gradle 14 | 15 | 在根目录的 build.gradle 添加: 16 | 17 | ```groovy 18 | allprojects { 19 | repositories { 20 | // ... 21 | maven { url 'https://www.jitpack.io' } 22 | } 23 | } 24 | ``` 25 | 26 | 添加依赖: 27 | 28 | ```groovy 29 | dependencies { 30 | implementation 'com.github.DylanCaiCoding:Tracker:1.0.1' 31 | } 32 | ``` 33 | 34 | ## 示例 35 | 36 | 本库有模拟西瓜视频埋点需求的示例代码,大家可以克隆项目运行 `sample-java` 或 `sample-kotlin`,点击各个位置的收藏按钮查看埋点日志。 37 | 38 | ## 更新日志 39 | 40 | [Releases](https://github.com/DylanCaiCoding/Tracker/releases) 41 | 42 | ## 反馈 43 | 44 | 有问题可以提 [issues](https://github.com/DylanCaiCoding/Tracker/issues) 或加个人微信 `DylanCaiCoding`直接反馈。 45 | 46 | ## 作者其它的库 47 | 48 | | 库 | 简介 | 49 | | ------------------------------------------------------------ | ---------------------------------------------- | 50 | | [Longan](https://github.com/DylanCaiCoding/Longan) | 可能是最好用的 Kotlin 工具库 | 51 | | [LoadingStateView](https://github.com/DylanCaiCoding/LoadingStateView) | 深度解耦标题栏或加载中、加载失败、无数据等视图 | 52 | | [ViewBindingKTX](https://github.com/DylanCaiCoding/ViewBindingKTX) | 最全面的 ViewBinding 工具 | 53 | | [MMKV-KTX](https://github.com/DylanCaiCoding/MMKV-KTX) | 用属性委托的方式使用 MMKV | 54 | 55 | ## License 56 | 57 | ``` 58 | Copyright (C) 2022. Dylan Cai 59 | 60 | Licensed under the Apache License, Version 2.0 (the "License"); 61 | you may not use this file except in compliance with the License. 62 | You may obtain a copy of the License at 63 | 64 | http://www.apache.org/licenses/LICENSE-2.0 65 | 66 | Unless required by applicable law or agreed to in writing, software 67 | distributed under the License is distributed on an "AS IS" BASIS, 68 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 69 | See the License for the specific language governing permissions and 70 | limitations under the License. 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [介绍](/) 2 | * [责任链埋点思路](/zh/idea) 3 | * [用法](/zh/usage) -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/zh/idea.md: -------------------------------------------------------------------------------- 1 | # 责任链埋点思路 2 | 3 | ## 埋点需求 4 | 5 | 行为分析埋点通常需要包括某一事件发生时的前因、后果,以及事件发生对象的特征。在复杂的数据分析、模型训练等需求中,不仅仅需要获知某个事件的发生次数,对埋点上下文尤为关注。此处上下文指的通常有 2 类,分别是: 6 | 7 | - 事件发生的页面信息和页面位置信息; 8 | - 用户经过怎样的路径来到当前页面,也就是“来源”信息; 9 | 10 | 比如西瓜视频点击收藏的埋点场景,要求包含收藏影片的信息,所在的场景信息等。 11 | 12 | ![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85f841476fd74b33ad4caa095bc2ceea~tplv-k3u1fbpfcp-zoom-1.image) 13 | 14 | 1. 如果收藏事件发生在列表页,会上报如下的内容: 15 | 16 | ``` 17 | { 18 |   "event": "click_favorite", 19 |   "params": { 20 |     "video_id": "123",  // 影片ID 21 |     "video_type": 2,  // 影片类型 22 |     "page_name": "feed",  // 当前页面 23 |     "tab_name": "long_video"  // 当前所在的底Tab 24 |     "channel_name": "lvideo_recommend", // 当前所在的频道 25 |   } 26 | } 27 | ``` 28 | 29 | 2. 如果收藏事件发生在详情页,会上报如下的内容: 30 | 31 | ``` 32 | { 33 |   "event": "click_favorite", 34 |   "params": { 35 |     "video_id": "123",  // 影片ID 36 |     "video_type": 2,  // 影片类型 37 |     "page_name": "detail",  // 当前页面 38 |     "from_page": "feed",  // 来源页面 39 |     "from_tab_name": "long_video"  // 来源底Tab 40 |     "from_channel_name": "lvideo_recommend", // 来源频道 41 |   } 42 | } 43 | ``` 44 | 45 | ## 现有方案 46 | 47 | ### 直接传参 48 | 49 | 通过平台支持的参数传递方式,逐个定义并且读写参数。直接传参有非常显著的缺陷: 50 | 51 | - 每增加一个参数,都需要写大量的重复代码,工程代码膨胀; 52 | - 模块间约定了很多埋点参数的协议,耦合程度高,难以维护; 53 | - 一些场景的嵌套层次深,经过很多层的参数传递,非常容易漏报埋点参数; 54 | 55 | ### 单例传参 56 | 57 | 通过一个单例进行埋点参数的维护,程序中的任何位置都能方便地读和写埋点参数。这种方式带来的好处是不需要在每个类都定义大量的埋点参数,只需要访问单例进行修改和读取。会比前面的直接传参更简单,但这种方案治标不治本,同样有明显的弊端: 58 | 59 | - 无法解决列表页这种多实例场景的问题,比如一个推荐列表中有多个卡片,每个卡片的埋点参数都不一样,卡片的埋点参数还是需要自己传; 60 | - 单例的数据可能被多个位置写入,且一旦被覆盖就没法恢复,导致埋点参数上报错误; 61 | - 存放和清理的时机难以控制,清理早了会导致埋点参数缺失,忘记清理可能导致后面的埋点获取不到参数; 62 | 63 | ### 全埋点/无埋点 64 | 65 | 指埋点 SDK 通过编译时插桩、运行时反射或动态代理的方式,自动进行埋点事件的触发和上报,理论上能够搜集到所有页面、视图的曝光、点击等事件,无须客户端工程师手动进行埋点开发工作。理想很丰满,现实很骨感,看似很完美的方案也是有些弊端: 66 | 67 | - 仅能上报有限的简单事件类型,无法完成复杂事件的上报; 68 | - 全场景的数据上报,可能产生大量的无用数据,消耗大量传输、存储、计算资源; 69 | - 把复杂度从开发转嫁给了产品经理、数据分析师,消费成本较高; 70 | 71 | ## 西瓜视频的责任链方案 72 | 73 | 分析数据与视图节点的关系可以发现,埋点参数恰好就分布在视图树的责任链中。 74 | 75 | ![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f54c6499d214e668c50a116f442af45~tplv-k3u1fbpfcp-zoom-1.image) 76 | 77 | ![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c777c72524ee4fd3b75b7cbb05ab967f~tplv-k3u1fbpfcp-zoom-1.image) 78 | 79 | 结合跳转链路,逻辑上也是个树状结构。 80 | 81 | ![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4906defb017f474391880c5399951459~tplv-k3u1fbpfcp-zoom-1.image) 82 | 83 | ![图片](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e4056fcff5a84c75988f4fa60e7e6ab5~tplv-k3u1fbpfcp-zoom-1.image) 84 | 85 | 所以我们需要的埋点上下文参数,理论上都可以通过节点的关系找到,然后通过责任链能很方便地收集到埋点参数。 86 | 87 | ## 各方案的优缺点 88 | 89 | 现有的三种埋点方案都有明显的缺点,全埋点或无埋点看似很美好,却只是个半自动方案,能自动上报的只有简单事件,复杂的事件只能手动处理,这又回到了直接传参或单例传参。 90 | 91 | 个人不推荐单例传参,因为不太可控,可能会被覆盖,清理时机不好把控,清早了丢数据。 92 | 93 | 直接传参是最稳的,但是会有大量的重复代码,并且嵌套过深可能会漏传参数。 94 | 95 | 而西瓜视频的责任链方案是直接传参的一种升级版,也是会传递参数,不过通过视图树和跳转链路建立的责任链自动收集埋点参数,代码量远比直接传参少很多。该方案也能作为全埋点或者无埋点的一种补充。 96 | 97 | ## 参考文章 98 | 99 | - [《西瓜客户端埋点实践:基于责任链的埋点框架》](https://mp.weixin.qq.com/s/iMn--4FNugtH26G90N1MaQ) 100 | -------------------------------------------------------------------------------- /docs/zh/usage.md: -------------------------------------------------------------------------------- 1 | # 用法 2 | 3 | ## 初始化 4 | 5 | 在 Application 初始化,传入一个 `TrackHandler` 实例。 6 | 7 | 8 | 9 | #### **Kotlin** 10 | 11 | ```kotlin 12 | initTracker(UMTrackHandler()) 13 | ``` 14 | 15 | ```kotlin 16 | class UMTrackHandler : TrackHandler { 17 | override fun onEvent(context: Context, eventId: String, params: Map) { 18 | MobclickAgent.onEvent(context, eventId, params) // 以友盟统计为例 19 | } 20 | } 21 | ``` 22 | 23 | #### **Java** 24 | 25 | ```java 26 | Tracker.init(this, new UMTrackHandler()); 27 | ``` 28 | 29 | ```java 30 | public class UMTrackHandler implements TrackHandler { 31 | 32 | @Override 33 | public void onEvent(@NonNull Context context, @NonNull String eventId, @NonNull Map params) { 34 | MobclickAgent.onEvent(context, eventId, params); // 以友盟统计为例 35 | } 36 | } 37 | ``` 38 | 39 | 40 | 41 | ## 建立页面内上下级责任链 42 | 43 | 可以给 Activity、Fragment、View 设置埋点节点 `TrackNode` 添加埋点参数。 44 | 45 | 46 | 47 | #### **Kotlin** 48 | 49 | ```kotlin 50 | trackNode = TrackNode("channel_name" to "recommend") 51 | ``` 52 | 53 | ```kotlin 54 | holder.itemView.trackNode = TrackNode("video_id" to item.id, "video_type" to item.type) 55 | ``` 56 | 57 | #### **Java** 58 | 59 | ```java 60 | Tracker.setTrackNode(this, params -> params.put("channel_name", "recommend")); 61 | ``` 62 | 63 | ```java 64 | Tracker.setTrackNode(holder.itemView, params -> params.put("video_id", item.getId()).put("video_type", item.getType())); 65 | ``` 66 | 67 | 68 | 69 | 通过视图树的层级关系(比如:`Activity -> Fragment -> ViewHolder -> Button`)就能建立节点的上下级责任链关系。 70 | 71 | ## 建立页面来源责任链 72 | 73 | 页面跳转时需要设置来源节点。 74 | 75 | 76 | 77 | #### **Kotlin** 78 | 79 | ```kotlin 80 | val intent = Intent(activity, DetailsActivity::class.java).putReferrerTrackNode(view) 81 | activity.startActivity(intent) 82 | ``` 83 | 84 | #### **Java** 85 | 86 | ```java 87 | Intent intent = new Intent(activity, DetailsActivity.class); 88 | Tracker.putReferrerTrackNode(intent, view); 89 | activity.startActivity(intent); 90 | ``` 91 | 92 | 93 | 94 | 然后在跳转的 Activity 设置一个页面节点 `PageTrackNode`,这样就建立了页面间的来源责任链。 95 | 96 | 97 | 98 | #### **Kotlin** 99 | 100 | ```kotlin 101 | trackNode = PageTrackNode("page_name" to "details") 102 | ``` 103 | 104 | #### **Java** 105 | 106 | ```java 107 | Tracker.setPageTrackNode(this, params -> params.put("page_name", "details")); 108 | ``` 109 | 110 | 111 | 112 | `PageTrackNode` 会添加前面所有节点的参数,添加的时候可以设置一些转换规则。比如上个页面的 `page_name`,跳转后上报 `from_page`。 113 | 114 | 115 | 116 | #### **Kotlin** 117 | 118 | ```kotlin 119 | val referrerKeyMap = mapOf("page_name" to "from_page", "channel_name" to "from_channel_name") 120 | trackNode = PageTrackNode(referrerKeyMap, "page_name" to "details") 121 | ``` 122 | 123 | #### **Java** 124 | 125 | ```java 126 | HashMap referrerKeyMap = new HashMap<>(); 127 | referrerKeyMap.put("page_name", "from_page"); 128 | referrerKeyMap.put("channel_name", "from_channel_name"); 129 | Tracker.setPageTrackNode(this, referrerKeyMap, params -> params.put("page_name", "details")); 130 | ``` 131 | 132 | 133 | 134 | ## 上报埋点参数 135 | 136 | 上报埋点会通过上下级责任链和来源责任链收集埋点参数,回调给 `TrackHandler` 进行上报。 137 | 138 | 139 | 140 | #### **Kotlin** 141 | 142 | ```kotlin 143 | view.postTrack("click_favorite") 144 | ``` 145 | 146 | #### **Java** 147 | 148 | ```java 149 | Tracker.postTrack(view, "click_favorite"); 150 | ``` 151 | 152 | 153 | 154 | ## 线索节点 155 | 156 | 线索节点适合用于具有会话特性的流程中,方便在流程中共享参数,常见的有登录、注册的流程、订单创建流程等。 157 | 158 | 在 Activity 可以设置线索节点,线索节点能在 View 或页面之间共享埋点参数。 159 | 160 | 161 | 162 | #### **Kotlin** 163 | 164 | ```kotlin 165 | class RecordTrackNode : TrackNode { 166 | var isRecord = false 167 | 168 | override fun fillTackParams(params: TrackParams) { 169 | params.put("is_record", it) 170 | } 171 | } 172 | ``` 173 | 174 | ```kotlin 175 | activity.putThreadTrackNode(RecordTrackNode()) 176 | ``` 177 | 178 | #### **Java** 179 | 180 | ```java 181 | public class RecordTrackNode implements TrackNode { 182 | 183 | public boolean isRecord = false; 184 | 185 | @Override 186 | public void fillTackParams(@NonNull TrackParams params) { 187 | params.put("is_record", isRecord); 188 | } 189 | } 190 | ``` 191 | 192 | ```java 193 | Tracker.putThreadTrackNode(activity, new RecordTrackNode()); 194 | ``` 195 | 196 | 197 | 198 | 之后就能在 Activity、Fragment、View 更新线索节点中的参数。 199 | 200 | 201 | 202 | #### **Kotlin** 203 | 204 | ```kotlin 205 | view.updateThreadTrackNode { isRecord = true } 206 | ``` 207 | 208 | #### **Java** 209 | 210 | ```java 211 | Tracker.updateThreadTrackNode(view, RecordTrackNode.class, node -> node.isRecord = true); 212 | ``` 213 | 214 | 215 | 216 | 上报的时候需要对线索节点进行声明才会收集参数。 217 | 218 | 219 | 220 | #### **Kotlin** 221 | 222 | ```kotlin 223 | view.postTrack("click_publish", RecordTrackNode::class.java) 224 | ``` 225 | 226 | #### **Java** 227 | 228 | ```java 229 | Tracker.postTrack(view, "click_publish", RecordTrackNode.class); 230 | ``` 231 | 232 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DylanCaiCoding/Tracker/6e766f52ad1732f3718f156da08f3b3ef938f120/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 27 17:41:12 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /sample-java/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /sample-java/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk rootProject.targetSdkVersion 8 | 9 | defaultConfig { 10 | applicationId "com.dylanc.tracker.sample.java" 11 | minSdk rootProject.minSdkVersion 12 | targetSdk rootProject.targetSdkVersion 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | } 34 | 35 | buildFeatures { 36 | viewBinding true 37 | } 38 | } 39 | 40 | dependencies { 41 | implementation "androidx.core:core-ktx:$coreVersion" 42 | implementation "androidx.appcompat:appcompat:$appcompatVersion" 43 | implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion" 44 | implementation "com.google.android.material:material:$materialVersion" 45 | implementation project(path: ':tracker') 46 | testImplementation "junit:junit:$junitVersion" 47 | androidTestImplementation "androidx.test.ext:junit:$junitExtVersion" 48 | androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" 49 | } -------------------------------------------------------------------------------- /sample-java/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /sample-java/src/androidTest/java/com/dylanc/tracker/sample/java/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.dylanc.tracker.sample.java 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.dylanc.tracker.sample.java", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /sample-java/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sample-java/src/main/java/com/dylanc/tracker/sample/java/App.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Dylan Cai 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dylanc.tracker.sample.java; 18 | 19 | import android.app.Application; 20 | 21 | import com.dylanc.tracker.Tracker; 22 | import com.dylanc.tracker.sample.java.track.UMTrackHandler; 23 | 24 | /** 25 | * @author Dylan Cai 26 | */ 27 | public class App extends Application { 28 | 29 | @Override 30 | public void onCreate() { 31 | super.onCreate(); 32 | Tracker.init(this, new UMTrackHandler()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sample-java/src/main/java/com/dylanc/tracker/sample/java/adapter/SeriesAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Dylan Cai 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dylanc.tracker.sample.java.adapter; 18 | 19 | import android.app.Activity; 20 | import android.content.Intent; 21 | import android.view.LayoutInflater; 22 | import android.view.ViewGroup; 23 | 24 | import androidx.annotation.NonNull; 25 | import androidx.recyclerview.widget.ListAdapter; 26 | import androidx.recyclerview.widget.RecyclerView; 27 | 28 | import com.dylanc.tracker.Tracker; 29 | import com.dylanc.tracker.sample.java.bean.Video; 30 | import com.dylanc.tracker.sample.java.databinding.ItemSeriesBinding; 31 | import com.dylanc.tracker.sample.java.databinding.ItemVideoBinding; 32 | import com.dylanc.tracker.sample.java.ui.DetailsActivity; 33 | 34 | /** 35 | * @author Dylan Cai 36 | */ 37 | public class SeriesAdapter extends ListAdapter { 38 | 39 | private final Activity activity; 40 | 41 | public SeriesAdapter(Activity activity) { 42 | super(new Video.DiffCallback()); 43 | this.activity = activity; 44 | } 45 | 46 | @NonNull 47 | @Override 48 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 49 | return new ViewHolder(activity, ItemSeriesBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); 50 | } 51 | 52 | @Override 53 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 54 | Video item = getItem(position); 55 | Tracker.setTrackNode(holder.itemView, params -> params.put("video_id", item.getId()).put("video_type", item.getType())); 56 | holder.binding.tvTitle.setText(item.getTitle()); 57 | } 58 | 59 | public class ViewHolder extends RecyclerView.ViewHolder { 60 | 61 | public ItemSeriesBinding binding; 62 | 63 | public ViewHolder(@NonNull Activity activity, @NonNull ItemSeriesBinding binding) { 64 | super(binding.getRoot()); 65 | this.binding = binding; 66 | binding.btnFavorite.setOnClickListener(v -> Tracker.postTrack(v, "click_favorite")); 67 | itemView.setOnClickListener(v -> { 68 | Intent intent = new Intent(activity, DetailsActivity.class) 69 | .putExtra("video", getItem(getAdapterPosition())); 70 | Tracker.putReferrerTrackNode(intent, v); 71 | activity.startActivity(intent); 72 | }); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /sample-java/src/main/java/com/dylanc/tracker/sample/java/adapter/VideoAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Dylan Cai 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dylanc.tracker.sample.java.adapter; 18 | 19 | import android.app.Activity; 20 | import android.content.Intent; 21 | import android.view.LayoutInflater; 22 | import android.view.ViewGroup; 23 | 24 | import androidx.annotation.NonNull; 25 | import androidx.recyclerview.widget.ListAdapter; 26 | import androidx.recyclerview.widget.RecyclerView; 27 | 28 | import com.dylanc.tracker.Tracker; 29 | import com.dylanc.tracker.sample.java.bean.Video; 30 | import com.dylanc.tracker.sample.java.databinding.ItemVideoBinding; 31 | import com.dylanc.tracker.sample.java.ui.DetailsActivity; 32 | import com.dylanc.tracker.sample.java.ui.SeriesActivity; 33 | 34 | /** 35 | * @author Dylan Cai 36 | */ 37 | public class VideoAdapter extends ListAdapter { 38 | 39 | private final Activity activity; 40 | 41 | public VideoAdapter(Activity activity) { 42 | super(new Video.DiffCallback()); 43 | this.activity = activity; 44 | } 45 | 46 | @NonNull 47 | @Override 48 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 49 | return new ViewHolder(activity, ItemVideoBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); 50 | } 51 | 52 | @Override 53 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 54 | Video item = getItem(position); 55 | Tracker.setTrackNode(holder.itemView, params -> params.put("video_id", item.getId()).put("video_type", item.getType())); 56 | 57 | ItemVideoBinding binding = holder.binding; 58 | binding.tvTitle.setText(item.getTitle()); 59 | if (item.getSeriesName() == null) { 60 | binding.tvSeriesName.setVisibility(ViewGroup.GONE); 61 | } else { 62 | binding.tvSeriesName.setVisibility(ViewGroup.VISIBLE); 63 | binding.tvSeriesName.setText(item.getSeriesName()); 64 | } 65 | } 66 | 67 | public class ViewHolder extends RecyclerView.ViewHolder { 68 | 69 | public ItemVideoBinding binding; 70 | 71 | public ViewHolder(@NonNull Activity activity, @NonNull ItemVideoBinding binding) { 72 | super(binding.getRoot()); 73 | this.binding = binding; 74 | binding.btnFavorite.setOnClickListener(v -> Tracker.postTrack(v, "click_favorite")); 75 | binding.tvSeriesName.setOnClickListener(v -> { 76 | Intent intent = new Intent(activity, SeriesActivity.class) 77 | .putExtra("video", getItem(getAdapterPosition())); 78 | Tracker.putReferrerTrackNode(intent, v); 79 | activity.startActivity(intent); 80 | }); 81 | itemView.setOnClickListener(v -> { 82 | Intent intent = new Intent(activity, DetailsActivity.class) 83 | .putExtra("video", getItem(getAdapterPosition())); 84 | Tracker.putReferrerTrackNode(intent, v); 85 | activity.startActivity(intent); 86 | }); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /sample-java/src/main/java/com/dylanc/tracker/sample/java/bean/Video.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Dylan Cai 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dylanc.tracker.sample.java.bean; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.recyclerview.widget.DiffUtil; 21 | 22 | import java.io.Serializable; 23 | 24 | /** 25 | * @author Dylan Cai 26 | */ 27 | public class Video implements Serializable { 28 | private String id; 29 | private String title; 30 | private String type; 31 | private String seriesName; 32 | 33 | public Video(String id, String title, String type) { 34 | this.id = id; 35 | this.title = title; 36 | this.type = type; 37 | } 38 | 39 | public Video(String id, String title, String type, String seriesName) { 40 | this.id = id; 41 | this.title = title; 42 | this.type = type; 43 | this.seriesName = seriesName; 44 | } 45 | 46 | public void setId(String id) { 47 | this.id = id; 48 | } 49 | 50 | public String getId() { 51 | return id; 52 | } 53 | 54 | public String getTitle() { 55 | return title; 56 | } 57 | 58 | public String getType() { 59 | return type; 60 | } 61 | 62 | public String getSeriesName() { 63 | return seriesName; 64 | } 65 | 66 | public static class DiffCallback extends DiffUtil.ItemCallback