├── .gitignore ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── foolchen │ │ └── lib │ │ └── tracker │ │ └── demo │ │ ├── App.kt │ │ ├── BaseActivity.kt │ │ ├── FragmentContainerActivity.kt │ │ ├── MainActivity.kt │ │ ├── Utils.kt │ │ ├── data │ │ └── Demo.kt │ │ └── fragments │ │ ├── AdapterViewFragment.kt │ │ ├── BaseFragment.kt │ │ ├── ButterKnifeFragment.java │ │ ├── MockLoginFragment.kt │ │ ├── NestedFragment.kt │ │ ├── PageFragment.kt │ │ ├── RxBindingFragment.kt │ │ ├── SimpleFragment.kt │ │ └── ViewPagerFragment.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── ic_back_material.xml │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_fragment_container.xml │ ├── activity_main.xml │ ├── fragment_adapter_view.xml │ ├── fragment_butter_knife.xml │ ├── fragment_mock_login.xml │ ├── fragment_nested.xml │ ├── fragment_rx_binding.xml │ ├── fragment_simple.xml │ ├── fragment_view_pager.xml │ ├── item_adapter_view.xml │ ├── item_butter_knife.xml │ └── item_common.xml │ ├── menu │ ├── menu_fragments.xml │ └── menu_rx_binding.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values │ ├── colors.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── network_security_config.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── maven_push.gradle ├── settings.gradle └── tracker ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── foolchen │ └── lib │ └── tracker │ ├── Tracker.kt │ ├── data │ ├── TrackerEvent.kt │ ├── TrackerEventType.kt │ ├── TrackerMNC.kt │ ├── TrackerMode.kt │ ├── TrackerNameDefs.kt │ ├── TrackerNetworkType.kt │ └── TrackerResult.kt │ ├── db │ ├── Contract.kt │ └── TrackerDbOpenHelper.kt │ ├── layout │ ├── LayoutManager.kt │ └── TrackLayout.kt │ ├── lifecycle │ ├── ITrackerContext.kt │ ├── ITrackerFragmentVisible.kt │ ├── ITrackerHelper.kt │ ├── ITrackerIgnore.kt │ ├── TrackerActivityLifeCycle.kt │ └── TrackerFragmentLifeCycle.kt │ ├── service │ ├── ToStringConverterFactory.kt │ ├── TrackerAPIDef.kt │ └── TrackerService.kt │ └── utils │ ├── EncodeUtils.kt │ ├── TrackerBuildInUtils.kt │ ├── TrackerGenerator.kt │ └── TrackerUtils.kt └── res └── values └── ids.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | maven_push.properties 10 | tracker.properties -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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 | # AndroidTracker 2 | 3 | `AndroidTracker`是一个Android端的无埋点统计的实现方法。其对`Activity`、`Fragment`的生命周期进行监听,实现了页面浏览以及点击事件的采集。 4 | 5 | 针对点击事件的处理,目前兼容`ActionBar`、`ToolBar`的点击,以及[ButterKnife](https://github.com/JakeWharton/butterknife)的点击注解。 6 | 7 | 支持版本为API 14及以上(Android 4.0及以上)。 8 | 9 | ## 初始化 10 | 11 | 在工程根目录的`build.gradle`文件的最后添加: 12 | 13 | ```groovy 14 | allprojects { 15 | repositories { 16 | // ... 17 | maven { url 'https://jitpack.io' } 18 | } 19 | } 20 | ``` 21 | 22 | 并在项目中增加如下依赖: 23 | 24 | ```groovy 25 | dependencies { 26 | implementation 'com.github.foolchen:AndroidTracker:0.3.3' 27 | } 28 | ``` 29 | 30 | 该库中引用了[okhttp3](https://github.com/square/okhttp)以及[retrofit2](https://github.com/square/retrofit),如果需要独立引用其他版本的`okhttp`和`retrofit`,则可以引用如下: 31 | 32 | ```groovy 33 | dependencies { 34 | implementation ("com.github.foolchen:AndroidTracker:0.3.3"){ 35 | exclude module: 'okhttp' 36 | exclude module: 'retrofit' 37 | } 38 | } 39 | ``` 40 | 41 | 并在项目的`Application`中初始化如下: 42 | 43 | ```kotlin 44 | class App : Application(), ITrackerContext { 45 | override fun onCreate() { 46 | super.onCreate() 47 | // 设定一些通用的属性,这些属性在每次统计事件中都会附带 48 | // 注意:如果此处的属性名与内置属性的名称相同,则内置属性会被覆盖 49 | Tracker.addProperty("附加的属性1", "附加的属性1") 50 | Tracker.addProperty("附加的属性2", "附加的属性2") 51 | // 设定上报数据的主机和接口 52 | // 注意:该方法一定要在Tracker.initialize()方法前调用 53 | // 否则会由于上报地址未初始化,在触发启动事件时导致崩溃 54 | Tracker.setService("https://www.demo.com", "report.php") 55 | // 设定上报数据的项目名称 56 | Tracker.setProjectName("android_tracker") 57 | // 设定上报数据的模式 58 | Tracker.setMode(TrackerMode.RELEASE) 59 | // 初始化AndroidTracker 60 | Tracker.initialize(this) 61 | } 62 | } 63 | ``` 64 | 65 | ## Activity中的集成方式 66 | 67 | 在`Activity`中集成时,如果项目仅存在`Activity`,不需要对`Fragment`进行特殊处理,则仅需要实现`ITrackerHelper`接口,用于获取页面的名称和页面附加属性。 68 | 如果项目中有`Fragment`,需要在特定情况下忽略`Activity`的统计,则还应该实现`ITrackerIgnore`接口,手动处理`isIgnore`方法的返回值。示例如下: 69 | 70 | ```kotlin 71 | open class BaseActivity : AppCompatActivity(), ITrackerHelper, ITrackerIgnore { 72 | /////////////////////////////////////////////////////////////////////////// 73 | // 该类实现ITrackerHelper接口,此处两个方法全部返回null 74 | // 则页面名称(别名)会直接取使用canonicalName来当做标题 75 | // 并且不会有附加的属性 76 | /////////////////////////////////////////////////////////////////////////// 77 | override fun getTrackName(): String? = null 78 | 79 | override fun getTrackProperties(): Map? = null 80 | 81 | /////////////////////////////////////////////////////////////////////////// 82 | // ITrackerIgnore接口用于确定当前Activity中是否包含Fragment 83 | // 如果返回值为true,则表明当前Activity中有包含Fragment,则此时不会对Activity进行统计 84 | // 如果返回值为false,则表明当前Activity中不包含Fragment,则此时会对Activity进行统计 85 | // 此处默认不包含Fragment,如有需要应该在子类中覆写该方法并修改返回值 86 | /////////////////////////////////////////////////////////////////////////// 87 | override fun isIgnored(): Boolean = false 88 | } 89 | ``` 90 | 91 | ## Fragment中的集成方式 92 | 93 | 与`Activity`相同,如果`Fragment`中不再有子`Fragment`,则仅需要实现`ITrackerHelper`。 94 | 如果有子`Fragment`,或者需要手动忽略部分`Fragment`的统计,则需要实现`ITrackerIgnore`接口。示例如下: 95 | 96 | ```kotlin 97 | open class BaseFragment : Fragment(), ITrackerHelper, ITrackerIgnore { 98 | 99 | /////////////////////////////////////////////////////////////////////////// 100 | // Tracker.setUserVisibleHint()和Tracker.onHiddenChanged()方法用于同步Fragment 101 | // 的可见性,解决在Fragment显隐/与ViewPager结合使用时无法触发生命周期的问题 102 | /////////////////////////////////////////////////////////////////////////// 103 | override fun setUserVisibleHint(isVisibleToUser: Boolean) { 104 | super.setUserVisibleHint(isVisibleToUser) 105 | Tracker.setUserVisibleHint(this, isVisibleToUser) 106 | } 107 | 108 | override fun onHiddenChanged(hidden: Boolean) { 109 | super.onHiddenChanged(hidden) 110 | Tracker.onHiddenChanged(this, hidden) 111 | } 112 | 113 | /////////////////////////////////////////////////////////////////////////// 114 | // 该类实现ITrackerHelper接口,此处两个方法全部返回null 115 | // 则页面名称(别名)会直接取使用canonicalName来当做标题 116 | // 并且不会有附加的属性 117 | /////////////////////////////////////////////////////////////////////////// 118 | override fun getTrackName(): String? = null 119 | 120 | override fun getTrackProperties(): Map? = null 121 | 122 | /////////////////////////////////////////////////////////////////////////// 123 | // ITrackerIgnore接口用于确定当前Fragment中是否包含子Fragment 124 | // 如果返回值为true,则表明当前Fragment中有包含子Fragment,则此时不会对当前Fragment进行统计 125 | // 如果返回值为false,则表明当前Fragment中不包含子Fragment,则此时会对当前Fragment进行统计 126 | // 此处默认不包含子Fragment,如有需要应该在子类中覆写该方法并修改返回值 127 | /////////////////////////////////////////////////////////////////////////// 128 | override fun isIgnored(): Boolean = false 129 | } 130 | ``` 131 | 132 | ## 对点击事件增加属性 133 | 134 | 在某些场景下,仅对`View`进行默认属性的统计可能无法满足需求,故可以针对点击事件增加自定义属性。示例如下(ButterKnife): 135 | 136 | ```java 137 | @OnClick(R.id.tv_clickable) public void click(View view) { 138 | Toast.makeText(view.getContext(), ((TextView) view).getText().toString(), Toast.LENGTH_SHORT) 139 | .show(); 140 | // 此处针对点击事件增加属性 141 | Map map = new HashMap<>(); 142 | map.put("附加的View属性", view.toString()); 143 | Tracker.INSTANCE.trackView(view, map); 144 | } 145 | ``` 146 | 147 | ## 登入/登出处理 148 | 149 | 每个用户会使用`distinct_id`来标识,用户未登录时,该值由设备`Android Id`、设备制造商、设备型号等字段计算得到唯一值。 150 | 而在用户登录后,则需要使用确切的用户id来替代该值。示例如下: 151 | kotlin: 152 | 153 | ```kotlin 154 | Tracker.login("此处设置用户id") 155 | ``` 156 | 157 | java: 158 | 159 | ```java 160 | Tracker.INSTANCE.login("此处设置用户id"); 161 | ``` 162 | 163 | 在用户退出登录时,需要主动调用登出方法。此时,`distinct_id`的值会替换为SDK生成的值。示例如下: 164 | 165 | kotlin: 166 | 167 | ```kotlin 168 | Tracker.logout() 169 | ``` 170 | 171 | java: 172 | 173 | ```java 174 | Tracker.INSTANCE.logout(); 175 | ``` 176 | 177 | ## 设置渠道号 178 | 179 | 渠道号在统计事件中的属性为`channel`。设置如下: 180 | 181 | kotlin: 182 | 183 | ```kotlin 184 | Tracker.setChannelId("此处设置渠道号") 185 | ``` 186 | 187 | java: 188 | 189 | ```java 190 | Tracker.INSTANCE.setChannelId("此处设置渠道号"); 191 | ``` 192 | 193 | 194 | ## 针对所有统计附加属性 195 | 196 | 如果想要针对所有的统计事件增加一些固定的属性,则应该在初始化时设置如下: 197 | 198 | ```kotlin 199 | Tracker.addProperty("附加的属性1", "附加的属性1") 200 | Tracker.addProperty("附加的属性2", "附加的属性2") 201 | ``` 202 | 203 | ## 自定义事件 204 | 205 | 该库提供了追踪自定义事件的方法,并且可以自定义属性。调用示例如下: 206 | 207 | kotlin: 208 | 209 | ```kotlin 210 | Tracker.trackEvent("MainActivity的自定义追踪事件", null) 211 | ``` 212 | 213 | java: 214 | 215 | ```java 216 | Tracker.INSTANCE.trackEvent("MainActivity的自定义追踪事件", null) 217 | ``` 218 | 219 | ## 上报模式 220 | 221 | **DEBUG_ONLY**:仅在`Logcat`中打印日志,不上传数据。建议仅在调试阶段使用该模式。 222 | 223 | **DEBUG_TRACK**:在`Logcat`中打印日志,并且**即时**上传数据。建议在开发及测试阶段使用该模式。 224 | 225 | **RELEASE**:不在`Logcat`中打印日志,每10条记录尝试上传数据。在发行版本中使用该模式。 226 | 227 | **DISABLE**:禁用上报。设置该模式时,所有的上报相关方法都失效。如果想要重新生效,需要重新初始化。 228 | 229 | ## 调试 230 | 231 | 在`DEBUG_ONLY`、`DEBUG_TRACKE`模式下,可以在`Logcat`中输出日志,格式如下: 232 | 233 | ```json 234 | { 235 | "time": 1513062404277, 236 | "event": "AppStart", 237 | "lib": { 238 | "lib": "Android", 239 | "app_version": "1.0", 240 | "lib_version": "0.3.0" 241 | }, 242 | "distinct_id": "308597799c71b44b95d1f6845", 243 | "properties": { 244 | "parent": "", 245 | "screen_width": 2560, 246 | "wifi": true, 247 | "lib": "Android", 248 | "app_version": "1.0", 249 | "os": "Android", 250 | "device_id": "c71b44b95d1f6845", 251 | "resume_from_background": false, 252 | "os_version": "8.0.0", 253 | "imeicode": "359906070806943", 254 | "parent_class": "", 255 | "lib_version": "0.3.0", 256 | "title": "", 257 | "manufacturer": "google", 258 | "referrer": "", 259 | "carrier": "中国联通", 260 | "附加的属性1": "附加的属性1", 261 | "screen_height": 1440, 262 | "附加的属性2": "附加的属性2", 263 | "screen_name": "", 264 | "model": "Pixel XL", 265 | "referrer_class": "", 266 | "network_type": "WIFI", 267 | "screen_class": "" 268 | } 269 | } 270 | ``` 271 | 272 | ## 忽略处理 273 | 274 | ### 对Activity/Fragment进行忽略 275 | 276 | 如果需要对`Activity`/`Fragment`进行忽略,则需要实现`ITrackerIgnore`接口,并手动将`isIgnore()`方法的返回值置为`true`。 277 | 如果要解除对`Activity`/`Fragment`的忽略,则根据情况将返回值置为`false`即可。 278 | 279 | ### 对点击事件进行忽略 280 | 281 | 如果要对点击事件进行忽略,则需要在点击事件触发时手动调用`Tracker.ignoreView(view)`方法即可。该方式针对普通的点击监听设置方式以及`ButterKnife`的注解方式都生效。 282 | 283 | ## 注意事项 284 | 由于对点击事件的统计使用到了反射,故集成了该库之后会对点击时的效率有所影响。 285 | 286 | ## License 287 | 288 | ``` 289 | Copyright 2017 Chen Chong 290 | 291 | Licensed under the Apache License, Version 2.0 (the "License"); 292 | you may not use this file except in compliance with the License. 293 | You may obtain a copy of the License at 294 | 295 | http://www.apache.org/licenses/LICENSE-2.0 296 | 297 | Unless required by applicable law or agreed to in writing, software 298 | distributed under the License is distributed on an "AS IS" BASIS, 299 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 300 | See the License for the specific language governing permissions and 301 | limitations under the License. 302 | ``` 303 | 304 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | src/main/res/raw/charles_ssl_proxying_certificate.pem -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion rootProject.ext.compile_sdk_version 8 | defaultConfig { 9 | applicationId "com.foolchen.lib.tracker.demo" 10 | minSdkVersion rootProject.ext.min_sdk_version 11 | targetSdkVersion rootProject.ext.target_sdk_version 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | // 用于读取本地的配置信息 17 | Properties properties = new Properties() 18 | InputStream is = project.rootProject.file('local.properties').newDataInputStream() 19 | properties.load(is) 20 | buildConfigField "String", "SERVICE_HOST", 21 | String.format('\"%1$s\"', properties.getProperty('SERVICE_HOST')) 22 | buildConfigField "String", "SERVICE_PATH", 23 | String.format('\"%1$s\"', properties.getProperty('SERVICE_PATH')) 24 | buildConfigField "String", "PROJECT_NAME", 25 | String.format('\"%1$s\"', properties.getProperty('PROJECT_NAME')) 26 | } 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation fileTree(dir: 'libs', include: ['*.jar']) 37 | implementation project(":tracker") 38 | //implementation 'com.foolchen.lib:tracker:0.0.3-SNAPSHOT' 39 | 40 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 41 | implementation "com.android.support:design:$support_library_version" 42 | implementation "com.android.support:appcompat-v7:$support_library_version" 43 | implementation "com.android.support:support-fragment:$support_library_version" 44 | implementation "com.android.support:support-annotations:$support_library_version" 45 | implementation "com.jakewharton:butterknife:$butter_knife_version" 46 | 47 | implementation "com.android.support.constraint:constraint-layout:$constraint_layout_version" 48 | testImplementation 'junit:junit:4.12' 49 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 50 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 51 | debugImplementation 'com.amitshekhar.android:debug-db:1.0.1' 52 | 53 | implementation 'com.ogaclejapan.smarttablayout:library:1.6.1@aar' 54 | 55 | //Optional: see how to use the utility. 56 | implementation 'com.ogaclejapan.smarttablayout:utils-v4:1.6.1@aar' 57 | 58 | implementation "io.reactivex.rxjava2:rxjava:$rx_java_version" 59 | implementation("io.reactivex.rxjava2:rxandroid:$rx_android_version") { 60 | exclude module: 'rxjava' 61 | } 62 | implementation("com.jakewharton.rxbinding2:rxbinding:$rx_binding_version") { 63 | exclude module: 'rxjava' 64 | exclude module: 'rxandroid' 65 | } 66 | implementation("com.jakewharton.rxbinding2:rxbinding-support-v4:$rx_binding_version") { 67 | exclude module: 'rxjava' 68 | exclude module: 'rxandroid' 69 | } 70 | implementation("com.jakewharton.rxbinding2:rxbinding-appcompat-v7:$rx_binding_version") { 71 | exclude module: 'rxjava' 72 | exclude module: 'rxandroid' 73 | } 74 | 75 | kapt "com.jakewharton:butterknife-compiler:$butter_knife_version" 76 | } 77 | 78 | buildscript { 79 | dependencies { 80 | classpath "com.jakewharton:butterknife-gradle-plugin:$butter_knife_version" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/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 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/App.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo 2 | 3 | import android.app.Application 4 | import com.amitshekhar.DebugDB 5 | import com.foolchen.lib.tracker.Tracker 6 | import com.foolchen.lib.tracker.data.TrackerMode 7 | import com.foolchen.lib.tracker.lifecycle.ITrackerContext 8 | 9 | /** 10 | * 演示用的Application 11 | * @author chenchong 12 | * 2017/11/4 13 | * 下午12:08 14 | */ 15 | class App : Application(), ITrackerContext { 16 | override fun onCreate() { 17 | super.onCreate() 18 | // 设定一些通用的属性,这些属性在每次统计事件中都会附带 19 | // 注意:如果此处的属性名与内置属性的名称相同,则内置属性会被覆盖 20 | Tracker.addProperty("附加的属性1", "附加的属性1") 21 | Tracker.addProperty("附加的属性2", "附加的属性2") 22 | // 设定上报数据的主机和接口 23 | // 注意:该方法一定要在Tracker.initialize()方法前调用 24 | // 否则会由于上报地址未初始化,在触发启动事件时导致崩溃 25 | Tracker.setService(BuildConfig.SERVICE_HOST, BuildConfig.SERVICE_PATH) 26 | // 设定上报数据的项目名称 27 | Tracker.setProjectName(BuildConfig.PROJECT_NAME) 28 | // 设定上报数据的模式 29 | Tracker.setMode(TrackerMode.DEBUG_ONLY) 30 | // 初始化AndroidTracker 31 | Tracker.initialize(this) 32 | 33 | DebugDB.getAddressLog() 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo 2 | 3 | import android.content.Context 4 | import android.support.v7.app.AppCompatActivity 5 | import com.foolchen.lib.tracker.lifecycle.ITrackerIgnore 6 | import com.foolchen.lib.tracker.lifecycle.ITrackerHelper 7 | 8 | /** 9 | * Activity的基类 10 | * @author chenchong 11 | * 2017/11/23 12 | * 下午3:18 13 | */ 14 | open class BaseActivity : AppCompatActivity(), ITrackerHelper, ITrackerIgnore { 15 | /////////////////////////////////////////////////////////////////////////// 16 | // 该类实现ITrackerHelper接口,此处两个方法全部返回null 17 | // 则页面名称(别名)会直接取使用canonicalName来当做标题 18 | // 并且不会有附加的属性 19 | /////////////////////////////////////////////////////////////////////////// 20 | override fun getTrackName(context: Context): String? = null 21 | 22 | override fun getTrackProperties(context: Context): Map? = null 23 | 24 | /////////////////////////////////////////////////////////////////////////// 25 | // ITrackerIgnore接口用于确定当前Activity中是否包含Fragment 26 | // 如果返回值为true,则表明当前Activity中有包含Fragment,则此时不会对Activity进行统计 27 | // 如果返回值为false,则表明当前Activity中不包含Fragment,则此时会对Activity进行统计 28 | // 此处默认不包含Fragment,如有需要应该在子类中覆写该方法并修改返回值 29 | /////////////////////////////////////////////////////////////////////////// 30 | override fun isIgnored(): Boolean = false 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/FragmentContainerActivity.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import com.foolchen.lib.tracker.lifecycle.ITrackerIgnore 6 | 7 | /** 8 | * 用于显示Fragment的Activity,本身不应该被统计 9 | * @author chenchong 10 | * 2017/11/4 11 | * 下午5:00 12 | */ 13 | class FragmentContainerActivity : AppCompatActivity(), ITrackerIgnore { 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | if (intent.extras?.getBoolean("useToolBar") == true) { 17 | setTheme(R.style.AppTheme_NoActionBar) 18 | } 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_fragment_container) 21 | 22 | val fragment = createFragment() 23 | supportFragmentManager.beginTransaction().replace(R.id.fragment_container, fragment, 24 | fragment.javaClass.simpleName) 25 | .commit() 26 | } 27 | 28 | override fun isIgnored() = true 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.v7.widget.LinearLayoutManager 6 | import android.support.v7.widget.RecyclerView 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.TextView 11 | import com.foolchen.lib.tracker.Tracker 12 | import com.foolchen.lib.tracker.demo.data.Demo 13 | import com.foolchen.lib.tracker.demo.fragments.* 14 | import kotlinx.android.synthetic.main.activity_main.* 15 | 16 | class MainActivity : BaseActivity() { 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_main) 20 | Tracker.trackEvent("MainActivity的自定义追踪事件", null) 21 | 22 | // 简单的Fragment 23 | // 可以显示/隐藏的Fragment 24 | // 内嵌子Fragment的Fragment 25 | // 内嵌子Fragment并且可显隐的Fragment 26 | // ViewPager+FragmentStatePagerAdapter 27 | // ViewPager+FragmentStatePagerAdapter+内嵌 28 | val adapter = FragmentsAdapter( 29 | listOf( 30 | Demo("简单的Fragment", SimpleFragment::class.java.name, buildArgs(false, false)), 31 | Demo("内嵌的Fragment", SimpleFragment::class.java.name, buildArgs(false, true)), 32 | Demo("内嵌可显隐的Fragment", SimpleFragment::class.java.name, buildArgs(true, true)), 33 | Demo("ViewPager+Fragment", ViewPagerFragment::class.java.name, buildArgs(false, false)), 34 | Demo("ViewPager+Fragment+内嵌Fragment", ViewPagerFragment::class.java.name, 35 | buildArgs(false, true)), 36 | Demo("AdapterView点击事件Fragment", 37 | AdapterViewFragment::class.java.name, buildArgs(false, false)), 38 | Demo("模拟登录及渠道号", 39 | MockLoginFragment::class.java.name, buildArgs(false, false)), 40 | Demo("ButterKnife点击事件测试", 41 | ButterKnifeFragment::class.java.name, buildArgs(false, false)), 42 | Demo("Rx-Binding事件测试", 43 | RxBindingFragment::class.java.name, buildArgs(false, false, true)) 44 | ) 45 | ) 46 | 47 | rv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) 48 | rv.adapter = adapter 49 | 50 | title = getTrackName(this) 51 | } 52 | 53 | override fun getTrackName(context: Context) = "主Activity" 54 | 55 | override fun getTrackProperties(context: Context): Map? = null 56 | 57 | private fun buildArgs(isVisibilityEnable: Boolean, isChildrenEnable: Boolean, 58 | useToolBar: Boolean = false): Bundle { 59 | val args = Bundle() 60 | args.putBoolean("visibility_enable", isVisibilityEnable) 61 | args.putBoolean("children_enable", isChildrenEnable) 62 | args.putBoolean("useToolBar", useToolBar) 63 | return args 64 | } 65 | 66 | private inner class FragmentsAdapter( 67 | private val demos: List) : RecyclerView.Adapter() { 68 | 69 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FragmentsHolder { 70 | val itemView = LayoutInflater.from(parent.context).inflate( 71 | android.R.layout.simple_list_item_1, 72 | parent, 73 | false) 74 | return FragmentsHolder(itemView) 75 | } 76 | 77 | override fun onBindViewHolder(holder: FragmentsHolder, position: Int) { 78 | val item = getItem(position) 79 | holder.textView?.text = item.desc 80 | holder.itemView?.tag = item 81 | holder.itemView?.setOnClickListener { 82 | startFragment(item.name, item.args) 83 | if (position % 2 == 0) {// 在position=0,2,4...时忽略点击事件 84 | Tracker.ignoreView(holder.itemView) 85 | } 86 | } 87 | } 88 | 89 | override fun getItemCount(): Int = this.demos.size 90 | 91 | fun getItem(position: Int) = this.demos[position] 92 | } 93 | 94 | private class FragmentsHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 95 | val textView: TextView? = itemView.findViewById(android.R.id.text1) 96 | 97 | } 98 | } 99 | 100 | 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.support.v4.app.Fragment 7 | 8 | fun Activity.startFragment(fragment: String, args: Bundle? = null) { 9 | val intent = Intent(this, FragmentContainerActivity::class.java) 10 | intent.putExtra("fragment", fragment) 11 | if (args != null) { 12 | intent.putExtras(args) 13 | } 14 | startActivity(intent) 15 | } 16 | 17 | fun Fragment.startFragment(fragment: String, args: Bundle? = null) { 18 | val intent = Intent(context, FragmentContainerActivity::class.java) 19 | intent.putExtra("fragment", fragment) 20 | if (args != null) { 21 | intent.putExtras(args) 22 | } 23 | startActivity(intent) 24 | } 25 | 26 | fun Activity.createFragment() = Fragment.instantiate(this, intent.getStringExtra("fragment"), 27 | intent.extras)!! -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/data/Demo.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.data 2 | 3 | import android.os.Bundle 4 | 5 | data class Demo(val desc: String, val name: String, val args: Bundle) -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/fragments/AdapterViewFragment.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.AdapterView 8 | import android.widget.BaseAdapter 9 | import android.widget.TextView 10 | import android.widget.Toast 11 | import com.foolchen.lib.tracker.demo.R 12 | import kotlinx.android.synthetic.main.fragment_adapter_view.* 13 | 14 | /** 15 | * 包含ListView以及GridView的Fragment,用于验证AdapterView的点击统计 16 | * @author chenchong 17 | * 2017/11/28 18 | * 上午10:17 19 | */ 20 | class AdapterViewFragment : BaseFragment() { 21 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 22 | savedInstanceState: Bundle?): View = inflater.inflate(R.layout.fragment_adapter_view, 23 | container, false) 24 | 25 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 26 | super.onViewCreated(view, savedInstanceState) 27 | 28 | val items = ArrayList(100) 29 | (0 until 100).mapTo(items) { "item $it" } 30 | 31 | lv.adapter = AdapterViewAdapter(items) 32 | gv.adapter = AdapterViewAdapter(items) 33 | 34 | val listener = AdapterView.OnItemClickListener { _, _, position, _ -> 35 | Toast.makeText(this@AdapterViewFragment.context, items[position], Toast.LENGTH_LONG).show() 36 | } 37 | lv.onItemClickListener = listener 38 | gv.onItemClickListener = listener 39 | } 40 | 41 | private inner class AdapterViewAdapter(val items: List) : BaseAdapter() { 42 | 43 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { 44 | var view = convertView 45 | if (view == null) { 46 | view = LayoutInflater.from(parent.context).inflate( 47 | R.layout.item_adapter_view, parent, false) 48 | } 49 | 50 | with(view?.findViewById(R.id.tv_clickable)) { 51 | this?.text = getString(R.string.text_clickable_mask, position.toString()) 52 | this?.setOnClickListener { 53 | Toast.makeText(context, this.text, Toast.LENGTH_SHORT).show() 54 | } 55 | } 56 | 57 | view?.findViewById(R.id.tv_not_clickable)?.text = getString( 58 | R.string.text_not_clickable_mask, position.toString()) 59 | 60 | return view!! 61 | } 62 | 63 | override fun getItem(position: Int) = items[position] 64 | 65 | override fun getItemId(position: Int) = position.toLong() 66 | 67 | override fun getCount() = items.size 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/fragments/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.fragments 2 | 3 | import android.content.Context 4 | import android.support.v4.app.Fragment 5 | import com.foolchen.lib.tracker.Tracker 6 | import com.foolchen.lib.tracker.lifecycle.ITrackerHelper 7 | import com.foolchen.lib.tracker.lifecycle.ITrackerIgnore 8 | 9 | /** 10 | * Fragment的基类 11 | * @author chenchong 12 | * 2017/11/23 13 | * 下午3:28 14 | */ 15 | open class BaseFragment : Fragment(), ITrackerHelper, ITrackerIgnore { 16 | 17 | /////////////////////////////////////////////////////////////////////////// 18 | // Tracker.setUserVisibleHint()和Tracker.onHiddenChanged()方法用于同步Fragment 19 | // 的可见性,解决在Fragment显隐/与ViewPager结合使用时无法触发生命周期的问题 20 | /////////////////////////////////////////////////////////////////////////// 21 | override fun setUserVisibleHint(isVisibleToUser: Boolean) { 22 | super.setUserVisibleHint(isVisibleToUser) 23 | Tracker.setUserVisibleHint(this, isVisibleToUser) 24 | } 25 | 26 | override fun onHiddenChanged(hidden: Boolean) { 27 | super.onHiddenChanged(hidden) 28 | Tracker.onHiddenChanged(this, hidden) 29 | } 30 | 31 | /////////////////////////////////////////////////////////////////////////// 32 | // 该类实现ITrackerHelper接口,此处两个方法全部返回null 33 | // 则页面名称(别名)会直接取使用canonicalName来当做标题 34 | // 并且不会有附加的属性 35 | /////////////////////////////////////////////////////////////////////////// 36 | override fun getTrackName(context: Context): String? = null 37 | 38 | override fun getTrackProperties(context: Context): Map? = null 39 | 40 | /////////////////////////////////////////////////////////////////////////// 41 | // ITrackerIgnore接口用于确定当前Fragment中是否包含子Fragment 42 | // 如果返回值为true,则表明当前Fragment中有包含子Fragment,则此时不会对当前Fragment进行统计 43 | // 如果返回值为false,则表明当前Fragment中不包含子Fragment,则此时会对当前Fragment进行统计 44 | // 此处默认不包含子Fragment,如有需要应该在子类中覆写该方法并修改返回值 45 | /////////////////////////////////////////////////////////////////////////// 46 | override fun isIgnored(): Boolean = false 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/fragments/ButterKnifeFragment.java: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | import butterknife.BindView; 14 | import butterknife.ButterKnife; 15 | import butterknife.OnClick; 16 | import butterknife.Unbinder; 17 | import com.foolchen.lib.tracker.Tracker; 18 | import com.foolchen.lib.tracker.demo.R; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | /** 23 | * 验证ButterKnife点击的Fragment 24 | * 25 | * @author chenchong 26 | * 2017/11/29 27 | * 下午2:33 28 | */ 29 | 30 | public class ButterKnifeFragment extends BaseFragment { 31 | 32 | @BindView(R.id.rv) RecyclerView mRv; 33 | private Unbinder mUnbinder; 34 | 35 | @Nullable @Override 36 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 37 | @Nullable Bundle savedInstanceState) { 38 | View view = inflater.inflate(R.layout.fragment_butter_knife, container, false); 39 | mUnbinder = ButterKnife.bind(this, view); 40 | return view; 41 | } 42 | 43 | @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 44 | super.onViewCreated(view, savedInstanceState); 45 | mRv.setLayoutManager(new LinearLayoutManager(getContext())); 46 | } 47 | 48 | @Override public void onDestroyView() { 49 | mUnbinder.unbind(); 50 | super.onDestroyView(); 51 | } 52 | 53 | @OnClick(R.id.btn_apply_data) public void apply() { 54 | mRv.setAdapter(new ButterKnifeAdapter()); 55 | } 56 | 57 | static class ButterKnifeAdapter extends RecyclerView.Adapter { 58 | 59 | @NonNull @Override 60 | public ButterKnifeHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 61 | return new ButterKnifeHolder(LayoutInflater.from(parent.getContext()) 62 | .inflate(R.layout.item_butter_knife, parent, false)); 63 | } 64 | 65 | @Override public void onBindViewHolder(@NonNull ButterKnifeHolder holder, int position) { 66 | holder.mTvClickable.setText(holder.itemView.getContext() 67 | .getString(R.string.text_clickable_mask, String.valueOf(position))); 68 | holder.mTvNotClickable.setText(holder.itemView.getContext() 69 | .getString(R.string.text_not_clickable_mask, String.valueOf(position))); 70 | } 71 | 72 | @Override public int getItemCount() { 73 | return 100; 74 | } 75 | } 76 | 77 | static class ButterKnifeHolder extends RecyclerView.ViewHolder { 78 | @BindView(R.id.tv_clickable) TextView mTvClickable; 79 | @BindView(R.id.tv_not_clickable) TextView mTvNotClickable; 80 | 81 | ButterKnifeHolder(View itemView) { 82 | super(itemView); 83 | ButterKnife.bind(this, itemView); 84 | itemView.setOnClickListener(new View.OnClickListener() { 85 | @Override public void onClick(View v) { 86 | Toast.makeText(v.getContext(), "ItemView " + getAdapterPosition(), Toast.LENGTH_SHORT) 87 | .show(); 88 | } 89 | }); 90 | } 91 | 92 | @OnClick(R.id.tv_clickable) public void click(View view) { 93 | Toast.makeText(view.getContext(), ((TextView) view).getText().toString(), Toast.LENGTH_SHORT) 94 | .show(); 95 | Map map = new HashMap<>(); 96 | map.put("附加的View属性", view.toString()); 97 | Tracker.INSTANCE.trackView(view, map); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/fragments/MockLoginFragment.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.foolchen.lib.tracker.Tracker 8 | import com.foolchen.lib.tracker.demo.R 9 | import kotlinx.android.synthetic.main.fragment_mock_login.* 10 | 11 | /** 12 | * 该Fragment用于模拟登录(用于设置统计的用户id等) 13 | * @author chenchong 14 | * 2017/11/29 15 | * 上午9:16 16 | */ 17 | class MockLoginFragment : BaseFragment() { 18 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 19 | savedInstanceState: Bundle?): View = inflater.inflate(R.layout.fragment_mock_login, container, 20 | false) 21 | 22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 23 | super.onViewCreated(view, savedInstanceState) 24 | btn_apply_user_id.setOnClickListener { 25 | val text = tie_user_id?.text?.toString() ?: "" 26 | if (text.isBlank()) { 27 | Tracker.logout() 28 | } else { 29 | Tracker.login(text) 30 | } 31 | } 32 | 33 | btn_apply_channel_id.setOnClickListener { 34 | val text = tie_channel_id?.text?.toString() ?: "" 35 | Tracker.setChannelId(text) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/fragments/NestedFragment.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.fragments 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.foolchen.lib.tracker.demo.R 9 | import kotlinx.android.synthetic.main.fragment_nested.* 10 | 11 | /** 12 | * 内部嵌套的Fragment 13 | * @author chenchong 14 | * 2017/11/23 15 | * 下午4:35 16 | */ 17 | class NestedFragment : BaseFragment() { 18 | 19 | private var desc: String? = null 20 | 21 | companion object { 22 | private val KEY_DESC = "desc" 23 | 24 | fun newInstance(desc: String? = null): NestedFragment { 25 | val f = NestedFragment() 26 | val args = Bundle() 27 | args.putString(KEY_DESC, desc) 28 | f.arguments = args 29 | return f 30 | } 31 | } 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | desc = arguments?.getString(KEY_DESC) 36 | } 37 | 38 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 39 | savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_nested, container, 40 | false) 41 | 42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 43 | super.onViewCreated(view, savedInstanceState) 44 | if (!desc.isNullOrEmpty()) { 45 | tv_desc.text = desc 46 | } 47 | } 48 | 49 | override fun getTrackName(context: Context): String? = tv_desc?.text.toString() 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/fragments/PageFragment.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.fragments 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.foolchen.lib.tracker.demo.R 9 | import kotlinx.android.synthetic.main.fragment_simple.* 10 | 11 | /** 12 | * ViewPagerFragment的Item 13 | * @author chenchong 14 | * 2017/11/23 15 | * 下午5:34 16 | */ 17 | class PageFragment : BaseFragment() { 18 | 19 | private var position = 0 20 | private var hasChildren = false 21 | 22 | companion object { 23 | private val KEY_POSITION = "position" 24 | private val KEY_HAS_CHILDREN = "has_children" 25 | 26 | fun newInstance(position: Int, hasChildren: Boolean): PageFragment { 27 | val f = PageFragment() 28 | val args = Bundle() 29 | args.putInt(KEY_POSITION, position) 30 | args.putBoolean(KEY_HAS_CHILDREN, hasChildren) 31 | f.arguments = args 32 | return f 33 | } 34 | } 35 | 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | position = arguments?.getInt(KEY_POSITION) ?: 0 39 | hasChildren = arguments?.getBoolean(KEY_HAS_CHILDREN) ?: false 40 | } 41 | 42 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 43 | savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_simple, 44 | container, false) 45 | 46 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 47 | super.onViewCreated(view, savedInstanceState) 48 | tv_desc.text = getString(R.string.text_vp_item_desc_mask, position) 49 | 50 | if (isIgnored()) { 51 | fragment_container.visibility = View.VISIBLE 52 | childFragmentManager.beginTransaction().replace(R.id.fragment_container, 53 | NestedFragment.newInstance( 54 | getString(R.string.text_vp_nested_fragment_mask, position))).commit() 55 | } else { 56 | fragment_container.visibility = View.GONE 57 | } 58 | } 59 | 60 | override fun isIgnored(): Boolean = hasChildren 61 | 62 | override fun getTrackName(context: Context): String? = "Fragment#${arguments!![KEY_POSITION]}" 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/fragments/RxBindingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.fragments 2 | 3 | import android.os.Bundle 4 | import android.support.design.widget.Snackbar 5 | import android.support.v7.widget.GridLayoutManager 6 | import android.support.v7.widget.RecyclerView 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.TextView 11 | import com.foolchen.lib.tracker.Tracker 12 | import com.foolchen.lib.tracker.demo.R 13 | import com.jakewharton.rxbinding2.support.v7.widget.RxToolbar 14 | import com.jakewharton.rxbinding2.view.RxView 15 | import kotlinx.android.synthetic.main.fragment_rx_binding.* 16 | import java.util.concurrent.TimeUnit 17 | 18 | /** 19 | * rx-binding事件测试 20 | * @author chenchong 21 | * 2018/5/17 22 | * 上午10:09 23 | */ 24 | class RxBindingFragment : BaseFragment() { 25 | 26 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 27 | savedInstanceState: Bundle?): View? { 28 | return inflater.inflate(R.layout.fragment_rx_binding, container, false) 29 | } 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | 34 | tool_bar.title = "Rx-Binding" 35 | tool_bar.inflateMenu(R.menu.menu_rx_binding) 36 | RxToolbar.itemClicks(tool_bar) 37 | .subscribe { menuItem -> 38 | val map = HashMap() 39 | map["custom_action"] = "自定义行为_点击条目_${menuItem.title}" 40 | Tracker.trackView(tool_bar.findViewById(menuItem.itemId), map) 41 | Snackbar.make(cl, menuItem.title.toString(), Snackbar.LENGTH_INDEFINITE) 42 | .setAction("关闭", { 43 | }).show() 44 | } 45 | tool_bar.setNavigationIcon(R.drawable.ic_back_material) 46 | tool_bar.navigationContentDescription = "back" 47 | RxToolbar.navigationClicks(tool_bar) 48 | .subscribe { 49 | val map = HashMap() 50 | map["custom_action"] = "自定义行为_关闭页面" 51 | val childCount = tool_bar.childCount 52 | for (i in 0..childCount) { 53 | val childAt = tool_bar.getChildAt(i) 54 | if (childAt.contentDescription == "back") { 55 | Tracker.trackView(childAt, map) 56 | break 57 | } 58 | } 59 | activity?.finish() 60 | } 61 | 62 | rv_data.layoutManager = GridLayoutManager(context, 3) 63 | 64 | RxView.clicks(acb_fill_data) 65 | .throttleFirst(1500, TimeUnit.MILLISECONDS) 66 | .subscribe { 67 | // 触发填充数据 68 | val map = HashMap() 69 | map["custom_action"] = "自定义行为_填充数据" 70 | Tracker.trackView(acb_fill_data, map) 71 | 72 | val data = ArrayList() 73 | for (i in 0 until 100) { 74 | data.add("Item_$i") 75 | } 76 | 77 | rv_data.adapter = RxBindingAdapter(data) 78 | } 79 | } 80 | 81 | private inner class RxBindingAdapter( 82 | val data: List) : RecyclerView.Adapter() { 83 | override fun onCreateViewHolder(parent: ViewGroup, 84 | viewType: Int): RxBindingHolder = RxBindingHolder( 85 | LayoutInflater.from(parent.context).inflate(R.layout.item_common, parent, false)) 86 | 87 | override fun getItemCount(): Int = data.size 88 | 89 | override fun onBindViewHolder(holder: RxBindingHolder, position: Int) { 90 | (holder.itemView as? TextView)?.text = data[position] 91 | } 92 | } 93 | 94 | private inner class RxBindingHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 95 | init { 96 | RxView.clicks(itemView) 97 | .throttleFirst(1500, TimeUnit.MILLISECONDS) 98 | .subscribe { 99 | val text = (itemView as? TextView)?.text?.toString() ?: "" 100 | val map = HashMap() 101 | map["custom_action"] = "自定义行为_点击条目_$text" 102 | Tracker.trackView(itemView, map) 103 | 104 | Snackbar.make(cl, text, Snackbar.LENGTH_INDEFINITE) 105 | .setAction("关闭", { view -> 106 | val localMap = HashMap() 107 | localMap["custom_action"] = "自定义行为_SnackBar_$text" 108 | Tracker.trackView(view, localMap) 109 | }) 110 | .show() 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/fragments/SimpleFragment.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.fragments 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.view.* 6 | import com.foolchen.lib.tracker.demo.R 7 | import kotlinx.android.synthetic.main.fragment_simple.* 8 | 9 | /** 10 | * 简单的Fragment,内部不包含任何其他的Framgent 11 | * @author chenchong 12 | * 2017/11/23 13 | * 下午3:33 14 | */ 15 | class SimpleFragment : BaseFragment() { 16 | 17 | private var isVisibilityEnable = false 18 | private var isChildrenEnable = false 19 | 20 | private var nestedFragment: Fragment? = null 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | isVisibilityEnable = arguments?.getBoolean("visibility_enable") ?: false 25 | isChildrenEnable = arguments?.getBoolean("children_enable") ?: false 26 | 27 | setHasOptionsMenu(isChildrenEnable) 28 | } 29 | 30 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 31 | savedInstanceState: Bundle?): View? = 32 | inflater.inflate(R.layout.fragment_simple, container, false) 33 | 34 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 35 | super.onViewCreated(view, savedInstanceState) 36 | 37 | if (isChildrenEnable) { 38 | fragment_container.visibility = View.VISIBLE 39 | 40 | nestedFragment = NestedFragment.newInstance() 41 | childFragmentManager.beginTransaction().replace(R.id.fragment_container, 42 | nestedFragment).commit() 43 | 44 | } else { 45 | fragment_container.visibility = View.GONE 46 | } 47 | } 48 | 49 | override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { 50 | inflater?.inflate(R.menu.menu_fragments, menu) 51 | } 52 | 53 | override fun onPrepareOptionsMenu(menu: Menu?) { 54 | menu?.findItem(R.id.action_show)?.isVisible = isVisibilityEnable 55 | menu?.findItem(R.id.action_hide)?.isVisible = isVisibilityEnable 56 | } 57 | 58 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 59 | val itemId = item?.itemId 60 | when (itemId) { 61 | R.id.action_show -> { 62 | if (nestedFragment?.isHidden == true) { 63 | childFragmentManager.beginTransaction().show(nestedFragment).commit() 64 | } 65 | return true 66 | } 67 | R.id.action_hide -> { 68 | if (nestedFragment?.isHidden == false) { 69 | childFragmentManager.beginTransaction().hide(nestedFragment).commit() 70 | } 71 | return true 72 | } 73 | } 74 | 75 | return super.onOptionsItemSelected(item) 76 | } 77 | 78 | override fun isIgnored(): Boolean = isChildrenEnable 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/foolchen/lib/tracker/demo/fragments/ViewPagerFragment.kt: -------------------------------------------------------------------------------- 1 | package com.foolchen.lib.tracker.demo.fragments 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.support.v4.app.FragmentManager 6 | import android.support.v4.app.FragmentStatePagerAdapter 7 | import android.view.* 8 | import com.foolchen.lib.tracker.demo.R 9 | import kotlinx.android.synthetic.main.fragment_view_pager.* 10 | 11 | /** 12 | * ViewPager+Fragment的实现 13 | * @author chenchong 14 | * 2017/11/23 15 | * 下午5:24 16 | */ 17 | class ViewPagerFragment : BaseFragment() { 18 | private var nestedFragment: NestedFragment? = null 19 | private var isChildrenEnable = false 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | isChildrenEnable = arguments?.getBoolean("children_enable") ?: false 24 | } 25 | 26 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 27 | savedInstanceState: Bundle?): View? = 28 | inflater.inflate(R.layout.fragment_view_pager, container, false) 29 | 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | vp.adapter = VpAdapter(childFragmentManager, isChildrenEnable) 34 | stl.setViewPager(vp) 35 | } 36 | 37 | override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { 38 | inflater?.inflate(R.menu.menu_fragments, menu) 39 | } 40 | 41 | 42 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 43 | when (item?.itemId) { 44 | R.id.action_show -> { 45 | 46 | return true 47 | } 48 | R.id.action_hide -> { 49 | return true 50 | } 51 | } 52 | 53 | return super.onOptionsItemSelected(item) 54 | } 55 | 56 | override fun isIgnored(): Boolean = true 57 | } 58 | 59 | private class VpAdapter : FragmentStatePagerAdapter { 60 | private val isChildrenEnable: Boolean 61 | 62 | constructor(fm: FragmentManager, isChildrenEnable: Boolean) : super(fm) { 63 | this.isChildrenEnable = isChildrenEnable 64 | } 65 | 66 | override fun getCount(): Int = 20 67 | 68 | 69 | override fun getItem(position: Int): Fragment = PageFragment.newInstance(position, 70 | isChildrenEnable) 71 | 72 | override fun getPageTitle(position: Int): CharSequence? { 73 | return "Fragment#$position" 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 24 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_fragment_container.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_adapter_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_butter_knife.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |