├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── tamic
│ │ └── antodotting
│ │ ├── LoginActivity.java
│ │ ├── StatAppliation.java
│ │ ├── TamicActivity.java
│ │ └── ViewPath.java
│ └── res
│ ├── layout
│ └── activity_login.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # Intellij
38 | *.iml
39 | .idea/workspace.xml
40 | .idea/tasks.xml
41 | .idea/gradle.xml
42 | .idea/dictionaries
43 | .idea/libraries
44 | .idea
45 |
46 | # Keystore files
47 | *.jks
48 |
49 | # External native build folder generated in Android Studio 2.2 and later
50 | .externalNativeBuild
51 |
52 | # Google Services (e.g. APIs or Firebase)
53 | google-services.json
54 |
55 | # Freeline
56 | freeline.py
57 | freeline/
58 | freeline_project_description.json
--------------------------------------------------------------------------------
/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 | # TamicAppMonitoring
2 | Android App 无痕迹全埋点方案
3 |
4 | 本次基于的埋点框架: https://github.com/Tamicer/SkyMonitoring
5 |
6 |
7 | # 背景
8 |
9 | 目前统计已经是一个产品常见的需求,尤其在业务模式探索的前期,埋点功能更是必不可少的功能,下面将介绍最简单的app全埋点方案!
10 |
11 |
12 |
13 | # 什么是数据埋点
14 |
15 | 数据埋点是一般项目采用统计UV,PV,Action,Time等一系列的数据信息,对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。
16 |
17 |
18 |
19 | # 为什么要数据埋点
20 |
21 | 产品或运营分析人员,基于埋点数据分析需要,对用户行为的每一个事件进行埋点布置,并通过SDK上报埋点的数据结果,进行分析,并进一步优化产品或指导运营。
22 |
23 | # 数据埋点包括哪些
24 |
25 | 这里有我之前写的一篇文章[App优质精准的用户行为统计和日志打捞方案](http://blog.csdn.net/sk719887916/article/details/50931485)
26 |
27 | 地址:http://blog.csdn.net/sk719887916/article/details/50931485
28 |
29 |
30 | # 数据埋点采集模式
31 |
32 | ##自动埋点
33 |
34 | App通过代理,调用Sdk相关API,进行的将数据埋点上报的模式.
35 |
36 | ## 无痕埋点
37 |
38 | 无需通过专门提供代理类,直接由sdk提供相关接口,或者通过编译工具,预编译替换代码等,直接由sdk全部负责采集上报
39 |
40 | ## 可视化埋点
41 |
42 | 可视化埋点指 前端或者app端基于dom 元素和控件所精准自动埋点的上报的方案。
43 |
44 |
45 | ## 对比分析:
46 |
47 | ### 自动埋点:
48 |
49 | 缺点:
50 | 1 开发人员工作量大,需对业务提供唯一的ID,来区分每一个业务,无论是否提供sdk代理,业务开发人员至少需要多次调用sdk相关API.
51 |
52 | 2 业务人员和产品沟通成本提高,需要对具体业务制定相关的业务标识,以便于产品分析和统计
53 |
54 | 优点:
55 |
56 | 产品运营工作量少,对照业务映射表,就能分析出还原相关业务场景, 数据比较精细,无需大量的加工和处理。
57 |
58 | ### 无痕埋点
59 |
60 | 缺点:
61 |
62 | 1 sdk开发人员需提供一套无痕埋点技术成品,包括能正确获取PV,UV,ACtion,TIme等多项统计指标。前期技术投入大。
63 |
64 | 2 数据量大,需后端落地进行大量处理,并由产品进行自我还原业务员场景。 无论采用智能系统平台,还是通过原生的数据库查询数据,都是一种大量的分析精力。
65 |
66 | 优点:
67 |
68 | 1 开发人员工作量小,无需对业务标识进行唯一区分,由sdk自动进行生成,ID规则由sdk和产品进行约定。减少业务人员的沟通成本和使用步骤。
69 |
70 | 2 数据量全面,覆盖面广,产品可按需进行分析。做到毫无遗漏。
71 |
72 | 3 支持动态页面和局部动效的统计。
73 |
74 | ## 可视化埋点
75 |
76 |
77 | **优点:**
78 |
79 | 1 相对数据量而言
80 | 相比较于无埋点相而言对较低,但是这个可视化元素的识别技术是客户端或者前端所要实现的,唯一id生成也无需客户端去自定义规则,这套生成规则由相关产品在自动化工具的情况下生成配置表,下发到客户端,再由客户端按坑就班到相关界面去实现。
81 |
82 | 2 数据量相对精确
83 |
84 | **缺点:**
85 |
86 | 1 可视化工具的平台的搭建,静态页面的元素识别都需要额外开发。
87 | 2 动态效果可能会遗漏。
88 |
89 |
90 | #实现方案:
91 |
92 | 埋点需求可参考我之前的文章:
93 |
94 | [App优质精准的用户行为统计和日志打捞方案](http://blog.csdn.net/sk719887916/article/details/50931485)
95 |
96 | [App打造自定义的统计SDK](https://www.jianshu.com/p/cd83e81b78aa)
97 |
98 | 自动埋点实际上也是,提供一个base类,由业务类继承base类,在base里面做相关统计api调用,
99 | 可参考我的**github:https://github.com/Tamicer/SkyMonitoring**
100 |
101 |
102 |
103 | ## 核心实现: ##
104 |
105 | 以android作为列子:
106 |
107 |
108 | 提供自动遍历元素 并能扑捉点击的控件的activity, 并能在生命周期统计pv的打开和关闭,调用我开源的`SkyMonitoring`的对应的api.
109 |
110 |
111 | 复写`dispatchTouchEvent(MotionEvent ev)` 事件函数,确定被点击的view的相关位置,并生成唯一的ID,企业级app都是从服务器下发对应的ID,对应页面去调用埋点sdk Api,实现事件行为`TcStatInterface.initEvent(path.viewTree)`;。
112 |
113 | 这个path就是view的路径,页面的深度路径,包括打开和关闭sdk在SkyMonitoring中已能自动获取。
114 |
115 | 本次demo是id生成规则是按照 :包名+ Activity+ Viewgroup+ Layout+ view + View index + viewID实现的。
116 |
117 | 业务直接去继承`TamicActivity`即可,就能去实现所有可视化view的埋点功能。
118 |
119 |
120 | App项目集成使用,初始化url和相关统计配置字典,这个字典可以从服务器下发下来,我本次只是通过简单的本地文件做实践。
121 |
122 | ```
123 | public class StatAppliation extends Application {
124 |
125 | @Override
126 | public void onCreate() {
127 | super.onCreate();
128 | // you app id
129 | int appId = 21212;
130 | // assets
131 | String fileName = "my_statconfig.json";
132 | String url = "https://github.com/Tamicer/TamicAppMonitoring";
133 | // init statSdk
134 | TcStatInterface.initialize(this, appId, "you app chanel", fileName);
135 | TcStatInterface.setUrl(url);
136 | TcStatInterface.setUploadPolicy(TcStatInterface.UploadPolicy.UPLOAD_POLICY_DEVELOPMENT, TcStatInterface.UPLOAD_TIME_ONE);
137 | }
138 | }
139 |
140 | ```
141 |
142 | 可视化也可以通过aop插桩实现,但是实现起来对代码的入侵性太高,这里不做介绍。
143 |
144 | Aop 插桩对碎片化fragment支持比较好。对这块的介绍可看我以前在公众号推送的一篇文章
145 | :[AOP编程之AspectJ实战实现数据无痕埋点](https://mp.weixin.qq.com/s/neH9JXL5AYzjaAaxF-ZF-g)
146 |
147 | 可参考:
148 | https://www.baidu.com/link?url=FniQOFyj1pd6O5Fz6viRMN3ZgexIKAk7SQ08EgpBU9cHHMszPlm2jRXJ21mkomtY&wd=&eqid=ffc87acf0005fd18000000045a5d98dd
149 |
150 | # 项目地址:
151 | github:https://github.com/Tamicer/TamicAppMonitoring
152 |
153 | >Tamic 原创 http://blog.csdn.net/sk719887916/article/details/79074556
154 |
155 | 第一时间获取我的技术文章请关注微信公众号!
156 |
157 | 
158 |
159 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # Intellij
38 | *.iml
39 | .idea/workspace.xml
40 | .idea/tasks.xml
41 | .idea/gradle.xml
42 | .idea/dictionaries
43 | .idea/libraries
44 | .idea
45 |
46 | # Keystore files
47 | *.jks
48 |
49 | # External native build folder generated in Android Studio 2.2 and later
50 | .externalNativeBuild
51 |
52 | # Google Services (e.g. APIs or Firebase)
53 | google-services.json
54 |
55 | # Freeline
56 | freeline.py
57 | freeline/
58 | freeline_project_description.json
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | buildToolsVersion "26.0.1"
6 | defaultConfig {
7 | applicationId "com.tamic.antodotting"
8 | minSdkVersion 14
9 | targetSdkVersion 26
10 | versionCode 6
11 | versionName "3.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
28 | compile 'com.android.support:design:26.0.0-alpha1'
29 | testCompile 'junit:junit:4.12'
30 | compile 'com.tamic:StatInterface:2.1'
31 | }
32 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Users\liuyongkui726\AppData\Local\Android\android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tamic/antodotting/LoginActivity.java:
--------------------------------------------------------------------------------
1 | package com.tamic.antodotting;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.annotation.TargetApi;
6 | import android.content.pm.PackageManager;
7 | import android.app.LoaderManager.LoaderCallbacks;
8 |
9 | import android.content.CursorLoader;
10 | import android.content.Loader;
11 | import android.database.Cursor;
12 | import android.net.Uri;
13 | import android.os.AsyncTask;
14 |
15 | import android.os.Build;
16 | import android.os.Bundle;
17 | import android.provider.ContactsContract;
18 | import android.text.TextUtils;
19 | import android.view.KeyEvent;
20 | import android.view.View;
21 | import android.view.View.OnClickListener;
22 | import android.view.inputmethod.EditorInfo;
23 | import android.widget.ArrayAdapter;
24 | import android.widget.AutoCompleteTextView;
25 | import android.widget.Button;
26 | import android.widget.EditText;
27 | import android.widget.TextView;
28 | import java.util.ArrayList;
29 | import java.util.List;
30 |
31 | import static android.Manifest.permission.READ_CONTACTS;
32 |
33 | /**
34 | * A login screen that offers login via email/password.
35 | */
36 |
37 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
38 | public class LoginActivity extends TamicActivity implements LoaderCallbacks {
39 |
40 | /**
41 | * Id to identity READ_CONTACTS permission request.
42 | */
43 | private static final int REQUEST_READ_CONTACTS = 0;
44 |
45 | /**
46 | * A dummy authentication store containing known user names and passwords.
47 | * TODO: remove after connecting to a real authentication system.
48 | */
49 | private static final String[] DUMMY_CREDENTIALS = new String[]{
50 | "foo@example.com:hello", "bar@example.com:world"
51 | };
52 | /**
53 | * Keep track of the login task to ensure we can cancel it if requested.
54 | */
55 | private UserLoginTask mAuthTask = null;
56 |
57 | // UI references.
58 | private AutoCompleteTextView mEmailView;
59 | private EditText mPasswordView;
60 | private View mProgressView;
61 | private View mLoginFormView;
62 |
63 | @TargetApi(Build.VERSION_CODES.CUPCAKE)
64 | @Override
65 | protected void onCreate(Bundle savedInstanceState) {
66 | super.onCreate(savedInstanceState);
67 | setContentView(R.layout.activity_login);
68 | // Set up the login form.
69 | mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
70 | //populateAutoComplete();
71 |
72 | mPasswordView = (EditText) findViewById(R.id.password);
73 | mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
74 | @Override
75 | public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
76 | if (id == R.id.login || id == EditorInfo.IME_NULL) {
77 | attemptLogin();
78 | return true;
79 | }
80 | return false;
81 | }
82 | });
83 |
84 | Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
85 | mEmailSignInButton.setOnClickListener(new OnClickListener() {
86 | @Override
87 | public void onClick(View view) {
88 | attemptLogin();
89 | }
90 | });
91 |
92 | mLoginFormView = findViewById(R.id.login_form);
93 | mProgressView = findViewById(R.id.login_progress);
94 | }
95 |
96 | private void populateAutoComplete() {
97 | if (!mayRequestContacts()) {
98 | return;
99 | }
100 |
101 | getLoaderManager().initLoader(0, null, this);
102 | }
103 |
104 | private boolean mayRequestContacts() {
105 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
106 | return true;
107 | }
108 | if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
109 | return true;
110 | }
111 | /* if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
112 | Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
113 | .setAction(android.R.string.ok, new View.OnClickListener() {
114 | @Override
115 | @TargetApi(Build.VERSION_CODES.M)
116 | public void onClick(View v) {
117 | requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
118 | }
119 | });
120 | } else {
121 | requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
122 | }*/
123 | return false;
124 | }
125 |
126 | /**
127 | * Callback received when a permissions request has been completed.
128 | */
129 | @Override
130 | public void onRequestPermissionsResult(int requestCode, String[] permissions,
131 | int[] grantResults) {
132 | if (requestCode == REQUEST_READ_CONTACTS) {
133 | if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
134 | populateAutoComplete();
135 | }
136 | }
137 | }
138 |
139 |
140 | /**
141 | * Attempts to sign in or register the account specified by the login form.
142 | * If there are form errors (invalid email, missing fields, etc.), the
143 | * errors are presented and no actual login attempt is made.
144 | */
145 | private void attemptLogin() {
146 | if (mAuthTask != null) {
147 | return;
148 | }
149 |
150 | // Reset errors.
151 | mEmailView.setError(null);
152 | mPasswordView.setError(null);
153 |
154 | // Store values at the time of the login attempt.
155 | String email = mEmailView.getText().toString();
156 | String password = mPasswordView.getText().toString();
157 |
158 | boolean cancel = false;
159 | View focusView = null;
160 |
161 | // Check for a valid password, if the user entered one.
162 | if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
163 | mPasswordView.setError(getString(R.string.error_invalid_password));
164 | focusView = mPasswordView;
165 | cancel = true;
166 | }
167 |
168 | // Check for a valid email address.
169 | if (TextUtils.isEmpty(email)) {
170 | mEmailView.setError(getString(R.string.error_field_required));
171 | focusView = mEmailView;
172 | cancel = true;
173 | } else if (!isEmailValid(email)) {
174 | mEmailView.setError(getString(R.string.error_invalid_email));
175 | focusView = mEmailView;
176 | cancel = true;
177 | }
178 |
179 | if (cancel) {
180 | // There was an error; don't attempt login and focus the first
181 | // form field with an error.
182 | focusView.requestFocus();
183 | } else {
184 | // Show a progress spinner, and kick off a background task to
185 | // perform the user login attempt.
186 | showProgress(true);
187 | mAuthTask = new UserLoginTask(email, password);
188 | mAuthTask.execute((Void) null);
189 | }
190 | }
191 | private boolean isEmailValid(String email) {
192 | //TODO: Replace this with your own logic
193 | return email.contains("@");
194 | }
195 |
196 | private boolean isPasswordValid(String password) {
197 | //TODO: Replace this with your own logic
198 | return password.length() > 4;
199 | }
200 |
201 | /**
202 | * Shows the progress UI and hides the login form.
203 | */
204 | @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
205 | private void showProgress(final boolean show) {
206 | // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
207 | // for very easy animations. If available, use these APIs to fade-in
208 | // the progress spinner.
209 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
210 | int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
211 |
212 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
213 | mLoginFormView.animate().setDuration(shortAnimTime).alpha(
214 | show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
215 | @Override
216 | public void onAnimationEnd(Animator animation) {
217 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
218 | }
219 | });
220 |
221 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
222 | mProgressView.animate().setDuration(shortAnimTime).alpha(
223 | show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
224 | @Override
225 | public void onAnimationEnd(Animator animation) {
226 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
227 | }
228 | });
229 | } else {
230 | // The ViewPropertyAnimator APIs are not available, so simply show
231 | // and hide the relevant UI components.
232 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
233 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
234 | }
235 | }
236 |
237 | @Override
238 | public Loader onCreateLoader(int i, Bundle bundle) {
239 | return new CursorLoader(this,
240 | // Retrieve data rows for the device user's 'profile' contact.
241 | Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
242 | ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
243 |
244 | // Select only email addresses.
245 | ContactsContract.Contacts.Data.MIMETYPE +
246 | " = ?", new String[]{ContactsContract.CommonDataKinds.Email
247 | .CONTENT_ITEM_TYPE},
248 |
249 | // Show primary email addresses first. Note that there won't be
250 | // a primary email address if the user hasn't specified one.
251 | ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
252 | }
253 |
254 | @Override
255 | public void onLoadFinished(Loader cursorLoader, Cursor cursor) {
256 | List emails = new ArrayList<>();
257 | cursor.moveToFirst();
258 | while (!cursor.isAfterLast()) {
259 | emails.add(cursor.getString(ProfileQuery.ADDRESS));
260 | cursor.moveToNext();
261 | }
262 |
263 | addEmailsToAutoComplete(emails);
264 | }
265 |
266 | @Override
267 | public void onLoaderReset(Loader cursorLoader) {
268 |
269 | }
270 |
271 | private interface ProfileQuery {
272 | String[] PROJECTION = {
273 | ContactsContract.CommonDataKinds.Email.ADDRESS,
274 | ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
275 | };
276 |
277 | int ADDRESS = 0;
278 | int IS_PRIMARY = 1;
279 | }
280 |
281 |
282 | private void addEmailsToAutoComplete(List emailAddressCollection) {
283 | //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
284 | ArrayAdapter adapter =
285 | new ArrayAdapter<>(LoginActivity.this,
286 | android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
287 |
288 | mEmailView.setAdapter(adapter);
289 | }
290 |
291 | /**
292 | * Represents an asynchronous login/registration task used to authenticate
293 | * the user.
294 | */
295 | public class UserLoginTask extends AsyncTask {
296 |
297 | private final String mEmail;
298 | private final String mPassword;
299 |
300 | UserLoginTask(String email, String password) {
301 | mEmail = email;
302 | mPassword = password;
303 | }
304 |
305 | @Override
306 | protected Boolean doInBackground(Void... params) {
307 | // TODO: attempt authentication against a network service.
308 |
309 | try {
310 | // Simulate network access.
311 | Thread.sleep(2000);
312 | } catch (InterruptedException e) {
313 | return false;
314 | }
315 |
316 | for (String credential : DUMMY_CREDENTIALS) {
317 | String[] pieces = credential.split(":");
318 | if (pieces[0].equals(mEmail)) {
319 | // Account exists, return true if the password matches.
320 | return pieces[1].equals(mPassword);
321 | }
322 | }
323 |
324 | // TODO: register the new account here.
325 | return true;
326 | }
327 |
328 | @Override
329 | protected void onPostExecute(final Boolean success) {
330 | mAuthTask = null;
331 | showProgress(false);
332 |
333 | if (success) {
334 | finish();
335 | } else {
336 | mPasswordView.setError(getString(R.string.error_incorrect_password));
337 | mPasswordView.requestFocus();
338 | }
339 | }
340 |
341 | @Override
342 | protected void onCancelled() {
343 | mAuthTask = null;
344 | showProgress(false);
345 | }
346 | }
347 | }
348 |
349 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tamic/antodotting/StatAppliation.java:
--------------------------------------------------------------------------------
1 | package com.tamic.antodotting;
2 | import android.app.Application;
3 |
4 | import com.tamic.statInterface.statsdk.core.TcStatInterface;
5 |
6 |
7 | /**
8 | * Created by LIUYONGKUI726 on 2016-04-13.
9 | */
10 | public class StatAppliation extends Application {
11 |
12 | @Override
13 | public void onCreate() {
14 | super.onCreate();
15 | // you app id
16 | int appId = 21212;
17 | // assets
18 | String fileName = "my_statconfig.json";
19 | String url = "http://www.baidu.com";
20 | // init statSdk
21 | TcStatInterface.initialize(this, appId, "you app chanel", fileName);
22 | TcStatInterface.setUrl(url);
23 | TcStatInterface.setUploadPolicy(TcStatInterface.UploadPolicy.UPLOAD_POLICY_DEVELOPMENT, TcStatInterface.UPLOAD_TIME_ONE);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tamic/antodotting/TamicActivity.java:
--------------------------------------------------------------------------------
1 | package com.tamic.antodotting;
2 |
3 | import android.os.Build;
4 | import android.support.annotation.RequiresApi;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.util.Log;
7 | import android.view.MotionEvent;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.AbsListView;
11 |
12 | import com.tamic.statInterface.statsdk.core.TcStatInterface;
13 |
14 |
15 | /**
16 | * Created by LIUYONGKUI726 on 2017-12-07.
17 | */
18 |
19 | public abstract class TamicActivity extends AppCompatActivity {
20 |
21 | private int statusBarHeight;
22 | View rootView;
23 | String rootViewTree;
24 | String bigDataPrefix;
25 | String bigDataIngorePrefix;
26 | String bigDataEventPrefix;
27 | private String TAG = "Tamic";
28 |
29 | @Override
30 | public void onAttachedToWindow() {
31 | super.onAttachedToWindow();
32 | rootView = getWindow().getDecorView();
33 | rootViewTree = getPackageName() + "." + getClass().getSimpleName();
34 | bigDataPrefix = "Tamic_test";
35 | bigDataIngorePrefix = bigDataPrefix + "";
36 | bigDataEventPrefix = bigDataIngorePrefix + "Igmore";
37 | }
38 |
39 | @RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
40 | @Override
41 | protected void onResume() {
42 | super.onResume();
43 | TcStatInterface.recordPageStart(TamicActivity.this);
44 | }
45 |
46 | @RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
47 | @Override
48 | protected void onPause() {
49 | super.onPause();
50 |
51 | TcStatInterface.recordPageEnd();
52 | }
53 |
54 | @Override
55 | protected void onDestroy() {
56 | super.onDestroy();
57 | }
58 |
59 |
60 | @Override
61 | public boolean dispatchTouchEvent(MotionEvent ev) {
62 |
63 | if (ev.getAction() == MotionEvent.ACTION_DOWN) {
64 | ViewPath path = findClickView(ev);
65 | if (path != null) {
66 | Log.e(TAG, "path -->" + path.viewTree);
67 | TcStatInterface.initEvent(path.viewTree);
68 | }
69 | }
70 | return super.dispatchTouchEvent(ev);
71 | }
72 |
73 | private ViewPath findClickView(MotionEvent ev) {
74 | Log.e(TAG, "bigdata-->findClickView");
75 | ViewPath clickView = new ViewPath(rootView, rootViewTree);
76 | return searchClickView(clickView, ev, 0);
77 | }
78 |
79 |
80 | private ViewPath searchClickView(ViewPath myView, MotionEvent event, int index) {
81 | ViewPath clickView = null;
82 | View view = myView.view;
83 | if (isInView(view, event)) {
84 | //遍历根view下的子view以及所有子view上的控件
85 | // 当第二层不为LinearLayout时,说明系统进行了改造,多了一层,需要多剔除一层
86 | myView.level++;
87 | if (myView.level == 2 && !"LinearLayout".equals(view.getClass().getSimpleName())) {
88 | myView.filterLevelCount++;
89 | }
90 | if (myView.level > myView.filterLevelCount) {
91 | myView.viewTree = myView.viewTree + "." + view.getClass().getSimpleName() + "[" + index + "]";
92 | }
93 | Log.i(TAG, "bigdata-->tag = " + view.getTag());
94 | // 如果Layout有设置特定的tag,则直接返回View,主要用于复合组件的点击事件
95 | if (view.getTag() != null) {
96 | // 主动标记不需要统计时,不进行自动统计
97 | String tag = view.getTag().toString();
98 | if (tag.startsWith(bigDataIngorePrefix)) {
99 | return null;
100 | } else if (tag.startsWith(bigDataPrefix)) {
101 | if (tag.startsWith(bigDataEventPrefix)) {
102 | myView.specifyTag = tag.replace(bigDataEventPrefix, "");
103 | }
104 | return myView;
105 | }
106 | }
107 | if (view instanceof ViewGroup) {
108 | //遇到一些Layout之类的ViewGroup,继续遍历它下面的子View
109 | if (view instanceof AbsListView) {
110 | Log.i(TAG, "bigdata-->AbsListView ");
111 | return null;
112 | }
113 | ViewGroup group = (ViewGroup) view;
114 | int childCount = group.getChildCount();
115 | if (childCount == 0) {
116 | return myView;
117 | }
118 | for (int i = childCount - 1; i >= 0; i--) {
119 | myView.view = group.getChildAt(i);
120 | clickView = searchClickView(myView, event, i);
121 | if (clickView != null) {
122 | return clickView;
123 | }
124 | }
125 | } else {
126 | clickView = myView;
127 | }
128 | }
129 | return clickView;
130 | }
131 |
132 | private boolean isInView(View view, MotionEvent event) {
133 | //能被点击的view必然是可见的
134 | if (view == null || view.getVisibility() != View.VISIBLE) {
135 | return false;
136 | }
137 | int clickX = (int) event.getRawX();
138 | int clickY = (int) event.getRawY();
139 | //如下的view表示Activity中的子View或者控件
140 | int[] location = new int[2];
141 | view.getLocationOnScreen(location);
142 | int x = location[0];
143 | int y = location[1];
144 | int width = view.getWidth();
145 | int height = view.getHeight();
146 | //返回true,则判断这个view被点击了
147 | return clickX > x && clickX < (x + width) && clickY > y && clickY < (y + height);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tamic/antodotting/ViewPath.java:
--------------------------------------------------------------------------------
1 | package com.tamic.antodotting;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * Created by LIUYONGKUI726 on 2017-12-07.
7 | */
8 |
9 | public class ViewPath {
10 |
11 | View view; //view
12 | String viewTree; //view在视图树上的路径
13 | String specifyTag;
14 | int level = 0;//层级默认为0
15 | int filterLevelCount = 3;//需要过滤的层级
16 |
17 | ViewPath(View view, String viewTree) {
18 | this.view = view;
19 | this.viewTree = viewTree;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
21 |
22 |
26 |
27 |
32 |
33 |
36 |
37 |
45 |
46 |
47 |
48 |
51 |
52 |
63 |
64 |
65 |
66 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tamicer/TamicAppMonitoring/7f9312bb655cde51831d506a0e158d04380ae1a3/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tamicer/TamicAppMonitoring/7f9312bb655cde51831d506a0e158d04380ae1a3/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tamicer/TamicAppMonitoring/7f9312bb655cde51831d506a0e158d04380ae1a3/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tamicer/TamicAppMonitoring/7f9312bb655cde51831d506a0e158d04380ae1a3/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tamicer/TamicAppMonitoring/7f9312bb655cde51831d506a0e158d04380ae1a3/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AntoDotting
3 |
4 |
5 | Email
6 | Password (optional)
7 | Sign in or register
8 | Sign in
9 | This email address is invalid
10 | This password is too short
11 | This password is incorrect
12 | This field is required
13 | "Contacts permissions are needed for providing email
14 | completions."
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.2'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------