Repository: markzhai/AndroidPerformanceMonitor
Branch: master
Commit: ed688391cdf9
Files: 86
Total size: 182.8 KB
Directory structure:
gitextract_zx_2a8bu/
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README_CN.md
├── blockcanary-analyzer/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle-mvn-push.gradle
│ ├── gradle.properties
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── github/
│ │ └── moduth/
│ │ └── blockcanary/
│ │ ├── AbstractSampler.java
│ │ ├── BlockCanaryContext.java
│ │ ├── BlockCanaryInternals.java
│ │ ├── BlockInterceptor.java
│ │ ├── CpuSampler.java
│ │ ├── HandlerThreadFactory.java
│ │ ├── LogWriter.java
│ │ ├── LooperMonitor.java
│ │ ├── StackSampler.java
│ │ └── internal/
│ │ ├── BlockInfo.java
│ │ ├── PerformanceUtils.java
│ │ └── ProcessUtils.java
│ └── res/
│ └── values/
│ └── int.xml
├── blockcanary-android/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle-mvn-push.gradle
│ ├── gradle.properties
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── github/
│ │ └── moduth/
│ │ └── blockcanary/
│ │ ├── BlockCanary.java
│ │ ├── DisplayService.java
│ │ ├── SingleThreadFactory.java
│ │ ├── Uploader.java
│ │ └── ui/
│ │ ├── BlockCanaryUi.java
│ │ ├── BlockCanaryUtils.java
│ │ ├── BlockInfoCorruptException.java
│ │ ├── BlockInfoEx.java
│ │ ├── DetailAdapter.java
│ │ ├── DisplayActivity.java
│ │ ├── DisplayConnectorView.java
│ │ └── MoreDetailsView.java
│ └── res/
│ ├── layout/
│ │ ├── block_canary_block_row.xml
│ │ ├── block_canary_display_leak.xml
│ │ ├── block_canary_ref_row.xml
│ │ └── block_canary_ref_top_row.xml
│ ├── values/
│ │ ├── strings.xml
│ │ └── themes.xml
│ ├── values-v14/
│ │ └── themes.xml
│ └── values-v21/
│ └── themes.xml
├── blockcanary-android-no-op/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle-mvn-push.gradle
│ ├── gradle.properties
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── com/
│ └── github/
│ └── moduth/
│ └── blockcanary/
│ ├── BlockCanary.java
│ ├── BlockCanaryContext.java
│ └── internal/
│ └── BlockInfo.java
├── blockcanary-sample/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── blockcanary/
│ │ ├── AppContext.java
│ │ ├── DemoActivity.java
│ │ ├── DemoApplication.java
│ │ └── DemoFragment.java
│ └── res/
│ ├── drawable/
│ │ ├── background_splash.xml
│ │ ├── btn_nor.xml
│ │ ├── btn_pre.xml
│ │ └── btn_select.xml
│ ├── layout/
│ │ ├── activity_demo.xml
│ │ └── activity_main.xml
│ ├── menu/
│ │ └── menu_demo.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── values-zh/
│ └── strings.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# jetbrain project file
.idea/
*.iml
================================================
FILE: CHANGELOG.md
================================================
# Change Log
You can watch releases [on Maven](https://oss.sonatype.org/content/groups/public/com/github/markzhai/).
## Version 1.5 *(2017-02-26)*
Debug mode stop monitor.
## Version 1.4 *(2016-11-02)*
- Bug fix.
- Add onBlock interceptor.
### 1.4.1 (2017-01-19)
- Bug fix
## Version 1.3 *(2016-08-24)*
- Code refactor.
- Support white-list and concern packages.
## Version 1.2 *(2016-03-16)*
- Much faster! It now has almost none side-effect at run-time.
## Version 1.1 *(2016-01-23)*
- Extract blockcanary to three modules.
- Provide default implementation for BlockCanaryContext.
### 1.1.1 *(2016-01-25)*
fix issue #19 and #23
## Version 1.0 *(2016-01-21)*
Initial release.
### 1.0.1
Fix no-op api not align bug.
### 1.0.2
Add notification sound, fix 2.3 crash.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
[Chinese README](https://github.com/markzhai/AndroidPerformanceMonitor/blob/master/README_CN.md)
# Android Performance Monitor [](https://maven-badges.herokuapp.com/maven-central/com.github.markzhai/blockcanary-android)
A transparent ui-block detection library for Android, app only needs one-line-code to setup.
The naming is to pay respect to the great library [LeakCanary](https://github.com/square/leakcanary), ui-related codes are modified from leakcanary's ui part.
- 1.5.0 Add context that can stop monitor in debug mode.
- 1.4.1 Bug fix.
- 1.4.0 Bug fix, add onBlock interceptor in context.
- 1.3.1 Enable configuration of label and icon.
- 1.3.0 Add white-list and concern-package feature.
# Getting started
You may choose how to assemble them as you like.
```gradle
dependencies {
// most often used way, enable notification to notify block event
compile 'com.github.markzhai:blockcanary-android:1.5.0'
// this way you only enable BlockCanary in debug package
// debugCompile 'com.github.markzhai:blockcanary-android:1.5.0'
// releaseCompile 'com.github.markzhai:blockcanary-no-op:1.5.0'
}
```
As this library uses `getMainLooper().setMessageLogging()`, please check if you set it in your app (related issue https://github.com/moduth/blockcanary/issues/27)
# Usage
Maximum log count is set to 500, you can rewrite it in your app `int.xml`.
```xml
1000
```
Monitor app's label and icon can be configured by placing a `block_canary_icon` drawable in your xhdpi drawable directory and in `strings.xml`:
```xml
Blocks
```
```java
public class DemoApplication extends Application {
@Override
public void onCreate() {
// ...
// Do it on main process
BlockCanary.install(this, new AppBlockCanaryContext()).start();
}
}
```
Implement your application `BlockCanaryContext` context (strongly recommend you to check all these configs):
```java
public class AppBlockCanaryContext extends BlockCanaryContext {
/**
* Implement in your project.
*
* @return Qualifier which can specify this installation, like version + flavor.
*/
public String provideQualifier() {
return "unknown";
}
/**
* Implement in your project.
*
* @return user id
*/
public String provideUid() {
return "uid";
}
/**
* Network type
*
* @return {@link String} like 2G, 3G, 4G, wifi, etc.
*/
public String provideNetworkType() {
return "unknown";
}
/**
* Config monitor duration, after this time BlockCanary will stop, use
* with {@code BlockCanary}'s isMonitorDurationEnd
*
* @return monitor last duration (in hour)
*/
public int provideMonitorDuration() {
return -1;
}
/**
* Config block threshold (in millis), dispatch over this duration is regarded as a BLOCK. You may set it
* from performance of device.
*
* @return threshold in mills
*/
public int provideBlockThreshold() {
return 1000;
}
/**
* Thread stack dump interval, use when block happens, BlockCanary will dump on main thread
* stack according to current sample cycle.
*
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
*
*
* @return dump interval (in millis)
*/
public int provideDumpInterval() {
return provideBlockThreshold();
}
/**
* Path to save log, like "/blockcanary/", will save to sdcard if can.
*
* @return path of log files
*/
public String providePath() {
return "/blockcanary/";
}
/**
* If need notification to notice block.
*
* @return true if need, else if not need.
*/
public boolean displayNotification() {
return true;
}
/**
* Implement in your project, bundle files into a zip file.
*
* @param src files before compress
* @param dest files compressed
* @return true if compression is successful
*/
public boolean zip(File[] src, File dest) {
return false;
}
/**
* Implement in your project, bundled log files.
*
* @param zippedFile zipped file
*/
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
/**
* Packages that developer concern, by default it uses process name,
* put high priority one in pre-order.
*
* @return null if simply concern only package with process name.
*/
public List concernPackages() {
return null;
}
/**
* Filter stack without any in concern package, used with @{code concernPackages}.
*
* @return true if filter, false it not.
*/
public boolean filterNonConcernStack() {
return false;
}
/**
* Provide white list, entry in white list will not be shown in ui list.
*
* @return return null if you don't need white-list filter.
*/
public List provideWhiteList() {
LinkedList whiteList = new LinkedList<>();
whiteList.add("org.chromium");
return whiteList;
}
/**
* Whether to delete files whose stack is in white list, used with white-list.
*
* @return true if delete, false it not.
*/
public boolean deleteFilesInWhiteList() {
return true;
}
/**
* Block interceptor, developer may provide their own actions.
*/
public void onBlock(Context context, BlockInfo blockInfo) {
}
}
```
# How does it work?
Blog in Chinese: [BlockCanary](http://blog.zhaiyifan.cn/2016/01/16/BlockCanaryTransparentPerformanceMonitor/).
Principle flow picture:

# Screenshot


# Donation
If you find this repository helpful, you may make a donation to me via alipay or wechat.
 
# Contributors
This library is initially created by [markzhai](https://github.com/markzhai), and maintained under the organization [moduth](https://github.com/moduth) with [nimengbo](https://github.com/nimengbo) and [zzz40500](https://github.com/zzz40500).
Special thanks to [android-cjj](https://github.com/android-cjj), [Mr.Bao](https://github.com/baoyongzhang), [chiahaolu](https://github.com/chiahaolu) to contribute.
# Change Log
Check [CHANGELOG](https://github.com/markzhai/AndroidPerformanceMonitor/blob/master/CHANGELOG.md)
# Contribute
If you would like to contribute code to BlockCanary you can do so through GitHub by forking the repository and sending a pull request.
# License
Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README_CN.md
================================================
[English](https://github.com/markzhai/AndroidPerformanceMonitor/blob/master/README.md)
# Android Performance Monitor [](https://maven-badges.herokuapp.com/maven-central/com.github.markzhai/blockcanary-android)
BlockCanary是一个Android平台的一个非侵入式的性能监控组件,应用只需要实现一个抽象类,提供一些该组件需要的上下文环境,就可以在平时使用应用的时候检测主线程上的各种卡慢问题,并通过组件提供的各种信息分析出原因并进行修复。
取名为BlockCanary则是为了向LeakCanary致敬,顺便本库的UI部分是从LeakCanary改来的,之后可能会做一些调整。
- 1.5.0 Context 中增加 Debug 时不监控选项。
- 1.4.1 Bug修复
- 1.4.0 修复 1.3.x 的 bug,增加发生卡慢时的拦截方法。
- 1.3.0 增加白名单和包名过滤功能
# 包介绍
- blockcanary-android blockcanary类的一些实现
- blockcanary-analyzer 记录block信息的核心实现
- blockcanary-no-op 空包,为了release打包时不编译进去
# 引入
**一般选取以下其中一个 case 引入即可**
**如果有多个buildTypes需求,请使用 ```buildTypeComple ``` 关键字根据buildTypes组合使用即可**
```gradle
dependencies {
compile 'com.github.markzhai:blockcanary-android:1.5.0'
// 仅在debug包启用BlockCanary进行卡顿监控和提示的话,可以这么用
debugCompile 'com.github.markzhai:blockcanary-android:1.5.0'
releaseCompile 'com.github.markzhai:blockcanary-no-op:1.5.0'
}
```
PS: 由于该库使用了 `getMainLooper().setMessageLogging()`, 请确认是否与你的app冲突.
# 使用方法
在Application中:
```java
public class DemoApplication extends Application {
@Override
public void onCreate() {
// 在主进程初始化调用哈
BlockCanary.install(this, new AppBlockCanaryContext()).start();
}
}
```
实现自己的监控上下文(强烈建议看清所有配置项,避免使用错误):
```java
public class AppBlockCanaryContext extends BlockCanaryContext {
// 实现各种上下文,包括应用标示符,用户uid,网络类型,卡慢判断阙值,Log保存位置等
/**
* Implement in your project.
*
* @return Qualifier which can specify this installation, like version + flavor.
*/
public String provideQualifier() {
return "unknown";
}
/**
* Implement in your project.
*
* @return user id
*/
public String provideUid() {
return "uid";
}
/**
* Network type
*
* @return {@link String} like 2G, 3G, 4G, wifi, etc.
*/
public String provideNetworkType() {
return "unknown";
}
/**
* Config monitor duration, after this time BlockCanary will stop, use
* with {@code BlockCanary}'s isMonitorDurationEnd
*
* @return monitor last duration (in hour)
*/
public int provideMonitorDuration() {
return -1;
}
/**
* Config block threshold (in millis), dispatch over this duration is regarded as a BLOCK. You may set it
* from performance of device.
*
* @return threshold in mills
*/
public int provideBlockThreshold() {
return 1000;
}
/**
* Thread stack dump interval, use when block happens, BlockCanary will dump on main thread
* stack according to current sample cycle.
*
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
*
*
* @return dump interval (in millis)
*/
public int provideDumpInterval() {
return provideBlockThreshold();
}
/**
* Path to save log, like "/blockcanary/", will save to sdcard if can.
*
* @return path of log files
*/
public String providePath() {
return "/blockcanary/";
}
/**
* If need notification to notice block.
*
* @return true if need, else if not need.
*/
public boolean displayNotification() {
return true;
}
/**
* Implement in your project, bundle files into a zip file.
*
* @param src files before compress
* @param dest files compressed
* @return true if compression is successful
*/
public boolean zip(File[] src, File dest) {
return false;
}
/**
* Implement in your project, bundled log files.
*
* @param zippedFile zipped file
*/
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
/**
* Packages that developer concern, by default it uses process name,
* put high priority one in pre-order.
*
* @return null if simply concern only package with process name.
*/
public List concernPackages() {
return null;
}
/**
* Filter stack without any in concern package, used with @{code concernPackages}.
*
* @return true if filter, false it not.
*/
public boolean filterNonConcernStack() {
return false;
}
/**
* Provide white list, entry in white list will not be shown in ui list.
*
* @return return null if you don't need white-list filter.
*/
public List provideWhiteList() {
LinkedList whiteList = new LinkedList<>();
whiteList.add("org.chromium");
return whiteList;
}
/**
* Whether to delete files whose stack is in white list, used with white-list.
*
* @return true if delete, false it not.
*/
public boolean deleteFilesInWhiteList() {
return true;
}
/**
* Block interceptor, developer may provide their own actions.
*/
public void onBlock(Context context, BlockInfo blockInfo) {
}
}
```
# 功能及原理
见[BlockCanary — 轻松找出Android App界面卡顿元凶](http://blog.zhaiyifan.cn/2016/01/16/BlockCanaryTransparentPerformanceMonitor/).
或见下图

# 如何分析log
除了图形界面可以供开发、测试阶段直接看卡顿原因外,更多的使用场景其实在于大范围的log采集和分析:如线上环境和monkey,或者测试同学们在整个测试阶段的log收集和分析。
对于分析,主要可以从以下维度
- 卡顿时间
- 同堆栈的卡顿出现次数
进行排序和归类。
接着说说对各个log分析的过程。
- 首先可以根据手机性能,如核数、机型、内存来判断对应耗时是不是应该判定为卡顿。如一些差的机器,或者内存本身不足的时候。
- 根据CPU情况,是否是app拿不到cpu,被其他应用拿走了。
- 看timecost和threadtimecost,如果两者差得很多,则是主线程被等待或者资源被抢占。
- 看卡顿发生前最近的几次堆栈,如果堆栈相同,则可以判定为是该处发生卡顿,否则需要比较分析。
# Demo工程
**请参考本项目下的demo module,点击三个按钮会触发对应的耗时事件,消息栏则会弹出block的notification,点击可以进去查看详细信息。**


# 贡献者
该库最初由 [markzhai](https://github.com/markzhai) 创建, 并在 [魔都三帅](https://github.com/moduth) 组织下与 [nimengbo](https://github.com/nimengbo) 和 [zzz40500](https://github.com/zzz40500) 共同维护和更新.
特别鸣谢[Mr.Bao](https://github.com/baoyongzhang), [android-cjj](https://github.com/android-cjj), [chiahaolu](https://github.com/chiahaolu)在项目早期做出的贡献。
# 修改日志
见[CHANGELOG](https://github.com/markzhai/AndroidPerformanceMonitor/blob/master/CHANGELOG.md)
# 贡献
如果你希望贡献代码到BlockCanary,你可以fork本repository然后发一个PR。
# 协议
Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: blockcanary-analyzer/.gitignore
================================================
/build
================================================
FILE: blockcanary-analyzer/build.gradle
================================================
apply plugin: 'com.android.library'
apply from: 'gradle-mvn-push.gradle'
android {
compileSdkVersion LIBRARY_COMPILE_SDK_VERSION
buildToolsVersion LIBRARY_BUILD_TOOLS_VERSION
defaultConfig {
minSdkVersion LIBRARY_MIN_SDK_VERSION
targetSdkVersion LIBRARY_TARGET_SDK_VERSION
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
================================================
FILE: blockcanary-analyzer/gradle-mvn-push.gradle
================================================
/*
* Copyright 2013 Chris Banes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'maven'
apply plugin: 'signing'
def isReleaseBuild() {
return VERSION_NAME.contains("SNAPSHOT") == false
}
def getReleaseRepositoryUrl() {
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}
def getSnapshotRepositoryUrl() {
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: "https://oss.sonatype.org/content/repositories/snapshots/"
}
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
pom.groupId = GROUP
pom.artifactId = POM_ARTIFACT_ID
pom.version = VERSION_NAME
repository(url: getReleaseRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
snapshotRepository(url: getSnapshotRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
pom.project {
name POM_NAME
packaging POM_PACKAGING
description POM_DESCRIPTION
url POM_URL
scm {
url POM_SCM_URL
connection POM_SCM_CONNECTION
developerConnection POM_SCM_DEV_CONNECTION
}
licenses {
license {
name POM_LICENCE_NAME
url POM_LICENCE_URL
distribution POM_LICENCE_DIST
}
}
developers {
developer {
id POM_DEVELOPER_ID
name POM_DEVELOPER_NAME
}
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
task androidJavadocs(type: Javadoc) {
options.encoding = "utf-8"
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}
}
================================================
FILE: blockcanary-analyzer/gradle.properties
================================================
POM_NAME=Android BlockCanary Analyzer Library
POM_ARTIFACT_ID=blockcanary-analyzer
POM_PACKAGING=aar
VERSION_NAME=1.5.0
VERSION_CODE=16
GROUP=com.github.markzhai
POM_DESCRIPTION=Android BlockCanary Analyzer Library
POM_URL=https://github.com/markzhai/AndroidPerformanceMonitor
POM_SCM_URL=https://github.com/markzhai/AndroidPerformanceMonitor
POM_SCM_CONNECTION=scm:https://github.com/markzhai/AndroidPerformanceMonitor.git
POM_SCM_DEV_CONNECTION=scm:https://github.com/markzhai/AndroidPerformanceMonitor.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=markzhai
POM_DEVELOPER_NAME=markzhai
POM_DEVELOPER_URL=https://github.com/markzhai
================================================
FILE: blockcanary-analyzer/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/yifan/dev/sdk/adt-bundle-mac-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: blockcanary-analyzer/src/main/AndroidManifest.xml
================================================
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/AbstractSampler.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* {@link AbstractSampler} sampler defines sampler work flow.
*/
abstract class AbstractSampler {
private static final int DEFAULT_SAMPLE_INTERVAL = 300;
protected AtomicBoolean mShouldSample = new AtomicBoolean(false);
protected long mSampleInterval;
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
doSample();
if (mShouldSample.get()) {
HandlerThreadFactory.getTimerThreadHandler()
.postDelayed(mRunnable, mSampleInterval);
}
}
};
public AbstractSampler(long sampleInterval) {
if (0 == sampleInterval) {
sampleInterval = DEFAULT_SAMPLE_INTERVAL;
}
mSampleInterval = sampleInterval;
}
public void start() {
if (mShouldSample.get()) {
return;
}
mShouldSample.set(true);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
public void stop() {
if (!mShouldSample.get()) {
return;
}
mShouldSample.set(false);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
abstract void doSample();
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/BlockCanaryContext.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.content.Context;
import com.github.moduth.blockcanary.internal.BlockInfo;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
/**
* User should provide a real implementation of this class to use BlockCanary.
*/
public class BlockCanaryContext implements BlockInterceptor {
private static Context sApplicationContext;
private static BlockCanaryContext sInstance = null;
public BlockCanaryContext() {
}
static void init(Context context, BlockCanaryContext blockCanaryContext) {
sApplicationContext = context;
sInstance = blockCanaryContext;
}
public static BlockCanaryContext get() {
if (sInstance == null) {
throw new RuntimeException("BlockCanaryContext null");
} else {
return sInstance;
}
}
/**
* Provide application context.
*/
public Context provideContext() {
return sApplicationContext;
}
/**
* Implement in your project.
*
* @return Qualifier which can specify this installation, like version + flavor.
*/
public String provideQualifier() {
return "unknown";
}
/**
* Implement in your project.
*
* @return user id
*/
public String provideUid() {
return "uid";
}
/**
* Network type
*
* @return {@link String} like 2G, 3G, 4G, wifi, etc.
*/
public String provideNetworkType() {
return "unknown";
}
/**
* Config monitor duration, after this time BlockCanary will stop, use
* with {@code BlockCanary}'s isMonitorDurationEnd
*
* @return monitor last duration (in hour)
*/
public int provideMonitorDuration() {
return -1;
}
/**
* Config block threshold (in millis), dispatch over this duration is regarded as a BLOCK. You may set it
* from performance of device.
*
* @return threshold in mills
*/
public int provideBlockThreshold() {
return 1000;
}
/**
* Thread stack dump interval, use when block happens, BlockCanary will dump on main thread
* stack according to current sample cycle.
*
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
*
*
* @return dump interval (in millis)
*/
public int provideDumpInterval() {
return provideBlockThreshold();
}
/**
* Path to save log, like "/blockcanary/", will save to sdcard if can.
*
* @return path of log files
*/
public String providePath() {
return "/blockcanary/";
}
/**
* If need notification to notice block.
*
* @return true if need, else if not need.
*/
public boolean displayNotification() {
return true;
}
/**
* Implement in your project, bundle files into a zip file.
*
* @param src files before compress
* @param dest files compressed
* @return true if compression is successful
*/
public boolean zip(File[] src, File dest) {
return false;
}
/**
* Implement in your project, bundled log files.
*
* @param zippedFile zipped file
*/
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
/**
* Packages that developer concern, by default it uses process name,
* put high priority one in pre-order.
*
* @return null if simply concern only package with process name.
*/
public List concernPackages() {
return null;
}
/**
* Filter stack without any in concern package, used with @{code concernPackages}.
*
* @return true if filter, false it not.
*/
public boolean filterNonConcernStack() {
return false;
}
/**
* Provide white list, entry in white list will not be shown in ui list.
*
* @return return null if you don't need white-list filter.
*/
public List provideWhiteList() {
LinkedList whiteList = new LinkedList<>();
whiteList.add("org.chromium");
return whiteList;
}
/**
* Whether to delete files whose stack is in white list, used with white-list.
*
* @return true if delete, false it not.
*/
public boolean deleteFilesInWhiteList() {
return true;
}
/**
* Block interceptor, developer may provide their own actions.
*/
@Override
public void onBlock(Context context, BlockInfo blockInfo) {
}
/**
* Whether to stop monitoring when in debug mode.
*
* @return true if stop, false otherwise
*/
public boolean stopWhenDebugging() {
return true;
}
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/BlockCanaryInternals.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.os.Environment;
import android.os.Looper;
import com.github.moduth.blockcanary.internal.BlockInfo;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public final class BlockCanaryInternals {
LooperMonitor monitor;
StackSampler stackSampler;
CpuSampler cpuSampler;
private static BlockCanaryInternals sInstance;
private static BlockCanaryContext sContext;
private List mInterceptorChain = new LinkedList<>();
public BlockCanaryInternals() {
stackSampler = new StackSampler(
Looper.getMainLooper().getThread(),
sContext.provideDumpInterval());
cpuSampler = new CpuSampler(sContext.provideDumpInterval());
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
// Get recent thread-stack entries and cpu usage
ArrayList threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries)
.flushString();
LogWriter.save(blockInfo.toString());
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
LogWriter.cleanObsolete();
}
/**
* Get BlockCanaryInternals singleton
*
* @return BlockCanaryInternals instance
*/
static BlockCanaryInternals getInstance() {
if (sInstance == null) {
synchronized (BlockCanaryInternals.class) {
if (sInstance == null) {
sInstance = new BlockCanaryInternals();
}
}
}
return sInstance;
}
/**
* set {@link BlockCanaryContext} implementation
*
* @param context context
*/
public static void setContext(BlockCanaryContext context) {
sContext = context;
}
public static BlockCanaryContext getContext() {
return sContext;
}
void addBlockInterceptor(BlockInterceptor blockInterceptor) {
mInterceptorChain.add(blockInterceptor);
}
private void setMonitor(LooperMonitor looperPrinter) {
monitor = looperPrinter;
}
long getSampleDelay() {
return (long) (BlockCanaryInternals.getContext().provideBlockThreshold() * 0.8f);
}
static String getPath() {
String state = Environment.getExternalStorageState();
String logPath = BlockCanaryInternals.getContext()
== null ? "" : BlockCanaryInternals.getContext().providePath();
if (Environment.MEDIA_MOUNTED.equals(state)
&& Environment.getExternalStorageDirectory().canWrite()) {
return Environment.getExternalStorageDirectory().getPath() + logPath;
}
return getContext().provideContext().getFilesDir() + BlockCanaryInternals.getContext().providePath();
}
static File detectedBlockDirectory() {
File directory = new File(getPath());
if (!directory.exists()) {
directory.mkdirs();
}
return directory;
}
public static File[] getLogFiles() {
File f = detectedBlockDirectory();
if (f.exists() && f.isDirectory()) {
return f.listFiles(new BlockLogFileFilter());
}
return null;
}
private static class BlockLogFileFilter implements FilenameFilter {
private String TYPE = ".log";
BlockLogFileFilter() {
}
@Override
public boolean accept(File dir, String filename) {
return filename.endsWith(TYPE);
}
}
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/BlockInterceptor.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.content.Context;
import com.github.moduth.blockcanary.internal.BlockInfo;
interface BlockInterceptor {
void onBlock(Context context, BlockInfo blockInfo);
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/CpuSampler.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.util.Log;
import com.github.moduth.blockcanary.internal.BlockInfo;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Dumps cpu usage.
*/
class CpuSampler extends AbstractSampler {
private static final String TAG = "CpuSampler";
private static final int BUFFER_SIZE = 1000;
/**
* TODO: Explain how we define cpu busy in README
*/
private final int BUSY_TIME;
private static final int MAX_ENTRY_COUNT = 10;
private final LinkedHashMap mCpuInfoEntries = new LinkedHashMap<>();
private int mPid = 0;
private long mUserLast = 0;
private long mSystemLast = 0;
private long mIdleLast = 0;
private long mIoWaitLast = 0;
private long mTotalLast = 0;
private long mAppCpuTimeLast = 0;
public CpuSampler(long sampleInterval) {
super(sampleInterval);
BUSY_TIME = (int) (mSampleInterval * 1.2f);
}
@Override
public void start() {
super.start();
reset();
}
/**
* Get cpu rate information
*
* @return string show cpu rate information
*/
public String getCpuRateInfo() {
StringBuilder sb = new StringBuilder();
synchronized (mCpuInfoEntries) {
for (Map.Entry entry : mCpuInfoEntries.entrySet()) {
long time = entry.getKey();
sb.append(BlockInfo.TIME_FORMATTER.format(time))
.append(' ')
.append(entry.getValue())
.append(BlockInfo.SEPARATOR);
}
}
return sb.toString();
}
public boolean isCpuBusy(long start, long end) {
if (end - start > mSampleInterval) {
long s = start - mSampleInterval;
long e = start + mSampleInterval;
long last = 0;
synchronized (mCpuInfoEntries) {
for (Map.Entry entry : mCpuInfoEntries.entrySet()) {
long time = entry.getKey();
if (s < time && time < e) {
if (last != 0 && time - last > BUSY_TIME) {
return true;
}
last = time;
}
}
}
}
return false;
}
@Override
protected void doSample() {
BufferedReader cpuReader = null;
BufferedReader pidReader = null;
try {
cpuReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/stat")), BUFFER_SIZE);
String cpuRate = cpuReader.readLine();
if (cpuRate == null) {
cpuRate = "";
}
if (mPid == 0) {
mPid = android.os.Process.myPid();
}
pidReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
String pidCpuRate = pidReader.readLine();
if (pidCpuRate == null) {
pidCpuRate = "";
}
parse(cpuRate, pidCpuRate);
} catch (Throwable throwable) {
Log.e(TAG, "doSample: ", throwable);
} finally {
try {
if (cpuReader != null) {
cpuReader.close();
}
if (pidReader != null) {
pidReader.close();
}
} catch (IOException exception) {
Log.e(TAG, "doSample: ", exception);
}
}
}
private void reset() {
mUserLast = 0;
mSystemLast = 0;
mIdleLast = 0;
mIoWaitLast = 0;
mTotalLast = 0;
mAppCpuTimeLast = 0;
}
private void parse(String cpuRate, String pidCpuRate) {
String[] cpuInfoArray = cpuRate.split(" ");
if (cpuInfoArray.length < 9) {
return;
}
long user = Long.parseLong(cpuInfoArray[2]);
long nice = Long.parseLong(cpuInfoArray[3]);
long system = Long.parseLong(cpuInfoArray[4]);
long idle = Long.parseLong(cpuInfoArray[5]);
long ioWait = Long.parseLong(cpuInfoArray[6]);
long total = user + nice + system + idle + ioWait
+ Long.parseLong(cpuInfoArray[7])
+ Long.parseLong(cpuInfoArray[8]);
String[] pidCpuInfoList = pidCpuRate.split(" ");
if (pidCpuInfoList.length < 17) {
return;
}
long appCpuTime = Long.parseLong(pidCpuInfoList[13])
+ Long.parseLong(pidCpuInfoList[14])
+ Long.parseLong(pidCpuInfoList[15])
+ Long.parseLong(pidCpuInfoList[16]);
if (mTotalLast != 0) {
StringBuilder stringBuilder = new StringBuilder();
long idleTime = idle - mIdleLast;
long totalTime = total - mTotalLast;
stringBuilder
.append("cpu:")
.append((totalTime - idleTime) * 100L / totalTime)
.append("% ")
.append("app:")
.append((appCpuTime - mAppCpuTimeLast) * 100L / totalTime)
.append("% ")
.append("[")
.append("user:").append((user - mUserLast) * 100L / totalTime)
.append("% ")
.append("system:").append((system - mSystemLast) * 100L / totalTime)
.append("% ")
.append("ioWait:").append((ioWait - mIoWaitLast) * 100L / totalTime)
.append("% ]");
synchronized (mCpuInfoEntries) {
mCpuInfoEntries.put(System.currentTimeMillis(), stringBuilder.toString());
if (mCpuInfoEntries.size() > MAX_ENTRY_COUNT) {
for (Map.Entry entry : mCpuInfoEntries.entrySet()) {
Long key = entry.getKey();
mCpuInfoEntries.remove(key);
break;
}
}
}
}
mUserLast = user;
mSystemLast = system;
mIdleLast = idle;
mIoWaitLast = ioWait;
mTotalLast = total;
mAppCpuTimeLast = appCpuTime;
}
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/HandlerThreadFactory.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.os.Handler;
import android.os.HandlerThread;
final class HandlerThreadFactory {
private static HandlerThreadWrapper sLoopThread = new HandlerThreadWrapper("loop");
private static HandlerThreadWrapper sWriteLogThread = new HandlerThreadWrapper("writer");
private HandlerThreadFactory() {
throw new InstantiationError("Must not instantiate this class");
}
public static Handler getTimerThreadHandler() {
return sLoopThread.getHandler();
}
public static Handler getWriteLogThreadHandler() {
return sWriteLogThread.getHandler();
}
private static class HandlerThreadWrapper {
private Handler handler = null;
public HandlerThreadWrapper(String threadName) {
HandlerThread handlerThread = new HandlerThread("BlockCanary-" + threadName);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
}
public Handler getHandler() {
return handler;
}
}
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/LogWriter.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.util.Log;
import com.github.moduth.blockcanary.internal.BlockInfo;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Locale;
/**
* Log writer which runs in standalone thread.
*/
public class LogWriter {
private static final String TAG = "LogWriter";
private static final Object SAVE_DELETE_LOCK = new Object();
private static final SimpleDateFormat FILE_NAME_FORMATTER
= new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss.SSS", Locale.US);
private static final SimpleDateFormat TIME_FORMATTER
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
private static final long OBSOLETE_DURATION = 2 * 24 * 3600 * 1000L;
private LogWriter() {
throw new InstantiationError("Must not instantiate this class");
}
/**
* Save log to file
*
* @param str block info string
* @return log file path
*/
public static String save(String str) {
String path;
synchronized (SAVE_DELETE_LOCK) {
path = save("looper", str);
}
return path;
}
/**
* Delete obsolete log files, which is by default 2 days.
*/
public static void cleanObsolete() {
HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
@Override
public void run() {
long now = System.currentTimeMillis();
File[] f = BlockCanaryInternals.getLogFiles();
if (f != null && f.length > 0) {
synchronized (SAVE_DELETE_LOCK) {
for (File aF : f) {
if (now - aF.lastModified() > OBSOLETE_DURATION) {
aF.delete();
}
}
}
}
}
});
}
public static void deleteAll() {
synchronized (SAVE_DELETE_LOCK) {
try {
File[] files = BlockCanaryInternals.getLogFiles();
if (files != null && files.length > 0) {
for (File file : files) {
file.delete();
}
}
} catch (Throwable e) {
Log.e(TAG, "deleteAll: ", e);
}
}
}
private static String save(String logFileName, String str) {
String path = "";
BufferedWriter writer = null;
try {
File file = BlockCanaryInternals.detectedBlockDirectory();
long time = System.currentTimeMillis();
path = file.getAbsolutePath() + "/"
+ logFileName + "-"
+ FILE_NAME_FORMATTER.format(time) + ".log";
OutputStreamWriter out =
new OutputStreamWriter(new FileOutputStream(path, true), "UTF-8");
writer = new BufferedWriter(out);
writer.write(BlockInfo.SEPARATOR);
writer.write("**********************");
writer.write(BlockInfo.SEPARATOR);
writer.write(TIME_FORMATTER.format(time) + "(write log time)");
writer.write(BlockInfo.SEPARATOR);
writer.write(BlockInfo.SEPARATOR);
writer.write(str);
writer.write(BlockInfo.SEPARATOR);
writer.flush();
writer.close();
writer = null;
} catch (Throwable t) {
Log.e(TAG, "save: ", t);
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (Exception e) {
Log.e(TAG, "save: ", e);
}
}
return path;
}
public static File generateTempZip(String filename) {
return new File(BlockCanaryInternals.getPath() + "/" + filename + ".zip");
}
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/LooperMonitor.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.os.Debug;
import android.os.SystemClock;
import android.util.Printer;
class LooperMonitor implements Printer {
private static final int DEFAULT_BLOCK_THRESHOLD_MILLIS = 3000;
private long mBlockThresholdMillis = DEFAULT_BLOCK_THRESHOLD_MILLIS;
private long mStartTimestamp = 0;
private long mStartThreadTimestamp = 0;
private BlockListener mBlockListener = null;
private boolean mPrintingStarted = false;
private final boolean mStopWhenDebugging;
public interface BlockListener {
void onBlockEvent(long realStartTime,
long realTimeEnd,
long threadTimeStart,
long threadTimeEnd);
}
public LooperMonitor(BlockListener blockListener, long blockThresholdMillis, boolean stopWhenDebugging) {
if (blockListener == null) {
throw new IllegalArgumentException("blockListener should not be null.");
}
mBlockListener = blockListener;
mBlockThresholdMillis = blockThresholdMillis;
mStopWhenDebugging = stopWhenDebugging;
}
@Override
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump();
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
private boolean isBlock(long endTime) {
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
private void notifyBlockEvent(final long endTime) {
final long startTime = mStartTimestamp;
final long startThreadTime = mStartThreadTimestamp;
final long endThreadTime = SystemClock.currentThreadTimeMillis();
HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
@Override
public void run() {
mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
}
});
}
private void startDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
BlockCanaryInternals.getInstance().stackSampler.start();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
BlockCanaryInternals.getInstance().cpuSampler.start();
}
}
private void stopDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
BlockCanaryInternals.getInstance().stackSampler.stop();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
BlockCanaryInternals.getInstance().cpuSampler.stop();
}
}
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/StackSampler.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import com.github.moduth.blockcanary.internal.BlockInfo;
import java.util.ArrayList;
import java.util.LinkedHashMap;
/**
* Dumps thread stack.
*/
class StackSampler extends AbstractSampler {
private static final int DEFAULT_MAX_ENTRY_COUNT = 100;
private static final LinkedHashMap sStackMap = new LinkedHashMap<>();
private int mMaxEntryCount = DEFAULT_MAX_ENTRY_COUNT;
private Thread mCurrentThread;
public StackSampler(Thread thread, long sampleIntervalMillis) {
this(thread, DEFAULT_MAX_ENTRY_COUNT, sampleIntervalMillis);
}
public StackSampler(Thread thread, int maxEntryCount, long sampleIntervalMillis) {
super(sampleIntervalMillis);
mCurrentThread = thread;
mMaxEntryCount = maxEntryCount;
}
public ArrayList getThreadStackEntries(long startTime, long endTime) {
ArrayList result = new ArrayList<>();
synchronized (sStackMap) {
for (Long entryTime : sStackMap.keySet()) {
if (startTime < entryTime && entryTime < endTime) {
result.add(BlockInfo.TIME_FORMATTER.format(entryTime)
+ BlockInfo.SEPARATOR
+ BlockInfo.SEPARATOR
+ sStackMap.get(entryTime));
}
}
}
return result;
}
@Override
protected void doSample() {
StringBuilder stringBuilder = new StringBuilder();
for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
stringBuilder
.append(stackTraceElement.toString())
.append(BlockInfo.SEPARATOR);
}
synchronized (sStackMap) {
if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
sStackMap.remove(sStackMap.keySet().iterator().next());
}
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/internal/BlockInfo.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary.internal;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.Build.VERSION;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.github.moduth.blockcanary.BlockCanaryInternals;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Locale;
/**
* Information to trace a block.
*/
public class BlockInfo {
private static final String TAG = "BlockInfo";
public static final SimpleDateFormat TIME_FORMATTER =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US);
public static final String SEPARATOR = "\r\n";
public static final String KV = " = ";
public static final String NEW_INSTANCE_METHOD = "newInstance: ";
public static final String KEY_QUA = "qua";
public static final String KEY_MODEL = "model";
public static final String KEY_API = "api-level";
public static final String KEY_IMEI = "imei";
public static final String KEY_UID = "uid";
public static final String KEY_CPU_CORE = "cpu-core";
public static final String KEY_CPU_BUSY = "cpu-busy";
public static final String KEY_CPU_RATE = "cpu-rate";
public static final String KEY_TIME_COST = "time";
public static final String KEY_THREAD_TIME_COST = "thread-time";
public static final String KEY_TIME_COST_START = "time-start";
public static final String KEY_TIME_COST_END = "time-end";
public static final String KEY_STACK = "stack";
public static final String KEY_PROCESS = "process";
public static final String KEY_VERSION_NAME = "versionName";
public static final String KEY_VERSION_CODE = "versionCode";
public static final String KEY_NETWORK = "network";
public static final String KEY_TOTAL_MEMORY = "totalMemory";
public static final String KEY_FREE_MEMORY = "freeMemory";
public static String sQualifier;
public static String sModel;
public static String sApiLevel = "";
/**
* The International Mobile Equipment Identity or IMEI /aɪˈmiː/ is a number,
* usually unique, to identify 3GPP and iDEN mobile phones
*/
public static String sImei = "";
public static int sCpuCoreNum = -1;
public String qualifier;
public String model;
public String apiLevel = "";
public String imei = "";
public int cpuCoreNum = -1;
// Per Block Info fields
public String uid;
public String processName;
public String versionName = "";
public int versionCode;
public String network;
public String freeMemory;
public String totalMemory;
public long timeCost;
public long threadTimeCost;
public String timeStart;
public String timeEnd;
public boolean cpuBusy;
public String cpuRateInfo;
public ArrayList threadStackEntries = new ArrayList<>();
private StringBuilder basicSb = new StringBuilder();
private StringBuilder cpuSb = new StringBuilder();
private StringBuilder timeSb = new StringBuilder();
private StringBuilder stackSb = new StringBuilder();
private static final String EMPTY_IMEI = "empty_imei";
static {
sCpuCoreNum = PerformanceUtils.getNumCores();
sModel = Build.MODEL;
sApiLevel = Build.VERSION.SDK_INT + " " + VERSION.RELEASE;
sQualifier = BlockCanaryInternals.getContext().provideQualifier();
try {
TelephonyManager telephonyManager = (TelephonyManager) BlockCanaryInternals
.getContext()
.provideContext()
.getSystemService(Context.TELEPHONY_SERVICE);
sImei = telephonyManager.getDeviceId();
} catch (Exception exception) {
Log.e(TAG, NEW_INSTANCE_METHOD, exception);
sImei = EMPTY_IMEI;
}
}
public BlockInfo() {
}
public static BlockInfo newInstance() {
BlockInfo blockInfo = new BlockInfo();
Context context = BlockCanaryInternals.getContext().provideContext();
if (blockInfo.versionName == null || blockInfo.versionName.length() == 0) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
blockInfo.versionCode = info.versionCode;
blockInfo.versionName = info.versionName;
} catch (Throwable e) {
Log.e(TAG, NEW_INSTANCE_METHOD, e);
}
}
blockInfo.cpuCoreNum = sCpuCoreNum;
blockInfo.model = sModel;
blockInfo.apiLevel = sApiLevel;
blockInfo.qualifier = sQualifier;
blockInfo.imei = sImei;
blockInfo.uid = BlockCanaryInternals.getContext().provideUid();
blockInfo.processName = ProcessUtils.myProcessName();
blockInfo.network = BlockCanaryInternals.getContext().provideNetworkType();
blockInfo.freeMemory = String.valueOf(PerformanceUtils.getFreeMemory());
blockInfo.totalMemory = String.valueOf(PerformanceUtils.getTotalMemory());
return blockInfo;
}
public BlockInfo setCpuBusyFlag(boolean busy) {
cpuBusy = busy;
return this;
}
public BlockInfo setRecentCpuRate(String info) {
cpuRateInfo = info;
return this;
}
public BlockInfo setThreadStackEntries(ArrayList threadStackEntries) {
this.threadStackEntries = threadStackEntries;
return this;
}
public BlockInfo setMainThreadTimeCost(long realTimeStart, long realTimeEnd, long threadTimeStart, long threadTimeEnd) {
timeCost = realTimeEnd - realTimeStart;
threadTimeCost = threadTimeEnd - threadTimeStart;
timeStart = TIME_FORMATTER.format(realTimeStart);
timeEnd = TIME_FORMATTER.format(realTimeEnd);
return this;
}
public BlockInfo flushString() {
String separator = SEPARATOR;
basicSb.append(KEY_QUA).append(KV).append(qualifier).append(separator);
basicSb.append(KEY_VERSION_NAME).append(KV).append(versionName).append(separator);
basicSb.append(KEY_VERSION_CODE).append(KV).append(versionCode).append(separator);
basicSb.append(KEY_IMEI).append(KV).append(imei).append(separator);
basicSb.append(KEY_UID).append(KV).append(uid).append(separator);
basicSb.append(KEY_NETWORK).append(KV).append(network).append(separator);
basicSb.append(KEY_MODEL).append(KV).append(model).append(separator);
basicSb.append(KEY_API).append(KV).append(apiLevel).append(separator);
basicSb.append(KEY_CPU_CORE).append(KV).append(cpuCoreNum).append(separator);
basicSb.append(KEY_PROCESS).append(KV).append(processName).append(separator);
basicSb.append(KEY_FREE_MEMORY).append(KV).append(freeMemory).append(separator);
basicSb.append(KEY_TOTAL_MEMORY).append(KV).append(totalMemory).append(separator);
timeSb.append(KEY_TIME_COST).append(KV).append(timeCost).append(separator);
timeSb.append(KEY_THREAD_TIME_COST).append(KV).append(threadTimeCost).append(separator);
timeSb.append(KEY_TIME_COST_START).append(KV).append(timeStart).append(separator);
timeSb.append(KEY_TIME_COST_END).append(KV).append(timeEnd).append(separator);
cpuSb.append(KEY_CPU_BUSY).append(KV).append(cpuBusy).append(separator);
cpuSb.append(KEY_CPU_RATE).append(KV).append(cpuRateInfo).append(separator);
if (threadStackEntries != null && !threadStackEntries.isEmpty()) {
StringBuilder temp = new StringBuilder();
for (String s : threadStackEntries) {
temp.append(s);
temp.append(separator);
}
stackSb.append(KEY_STACK).append(KV).append(temp.toString()).append(separator);
}
return this;
}
public String getBasicString() {
return basicSb.toString();
}
public String getCpuString() {
return cpuSb.toString();
}
public String getTimeString() {
return timeSb.toString();
}
public String toString() {
return String.valueOf(basicSb) + timeSb + cpuSb + stackSb;
}
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/internal/PerformanceUtils.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary.internal;
import android.app.ActivityManager;
import android.content.Context;
import android.util.Log;
import com.github.moduth.blockcanary.BlockCanaryInternals;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.util.regex.Pattern;
class PerformanceUtils {
private static final String TAG = "PerformanceUtils";
private static int sCoreNum = 0;
private static long sTotalMemo = 0;
private PerformanceUtils() {
throw new InstantiationError("Must not instantiate this class");
}
/**
* Get cpu core number
*
* @return int cpu core number
*/
public static int getNumCores() {
class CpuFilter implements FileFilter {
@Override
public boolean accept(File pathname) {
return Pattern.matches("cpu[0-9]", pathname.getName());
}
}
if (sCoreNum == 0) {
try {
// Get directory containing CPU info
File dir = new File("/sys/devices/system/cpu/");
// Filter to only list the devices we care about
File[] files = dir.listFiles(new CpuFilter());
// Return the number of cores (virtual CPU devices)
sCoreNum = files.length;
} catch (Exception e) {
Log.e(TAG, "getNumCores exception", e);
sCoreNum = 1;
}
}
return sCoreNum;
}
public static long getFreeMemory() {
ActivityManager am = (ActivityManager) BlockCanaryInternals.getContext().provideContext().getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
return mi.availMem / 1024;
}
public static long getTotalMemory() {
if (sTotalMemo == 0) {
String str1 = "/proc/meminfo";
String str2;
String[] arrayOfString;
long initial_memory = -1;
FileReader localFileReader = null;
try {
localFileReader = new FileReader(str1);
BufferedReader localBufferedReader = new BufferedReader(localFileReader, 8192);
str2 = localBufferedReader.readLine();
if (str2 != null) {
arrayOfString = str2.split("\\s+");
initial_memory = Integer.valueOf(arrayOfString[1]);
}
localBufferedReader.close();
} catch (IOException e) {
Log.e(TAG, "getTotalMemory exception = ", e);
} finally {
if (localFileReader != null) {
try {
localFileReader.close();
} catch (IOException e) {
Log.e(TAG, "close localFileReader exception = ", e);
}
}
}
sTotalMemo = initial_memory;
}
return sTotalMemo;
}
}
================================================
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/internal/ProcessUtils.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary.internal;
import android.app.ActivityManager;
import android.content.Context;
import com.github.moduth.blockcanary.BlockCanaryInternals;
import java.util.List;
public class ProcessUtils {
private static volatile String sProcessName;
private final static Object sNameLock = new Object();
private ProcessUtils() {
throw new InstantiationError("Must not instantiate this class");
}
public static String myProcessName() {
if (sProcessName != null) {
return sProcessName;
}
synchronized (sNameLock) {
if (sProcessName != null) {
return sProcessName;
}
sProcessName = obtainProcessName(BlockCanaryInternals.getContext().provideContext());
return sProcessName;
}
}
private static String obtainProcessName(Context context) {
final int pid = android.os.Process.myPid();
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List listTaskInfo = am.getRunningAppProcesses();
if (listTaskInfo != null && !listTaskInfo.isEmpty()) {
for (ActivityManager.RunningAppProcessInfo info : listTaskInfo) {
if (info != null && info.pid == pid) {
return info.processName;
}
}
}
return null;
}
}
================================================
FILE: blockcanary-analyzer/src/main/res/values/int.xml
================================================
500
================================================
FILE: blockcanary-android/.gitignore
================================================
/build
================================================
FILE: blockcanary-android/build.gradle
================================================
apply plugin: 'com.android.library'
apply from: 'gradle-mvn-push.gradle'
android {
compileSdkVersion LIBRARY_COMPILE_SDK_VERSION
buildToolsVersion LIBRARY_BUILD_TOOLS_VERSION
defaultConfig {
minSdkVersion LIBRARY_MIN_SDK_VERSION
targetSdkVersion LIBRARY_TARGET_SDK_VERSION
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
// compile project(':blockcanary-analyzer')
compile 'com.github.markzhai:blockcanary-analyzer:1.5.0'
}
================================================
FILE: blockcanary-android/gradle-mvn-push.gradle
================================================
/*
* Copyright 2013 Chris Banes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'maven'
apply plugin: 'signing'
def isReleaseBuild() {
return VERSION_NAME.contains("SNAPSHOT") == false
}
def getReleaseRepositoryUrl() {
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}
def getSnapshotRepositoryUrl() {
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: "https://oss.sonatype.org/content/repositories/snapshots/"
}
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
pom.groupId = GROUP
pom.artifactId = POM_ARTIFACT_ID
pom.version = VERSION_NAME
repository(url: getReleaseRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
snapshotRepository(url: getSnapshotRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
pom.project {
name POM_NAME
packaging POM_PACKAGING
description POM_DESCRIPTION
url POM_URL
scm {
url POM_SCM_URL
connection POM_SCM_CONNECTION
developerConnection POM_SCM_DEV_CONNECTION
}
licenses {
license {
name POM_LICENCE_NAME
url POM_LICENCE_URL
distribution POM_LICENCE_DIST
}
}
developers {
developer {
id POM_DEVELOPER_ID
name POM_DEVELOPER_NAME
}
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
task androidJavadocs(type: Javadoc) {
options.encoding = "utf-8"
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}
}
================================================
FILE: blockcanary-android/gradle.properties
================================================
POM_NAME=Android BlockCanary Library
POM_ARTIFACT_ID=blockcanary-android
POM_PACKAGING=aar
VERSION_NAME=1.5.0
VERSION_CODE=16
GROUP=com.github.markzhai
POM_DESCRIPTION=Android BlockCanary Library
POM_URL=https://github.com/markzhai/AndroidPerformanceMonitor
POM_SCM_URL=https://github.com/markzhai/AndroidPerformanceMonitor
POM_SCM_CONNECTION=scm:https://github.com/markzhai/AndroidPerformanceMonitor.git
POM_SCM_DEV_CONNECTION=scm:https://github.com/markzhai/AndroidPerformanceMonitor.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=markzhai
POM_DEVELOPER_NAME=markzhai
POM_DEVELOPER_URL=https://github.com/markzhai
================================================
FILE: blockcanary-android/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/Abner/myData/mySoftware/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Need if enable display in proguarded package
================================================
FILE: blockcanary-android/src/main/AndroidManifest.xml
================================================
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/BlockCanary.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Looper;
import android.preference.PreferenceManager;
import com.github.moduth.blockcanary.ui.DisplayActivity;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.DONT_KILL_APP;
public final class BlockCanary {
private static final String TAG = "BlockCanary";
private static BlockCanary sInstance;
private BlockCanaryInternals mBlockCanaryCore;
private boolean mMonitorStarted = false;
private BlockCanary() {
BlockCanaryInternals.setContext(BlockCanaryContext.get());
mBlockCanaryCore = BlockCanaryInternals.getInstance();
mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
if (!BlockCanaryContext.get().displayNotification()) {
return;
}
mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}
/**
* Install {@link BlockCanary}
*
* @param context Application context
* @param blockCanaryContext BlockCanary context
* @return {@link BlockCanary}
*/
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
BlockCanaryContext.init(context, blockCanaryContext);
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
return get();
}
/**
* Get {@link BlockCanary} singleton.
*
* @return {@link BlockCanary} instance
*/
public static BlockCanary get() {
if (sInstance == null) {
synchronized (BlockCanary.class) {
if (sInstance == null) {
sInstance = new BlockCanary();
}
}
}
return sInstance;
}
/**
* Start monitoring.
*/
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
/**
* Stop monitoring.
*/
public void stop() {
if (mMonitorStarted) {
mMonitorStarted = false;
Looper.getMainLooper().setMessageLogging(null);
mBlockCanaryCore.stackSampler.stop();
mBlockCanaryCore.cpuSampler.stop();
}
}
/**
* Zip and upload log files, will user context's zip and log implementation.
*/
public void upload() {
Uploader.zipAndUpload();
}
/**
* Record monitor start time to preference, you may use it when after push which tells start
* BlockCanary.
*/
public void recordStartTime() {
PreferenceManager.getDefaultSharedPreferences(BlockCanaryContext.get().provideContext())
.edit()
.putLong("BlockCanary_StartTime", System.currentTimeMillis())
.commit();
}
/**
* Is monitor duration end, compute from recordStartTime end provideMonitorDuration.
*
* @return true if ended
*/
public boolean isMonitorDurationEnd() {
long startTime =
PreferenceManager.getDefaultSharedPreferences(BlockCanaryContext.get().provideContext())
.getLong("BlockCanary_StartTime", 0);
return startTime != 0 && System.currentTimeMillis() - startTime >
BlockCanaryContext.get().provideMonitorDuration() * 3600 * 1000;
}
// these lines are originally copied from LeakCanary: Copyright (C) 2015 Square, Inc.
private static final Executor fileIoExecutor = newSingleThreadExecutor("File-IO");
private static void setEnabledBlocking(Context appContext,
Class> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
// end of lines copied from LeakCanary
private static void executeOnFileIoThread(Runnable runnable) {
fileIoExecutor.execute(runnable);
}
private static Executor newSingleThreadExecutor(String threadName) {
return Executors.newSingleThreadExecutor(new SingleThreadFactory(threadName));
}
private static void setEnabled(Context context,
final Class> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
@Override
public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/DisplayService.java
================================================
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.github.moduth.blockcanary.internal.BlockInfo;
import com.github.moduth.blockcanary.ui.DisplayActivity;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.JELLY_BEAN;
final class DisplayService implements BlockInterceptor {
private static final String TAG = "DisplayService";
@Override
public void onBlock(Context context, BlockInfo blockInfo) {
Intent intent = new Intent(context, DisplayActivity.class);
intent.putExtra("show_latest", blockInfo.timeStart);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);
String contentTitle = context.getString(R.string.block_canary_class_has_blocked, blockInfo.timeStart);
String contentText = context.getString(R.string.block_canary_notification_message);
show(context, contentTitle, contentText, pendingIntent);
}
@TargetApi(HONEYCOMB)
private void show(Context context, String contentTitle, String contentText, PendingIntent pendingIntent) {
NotificationManager notificationManager = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification;
if (SDK_INT < HONEYCOMB) {
notification = new Notification();
notification.icon = R.drawable.block_canary_notification;
notification.when = System.currentTimeMillis();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.defaults = Notification.DEFAULT_SOUND;
try {
Method deprecatedMethod = notification.getClass().getMethod("setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class);
deprecatedMethod.invoke(notification, context, contentTitle, contentText, pendingIntent);
} catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
Log.w(TAG, "Method not found", e);
}
} else {
Notification.Builder builder = new Notification.Builder(context)
.setSmallIcon(R.drawable.block_canary_notification)
.setWhen(System.currentTimeMillis())
.setContentTitle(contentTitle)
.setContentText(contentText)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setDefaults(Notification.DEFAULT_SOUND);
if (SDK_INT < JELLY_BEAN) {
notification = builder.getNotification();
} else {
notification = builder.build();
}
}
notificationManager.notify(0xDEAFBEEF, notification);
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/SingleThreadFactory.java
================================================
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import java.util.concurrent.ThreadFactory;
/**
* This is intended to only be used with a single thread executor.
*/
final class SingleThreadFactory implements ThreadFactory {
private final String threadName;
SingleThreadFactory(String threadName) {
this.threadName = "BlockCanary-" + threadName;
}
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable, threadName);
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/Uploader.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.util.Log;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
final class Uploader {
private static final String TAG = "Uploader";
private static final SimpleDateFormat FORMAT =
new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
private Uploader() {
throw new InstantiationError("Must not instantiate this class");
}
private static File zip() {
String timeString = Long.toString(System.currentTimeMillis());
try {
timeString = FORMAT.format(new Date());
} catch (Throwable e) {
Log.e(TAG, "zip: ", e);
}
File zippedFile = LogWriter.generateTempZip("BlockCanary-" + timeString);
BlockCanaryInternals.getContext().zip(BlockCanaryInternals.getLogFiles(), zippedFile);
LogWriter.deleteAll();
return zippedFile;
}
public static void zipAndUpload() {
HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
@Override
public void run() {
final File file = zip();
if (file.exists()) {
BlockCanaryInternals.getContext().upload(file);
}
}
});
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockCanaryUi.java
================================================
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary.ui;
import android.content.res.Resources;
import android.graphics.PorterDuffXfermode;
import android.util.DisplayMetrics;
import static android.graphics.PorterDuff.Mode.CLEAR;
final class BlockCanaryUi {
static final int LIGHT_GREY = 0xFFbababa;
static final int ROOT_COLOR = 0xFF84a6c5;
static final int LEAK_COLOR = 0xFFb1554e;
static final PorterDuffXfermode CLEAR_XFER_MODE = new PorterDuffXfermode(CLEAR);
private BlockCanaryUi() {
throw new AssertionError();
}
/**
* Converts from device independent pixels (dp or dip) to
* device dependent pixels. This method returns the input
* multiplied by the display's density. The result is not
* rounded nor clamped.
*
* The value returned by this method is well suited for
* drawing with the Canvas API but should not be used to
* set layout dimensions.
*
* @param dp The value in dp to convert to pixels
* @param resources An instances of Resources
*/
static float dpToPixel(float dp, Resources resources) {
DisplayMetrics metrics = resources.getDisplayMetrics();
return metrics.density * dp;
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockCanaryUtils.java
================================================
package com.github.moduth.blockcanary.ui;
import android.text.TextUtils;
import com.github.moduth.blockcanary.BlockCanaryInternals;
import com.github.moduth.blockcanary.internal.BlockInfo;
import com.github.moduth.blockcanary.internal.ProcessUtils;
import java.util.LinkedList;
import java.util.List;
final class BlockCanaryUtils {
private static final List WHITE_LIST = new LinkedList<>();
private static final List CONCERN_LIST = new LinkedList<>();
static {
WHITE_LIST.addAll(BlockCanaryInternals.getContext().provideWhiteList());
if (BlockCanaryInternals.getContext().concernPackages() != null) {
CONCERN_LIST.addAll(BlockCanaryInternals.getContext().concernPackages());
}
if (CONCERN_LIST.isEmpty()) {
CONCERN_LIST.add(ProcessUtils.myProcessName());
}
}
/**
* Get key stack string to show as title in ui list.
*/
public static String concernStackString(BlockInfo blockInfo) {
String result = "";
for (String stackEntry : blockInfo.threadStackEntries) {
if (Character.isLetter(stackEntry.charAt(0))) {
String[] lines = stackEntry.split(BlockInfo.SEPARATOR);
for (String line : lines) {
String keyStackString = concernStackString(line);
if (keyStackString != null) {
return keyStackString;
}
}
return classSimpleName(lines[0]);
}
}
return result;
}
public static boolean isBlockInfoValid(BlockInfo blockInfo) {
boolean isValid = !TextUtils.isEmpty(blockInfo.timeStart);
isValid = isValid && blockInfo.timeCost >= 0;
return isValid;
}
public static boolean isInWhiteList(BlockInfo info) {
for (String stackEntry : info.threadStackEntries) {
if (Character.isLetter(stackEntry.charAt(0))) {
String[] lines = stackEntry.split(BlockInfo.SEPARATOR);
for (String line : lines) {
for (String whiteListEntry : WHITE_LIST) {
if (line.startsWith(whiteListEntry)) {
return true;
}
}
}
}
}
return false;
}
public static List getConcernPackages() {
return CONCERN_LIST;
}
private static String concernStackString(String line) {
for (String concernPackage : CONCERN_LIST) {
if (line.startsWith(concernPackage)) {
return classSimpleName(line);
}
}
return null;
}
private static String classSimpleName(String stackLine) {
int index1 = stackLine.indexOf('(');
int index2 = stackLine.indexOf(')');
if (index1 >= 0 && index2 >= 0) {
return stackLine.substring(index1 + 1, index2);
}
return stackLine;
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockInfoCorruptException.java
================================================
package com.github.moduth.blockcanary.ui;
import java.util.Locale;
/**
* @author markzhai on 16/8/24
* @version 1.3.0
*/
public class BlockInfoCorruptException extends Exception {
public BlockInfoCorruptException(BlockInfoEx blockInfo) {
this(String.format(Locale.US,
"BlockInfo (%s) is corrupt.", blockInfo.logFile.getName()));
}
public BlockInfoCorruptException(String detailMessage) {
super(detailMessage);
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockInfoEx.java
================================================
package com.github.moduth.blockcanary.ui;
import android.util.Log;
import com.github.moduth.blockcanary.internal.BlockInfo;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
final class BlockInfoEx extends BlockInfo {
private static final String TAG = "BlockInfoEx";
public File logFile;
public String concernStackString;
/**
* Create {@link BlockInfoEx} from saved log file.
*
* @param file looper log file
* @return LooperLog created from log file
*/
public static BlockInfoEx newInstance(File file) {
BlockInfoEx blockInfo = new BlockInfoEx();
blockInfo.logFile = file;
BufferedReader reader = null;
try {
InputStreamReader in = new InputStreamReader(new FileInputStream(file), "UTF-8");
reader = new BufferedReader(in);
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
if (line.startsWith(KEY_QUA)) {
blockInfo.qualifier = line.split(KV)[1];
} else if (line.startsWith(KEY_MODEL)) {
blockInfo.model = line.split(KV)[1];
} else if (line.startsWith(KEY_API)) {
blockInfo.apiLevel = line.split(KV)[1];
} else if (line.startsWith(KEY_IMEI)) {
blockInfo.imei = line.split(KV)[1];
} else if (line.startsWith(KEY_CPU_CORE)) {
blockInfo.cpuCoreNum = Integer.valueOf(line.split(KV)[1]);
} else if (line.startsWith(KEY_UID)) {
blockInfo.uid = line.split(KV)[1];
} else if (line.startsWith(KEY_TIME_COST_START)) {
blockInfo.timeStart = line.split(KV)[1];
} else if (line.startsWith(KEY_TIME_COST_END)) {
blockInfo.timeEnd = line.split(KV)[1];
} else if (line.startsWith(KEY_TIME_COST)) {
blockInfo.timeCost = Long.valueOf(line.split(KV)[1]);
} else if (line.startsWith(KEY_THREAD_TIME_COST)) {
blockInfo.threadTimeCost = Long.valueOf(line.split(KV)[1]);
} else if (line.startsWith(KEY_PROCESS)) {
blockInfo.processName = line.split(KV)[1];
} else if (line.startsWith(KEY_VERSION_NAME)) {
blockInfo.versionName = line.split(KV)[1];
} else if (line.startsWith(KEY_VERSION_CODE)) {
blockInfo.versionCode = Integer.valueOf(line.split(KV)[1]);
} else if (line.startsWith(KEY_NETWORK)) {
blockInfo.network = line.split(KV)[1];
} else if (line.startsWith(KEY_TOTAL_MEMORY)) {
blockInfo.totalMemory = line.split(KV)[1];
} else if (line.startsWith(KEY_FREE_MEMORY)) {
blockInfo.freeMemory = line.split(KV)[1];
} else if (line.startsWith(KEY_CPU_BUSY)) {
blockInfo.cpuBusy = Boolean.valueOf(line.split(KV)[1]);
} else if (line.startsWith(KEY_CPU_RATE)) {
String[] split = line.split(KV);
if (split.length > 1) {
StringBuilder cpuRateSb = new StringBuilder(split[1]);
cpuRateSb.append(line.split(KV)[1]).append(SEPARATOR);
line = reader.readLine();
// read until SEPARATOR appears
while (line != null) {
if (!line.equals("")) {
cpuRateSb.append(line).append(SEPARATOR);
} else {
break;
}
line = reader.readLine();
}
blockInfo.cpuRateInfo = cpuRateSb.toString();
}
} else if (line.startsWith(KEY_STACK)) {
StringBuilder stackSb = new StringBuilder(line.split(KV)[1]);
line = reader.readLine();
// read until file ends
while (line != null) {
if (!line.equals("")) {
stackSb.append(line).append(SEPARATOR);
} else if (stackSb.length() > 0) {
// ignore continual blank lines
blockInfo.threadStackEntries.add(stackSb.toString());
stackSb = new StringBuilder();
}
line = reader.readLine();
}
}
}
reader.close();
reader = null;
} catch (Throwable t) {
Log.e(TAG, NEW_INSTANCE_METHOD, t);
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (Exception e) {
Log.e(TAG, NEW_INSTANCE_METHOD, e);
}
}
blockInfo.flushString();
return blockInfo;
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/DetailAdapter.java
================================================
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary.ui;
import android.content.Context;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.github.moduth.blockcanary.R;
import com.github.moduth.blockcanary.internal.BlockInfo;
import java.util.Arrays;
final class DetailAdapter extends BaseAdapter {
private static final int TOP_ROW = 0;
private static final int NORMAL_ROW = 1;
private boolean[] mFoldings = new boolean[0];
private BlockInfo mBlockInfo;
private static final int POSITION_BASIC = 1;
private static final int POSITION_TIME = 2;
private static final int POSITION_CPU = 3;
private static final int POSITION_THREAD_STACK = 4;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Context context = parent.getContext();
if (getItemViewType(position) == TOP_ROW) {
if (convertView == null) {
convertView =
LayoutInflater.from(context).inflate(R.layout.block_canary_ref_top_row, parent, false);
}
TextView textView = findById(convertView, R.id.__leak_canary_row_text);
textView.setText(context.getPackageName());
} else {
if (convertView == null) {
convertView =
LayoutInflater.from(context).inflate(R.layout.block_canary_ref_row, parent, false);
}
TextView textView = findById(convertView, R.id.__leak_canary_row_text);
boolean isThreadStackEntry = position == POSITION_THREAD_STACK + 1;
String element = getItem(position);
String htmlString = elementToHtmlString(element, position, mFoldings[position]);
if (isThreadStackEntry && !mFoldings[position]) {
htmlString += " " + "blocked" + "";
}
textView.setText(Html.fromHtml(htmlString));
DisplayConnectorView connectorView = findById(convertView, R.id.__leak_canary_row_connector);
connectorView.setType(connectorViewType(position));
MoreDetailsView moreDetailsView = findById(convertView, R.id.__leak_canary_row_more);
moreDetailsView.setFolding(mFoldings[position]);
}
return convertView;
}
private DisplayConnectorView.Type connectorViewType(int position) {
return (position == 1) ? DisplayConnectorView.Type.START : (
(position == getCount() - 1) ? DisplayConnectorView.Type.END :
DisplayConnectorView.Type.NODE);
}
private String elementToHtmlString(String element, int position, boolean folding) {
String htmlString = element.replaceAll(BlockInfo.SEPARATOR, " ");
switch (position) {
case POSITION_BASIC:
if (folding) {
htmlString = htmlString.substring(htmlString.indexOf(BlockInfo.KEY_CPU_CORE));
}
htmlString = String.format("%s ", htmlString);
break;
case POSITION_TIME:
if (folding) {
htmlString = htmlString.substring(0, htmlString.indexOf(BlockInfo.KEY_TIME_COST_START));
}
htmlString = String.format("%s ", htmlString);
break;
case POSITION_CPU:
// FIXME Figure out why sometimes \r\n cannot replace completely
htmlString = element;
if (folding) {
htmlString = htmlString.substring(0, htmlString.indexOf(BlockInfo.KEY_CPU_RATE));
}
htmlString = htmlString.replace("cpurate = ", " cpurate ");
htmlString = String.format("%s ", htmlString);
htmlString = htmlString.replaceAll("]", "] ");
break;
case POSITION_THREAD_STACK:
default:
if (folding) {
for (String concernPackage : BlockCanaryUtils.getConcernPackages()) {
int index = htmlString.indexOf(concernPackage);
if (index > 0) {
htmlString = htmlString.substring(index);
break;
}
}
}
htmlString = String.format("%s ", htmlString);
break;
}
return htmlString;
}
public void update(BlockInfo blockInfo) {
if (mBlockInfo != null && blockInfo.timeStart.equals(mBlockInfo.timeStart)) {
// Same data, nothing to change.
return;
}
mBlockInfo = blockInfo;
mFoldings = new boolean[POSITION_THREAD_STACK + mBlockInfo.threadStackEntries.size()];
Arrays.fill(mFoldings, true);
notifyDataSetChanged();
}
public void toggleRow(int position) {
mFoldings[position] = !mFoldings[position];
notifyDataSetChanged();
}
@Override
public int getCount() {
if (mBlockInfo == null) {
return 0;
}
return POSITION_THREAD_STACK + mBlockInfo.threadStackEntries.size();
}
@Override
public String getItem(int position) {
if (getItemViewType(position) == TOP_ROW) {
return null;
}
switch (position) {
case POSITION_BASIC:
return mBlockInfo.getBasicString();
case POSITION_TIME:
return mBlockInfo.getTimeString();
case POSITION_CPU:
return mBlockInfo.getCpuString();
case POSITION_THREAD_STACK:
default:
return mBlockInfo.threadStackEntries.get(position - POSITION_THREAD_STACK);
}
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TOP_ROW;
}
return NORMAL_ROW;
}
@Override
public long getItemId(int position) {
return position;
}
@SuppressWarnings("unchecked")
private static T findById(View view, int id) {
return (T) view.findViewById(id);
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/DisplayActivity.java
================================================
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary.ui;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.github.moduth.blockcanary.BlockCanaryContext;
import com.github.moduth.blockcanary.BlockCanaryInternals;
import com.github.moduth.blockcanary.LogWriter;
import com.github.moduth.blockcanary.R;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.DateUtils.FORMAT_SHOW_TIME;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
/**
* Display blocks.
*/
public class DisplayActivity extends Activity {
private static final String TAG = "DisplayActivity";
private static final String SHOW_BLOCK_EXTRA = "show_latest";
public static final String SHOW_BLOCK_EXTRA_KEY = "BlockStartTime";
// empty until it's been first loaded.
private List mBlockInfoEntries = new ArrayList<>();
private String mBlockStartTime;
private ListView mListView;
private TextView mFailureView;
private Button mActionButton;
private int mMaxStoredBlockCount;
public static PendingIntent createPendingIntent(Context context, String blockStartTime) {
Intent intent = new Intent(context, DisplayActivity.class);
intent.putExtra(SHOW_BLOCK_EXTRA, blockStartTime);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
return PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mBlockStartTime = savedInstanceState.getString(SHOW_BLOCK_EXTRA_KEY);
} else {
Intent intent = getIntent();
if (intent.hasExtra(SHOW_BLOCK_EXTRA)) {
mBlockStartTime = intent.getStringExtra(SHOW_BLOCK_EXTRA);
}
}
setContentView(R.layout.block_canary_display_leak);
mListView = (ListView) findViewById(R.id.__leak_canary_display_leak_list);
mFailureView = (TextView) findViewById(R.id.__leak_canary_display_leak_failure);
mActionButton = (Button) findViewById(R.id.__leak_canary_action);
mMaxStoredBlockCount = getResources().getInteger(R.integer.block_canary_max_stored_count);
updateUi();
}
// No, it's not deprecated. Android lies.
@Override
public Object onRetainNonConfigurationInstance() {
return mBlockInfoEntries;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(SHOW_BLOCK_EXTRA_KEY, mBlockStartTime);
}
@Override
protected void onResume() {
super.onResume();
LoadBlocks.load(this);
}
@Override
public void setTheme(int resid) {
// We don't want this to be called with an incompatible theme.
// This could happen if you implement runtime switching of themes
// using ActivityLifecycleCallbacks.
if (resid != R.style.block_canary_BlockCanary_Base) {
return;
}
super.setTheme(resid);
}
@Override
protected void onDestroy() {
super.onDestroy();
LoadBlocks.forgetActivity();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final BlockInfoEx blockInfo = getBlock(mBlockStartTime);
if (blockInfo != null) {
menu.add(R.string.block_canary_share_leak)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
shareBlock(blockInfo);
return true;
}
});
menu.add(R.string.block_canary_share_stack_dump)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
shareHeapDump(blockInfo);
return true;
}
});
return true;
}
return false;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
mBlockStartTime = null;
updateUi();
}
return true;
}
@Override
public void onBackPressed() {
if (mBlockStartTime != null) {
mBlockStartTime = null;
updateUi();
} else {
super.onBackPressed();
}
}
private void shareBlock(BlockInfoEx blockInfo) {
String leakInfo = blockInfo.toString();
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, leakInfo);
startActivity(Intent.createChooser(intent, getString(R.string.block_canary_share_with)));
}
private void shareHeapDump(BlockInfoEx blockInfo) {
File heapDumpFile = blockInfo.logFile;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
heapDumpFile.setReadable(true, false);
}
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("application/octet-stream");
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(heapDumpFile));
startActivity(Intent.createChooser(intent, getString(R.string.block_canary_share_with)));
}
private void updateUi() {
final BlockInfoEx blockInfo = getBlock(mBlockStartTime);
if (blockInfo == null) {
mBlockStartTime = null;
}
// Reset to defaults
mListView.setVisibility(VISIBLE);
mFailureView.setVisibility(GONE);
if (blockInfo != null) {
renderBlockDetail(blockInfo);
} else {
renderBlockList();
}
}
private void renderBlockList() {
ListAdapter listAdapter = mListView.getAdapter();
if (listAdapter instanceof BlockListAdapter) {
((BlockListAdapter) listAdapter).notifyDataSetChanged();
} else {
BlockListAdapter adapter = new BlockListAdapter();
mListView.setAdapter(adapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
mBlockStartTime = mBlockInfoEntries.get(position).timeStart;
updateUi();
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
invalidateOptionsMenu();
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(false);
}
}
setTitle(getString(R.string.block_canary_block_list_title, getPackageName()));
mActionButton.setText(R.string.block_canary_delete_all);
mActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DialogInterface.OnClickListener okListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
LogWriter.deleteAll();
mBlockInfoEntries = Collections.emptyList();
updateUi();
}
};
new AlertDialog.Builder(DisplayActivity.this)
.setTitle(getString(R.string.block_canary_delete))
.setMessage(getString(R.string.block_canary_delete_all_dialog_content))
.setPositiveButton(getString(R.string.block_canary_yes), okListener)
.setNegativeButton(getString(R.string.block_canary_no), null)
.show();
}
});
}
mActionButton.setVisibility(mBlockInfoEntries.isEmpty() ? GONE : VISIBLE);
}
private void renderBlockDetail(final BlockInfoEx blockInfo) {
ListAdapter listAdapter = mListView.getAdapter();
final DetailAdapter adapter;
if (listAdapter instanceof DetailAdapter) {
adapter = (DetailAdapter) listAdapter;
} else {
adapter = new DetailAdapter();
mListView.setAdapter(adapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
adapter.toggleRow(position);
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
invalidateOptionsMenu();
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
mActionButton.setVisibility(VISIBLE);
mActionButton.setText(R.string.block_canary_delete);
}
mActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (blockInfo != null) {
blockInfo.logFile.delete();
mBlockStartTime = null;
mBlockInfoEntries.remove(blockInfo);
updateUi();
}
}
});
adapter.update(blockInfo);
setTitle(getString(R.string.block_canary_class_has_blocked, blockInfo.timeCost));
}
private BlockInfoEx getBlock(String startTime) {
if (mBlockInfoEntries == null || TextUtils.isEmpty(startTime)) {
return null;
}
for (BlockInfoEx blockInfo : mBlockInfoEntries) {
if (blockInfo.timeStart != null && startTime.equals(blockInfo.timeStart)) {
return blockInfo;
}
}
return null;
}
class BlockListAdapter extends BaseAdapter {
@Override
public int getCount() {
return mBlockInfoEntries.size();
}
@Override
public BlockInfoEx getItem(int position) {
return mBlockInfoEntries.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(DisplayActivity.this)
.inflate(R.layout.block_canary_block_row, parent, false);
}
TextView titleView = (TextView) convertView.findViewById(R.id.__leak_canary_row_text);
TextView timeView = (TextView) convertView.findViewById(R.id.__leak_canary_row_time);
BlockInfoEx blockInfo = getItem(position);
String index;
if (position == 0 && mBlockInfoEntries.size() == mMaxStoredBlockCount) {
index = "MAX. ";
} else {
index = (mBlockInfoEntries.size() - position) + ". ";
}
String keyStackString = BlockCanaryUtils.concernStackString(blockInfo);
String title = index + keyStackString + " " +
getString(R.string.block_canary_class_has_blocked, blockInfo.timeCost);
titleView.setText(title);
String time = DateUtils.formatDateTime(DisplayActivity.this,
blockInfo.logFile.lastModified(), FORMAT_SHOW_TIME | FORMAT_SHOW_DATE);
timeView.setText(time);
return convertView;
}
}
static class LoadBlocks implements Runnable {
static final List inFlight = new ArrayList<>();
static final Executor backgroundExecutor = Executors.newSingleThreadExecutor();
private DisplayActivity activityOrNull;
private final Handler mainHandler;
LoadBlocks(DisplayActivity activity) {
this.activityOrNull = activity;
mainHandler = new Handler(Looper.getMainLooper());
}
static void load(DisplayActivity activity) {
LoadBlocks loadBlocks = new LoadBlocks(activity);
inFlight.add(loadBlocks);
backgroundExecutor.execute(loadBlocks);
}
static void forgetActivity() {
for (LoadBlocks loadBlocks : inFlight) {
loadBlocks.activityOrNull = null;
}
inFlight.clear();
}
@Override
public void run() {
final List blockInfoList = new ArrayList<>();
File[] files = BlockCanaryInternals.getLogFiles();
if (files != null) {
for (File blockFile : files) {
try {
BlockInfoEx blockInfo = BlockInfoEx.newInstance(blockFile);
if (!BlockCanaryUtils.isBlockInfoValid(blockInfo)) {
throw new BlockInfoCorruptException(blockInfo);
}
boolean needAddToList = true;
if (BlockCanaryUtils.isInWhiteList(blockInfo)) {
if (BlockCanaryContext.get().deleteFilesInWhiteList()) {
blockFile.delete();
blockFile = null;
}
needAddToList = false;
}
blockInfo.concernStackString = BlockCanaryUtils.concernStackString(blockInfo);
if (BlockCanaryContext.get().filterNonConcernStack() &&
TextUtils.isEmpty(blockInfo.concernStackString)) {
needAddToList = false;
}
if (needAddToList && blockFile != null) {
blockInfoList.add(blockInfo);
}
} catch (Exception e) {
// Probably blockFile corrupts or format changes, just delete it.
blockFile.delete();
Log.e(TAG, "Could not read block log file, deleted :" + blockFile, e);
}
}
Collections.sort(blockInfoList, new Comparator() {
@Override
public int compare(BlockInfoEx lhs, BlockInfoEx rhs) {
return Long.valueOf(rhs.logFile.lastModified())
.compareTo(lhs.logFile.lastModified());
}
});
}
mainHandler.post(new Runnable() {
@Override
public void run() {
inFlight.remove(LoadBlocks.this);
if (activityOrNull != null) {
activityOrNull.mBlockInfoEntries = blockInfoList;
Log.d(TAG, "load block entries: " + blockInfoList.size());
activityOrNull.updateUi();
}
}
});
}
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/DisplayConnectorView.java
================================================
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary.ui;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import static android.graphics.Bitmap.Config.ARGB_8888;
public final class DisplayConnectorView extends View {
private static final Paint iconPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static final Paint rootPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static final Paint leakPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static final Paint clearPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
static {
iconPaint.setColor(BlockCanaryUi.LIGHT_GREY);
rootPaint.setColor(BlockCanaryUi.ROOT_COLOR);
leakPaint.setColor(BlockCanaryUi.LEAK_COLOR);
clearPaint.setColor(Color.TRANSPARENT);
clearPaint.setXfermode(BlockCanaryUi.CLEAR_XFER_MODE);
}
public enum Type {
START, NODE, END
}
private Type type;
private Bitmap cache;
public DisplayConnectorView(Context context, AttributeSet attrs) {
super(context, attrs);
type = Type.NODE;
}
@SuppressWarnings("SuspiciousNameCombination")
@Override
protected void onDraw(Canvas canvas) {
int width = getWidth();
int height = getHeight();
if (cache != null && (cache.getWidth() != width || cache.getHeight() != height)) {
cache.recycle();
cache = null;
}
if (cache == null) {
cache = Bitmap.createBitmap(width, height, ARGB_8888);
Canvas cacheCanvas = new Canvas(cache);
float halfWidth = width / 2f;
float halfHeight = height / 2f;
float thirdWidth = width / 3f;
float strokeSize = BlockCanaryUi.dpToPixel(4f, getResources());
iconPaint.setStrokeWidth(strokeSize);
rootPaint.setStrokeWidth(strokeSize);
switch (type) {
case NODE:
cacheCanvas.drawLine(halfWidth, 0, halfWidth, height, iconPaint);
cacheCanvas.drawCircle(halfWidth, halfHeight, halfWidth, iconPaint);
cacheCanvas.drawCircle(halfWidth, halfHeight, thirdWidth, clearPaint);
break;
case START:
float radiusClear = halfWidth - strokeSize / 2f;
cacheCanvas.drawRect(0, 0, width, radiusClear, rootPaint);
cacheCanvas.drawCircle(0, radiusClear, radiusClear, clearPaint);
cacheCanvas.drawCircle(width, radiusClear, radiusClear, clearPaint);
cacheCanvas.drawLine(halfWidth, 0, halfWidth, halfHeight, rootPaint);
cacheCanvas.drawLine(halfWidth, halfHeight, halfWidth, height, iconPaint);
cacheCanvas.drawCircle(halfWidth, halfHeight, halfWidth, iconPaint);
cacheCanvas.drawCircle(halfWidth, halfHeight, thirdWidth, clearPaint);
break;
default:
cacheCanvas.drawLine(halfWidth, 0, halfWidth, halfHeight, iconPaint);
cacheCanvas.drawCircle(halfWidth, halfHeight, thirdWidth, leakPaint);
break;
}
}
canvas.drawBitmap(cache, 0, 0, null);
}
public void setType(Type type) {
if (type != this.type) {
this.type = type;
if (cache != null) {
cache.recycle();
cache = null;
}
invalidate();
}
}
}
================================================
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/MoreDetailsView.java
================================================
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary.ui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public final class MoreDetailsView extends View {
private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private boolean mFolding = true;
public MoreDetailsView(Context context, AttributeSet attrs) {
super(context, attrs);
mIconPaint.setStrokeWidth(BlockCanaryUi.dpToPixel(2f, getResources()));
mIconPaint.setColor(BlockCanaryUi.ROOT_COLOR);
}
@Override protected void onDraw(Canvas canvas) {
int width = getWidth();
int height = getHeight();
int halfHeight = height / 2;
int halfWidth = width / 2;
canvas.drawLine(0, halfHeight, width, halfHeight, mIconPaint);
if (mFolding) {
canvas.drawLine(halfWidth, 0, halfWidth, height, mIconPaint);
}
}
public void setFolding(boolean folding) {
if (folding != this.mFolding) {
this.mFolding = folding;
invalidate();
}
}
}
================================================
FILE: blockcanary-android/src/main/res/layout/block_canary_block_row.xml
================================================
================================================
FILE: blockcanary-android/src/main/res/layout/block_canary_display_leak.xml
================================================
================================================
FILE: blockcanary-android/src/main/res/layout/block_canary_ref_row.xml
================================================
================================================
FILE: blockcanary-android/src/main/res/layout/block_canary_ref_top_row.xml
================================================
================================================
FILE: blockcanary-android/src/main/res/values/strings.xml
================================================
BlocksShare infoShare stack dumpShare with…Blocks in %sDeleteDelete allAre you sure to delete all records?blocked %s msClick for more detailsYesNo
================================================
FILE: blockcanary-android/src/main/res/values/themes.xml
================================================
================================================
FILE: blockcanary-android/src/main/res/values-v14/themes.xml
================================================
================================================
FILE: blockcanary-android/src/main/res/values-v21/themes.xml
================================================
================================================
FILE: blockcanary-android-no-op/.gitignore
================================================
/build
================================================
FILE: blockcanary-android-no-op/build.gradle
================================================
apply plugin: 'com.android.library'
apply from: 'gradle-mvn-push.gradle'
android {
compileSdkVersion LIBRARY_COMPILE_SDK_VERSION
buildToolsVersion LIBRARY_BUILD_TOOLS_VERSION
defaultConfig {
minSdkVersion LIBRARY_MIN_SDK_VERSION
targetSdkVersion LIBRARY_TARGET_SDK_VERSION
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
================================================
FILE: blockcanary-android-no-op/gradle-mvn-push.gradle
================================================
/*
* Copyright 2013 Chris Banes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'maven'
apply plugin: 'signing'
def isReleaseBuild() {
return VERSION_NAME.contains("SNAPSHOT") == false
}
def getReleaseRepositoryUrl() {
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}
def getSnapshotRepositoryUrl() {
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: "https://oss.sonatype.org/content/repositories/snapshots/"
}
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
pom.groupId = GROUP
pom.artifactId = POM_ARTIFACT_ID
pom.version = VERSION_NAME
repository(url: getReleaseRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
snapshotRepository(url: getSnapshotRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
pom.project {
name POM_NAME
packaging POM_PACKAGING
description POM_DESCRIPTION
url POM_URL
scm {
url POM_SCM_URL
connection POM_SCM_CONNECTION
developerConnection POM_SCM_DEV_CONNECTION
}
licenses {
license {
name POM_LICENCE_NAME
url POM_LICENCE_URL
distribution POM_LICENCE_DIST
}
}
developers {
developer {
id POM_DEVELOPER_ID
name POM_DEVELOPER_NAME
}
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
task androidJavadocs(type: Javadoc) {
options.encoding = "utf-8"
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}
}
================================================
FILE: blockcanary-android-no-op/gradle.properties
================================================
POM_NAME=Android BlockCanary NO-OP Library
POM_ARTIFACT_ID=blockcanary-no-op
POM_PACKAGING=jar
VERSION_NAME=1.5.0
VERSION_CODE=16
GROUP=com.github.markzhai
POM_DESCRIPTION=Android BlockCanary NO-OP Library
POM_URL=https://github.com/markzhai/AndroidPerformanceMonitor
POM_SCM_URL=https://github.com/markzhai/AndroidPerformanceMonitor
POM_SCM_CONNECTION=scm:https://github.com/markzhai/AndroidPerformanceMonitor.git
POM_SCM_DEV_CONNECTION=scm:https://github.com/markzhai/AndroidPerformanceMonitor.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=markzhai
POM_DEVELOPER_NAME=markzhai
POM_DEVELOPER_URL=https://github.com/markzhai
================================================
FILE: blockcanary-android-no-op/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/yifan/dev/sdk/adt-bundle-mac-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: blockcanary-android-no-op/src/main/AndroidManifest.xml
================================================
================================================
FILE: blockcanary-android-no-op/src/main/java/com/github/moduth/blockcanary/BlockCanary.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.content.Context;
import android.util.Log;
/**
* No-op implementation.
*/
public final class BlockCanary {
private static final String TAG = "BlockCanary-no-op";
private static BlockCanary sInstance = null;
private BlockCanary() {
}
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
BlockCanaryContext.init(context, blockCanaryContext);
return get();
}
public static BlockCanary get() {
if (sInstance == null) {
synchronized (BlockCanary.class) {
if (sInstance == null) {
sInstance = new BlockCanary();
}
}
}
return sInstance;
}
public void start() {
Log.i(TAG, "start");
}
public void stop() {
Log.i(TAG, "stop");
}
public void upload() {
Log.i(TAG, "upload");
}
public void recordStartTime() {
Log.i(TAG, "recordStartTime");
}
public boolean isMonitorDurationEnd() {
return true;
}
}
================================================
FILE: blockcanary-android-no-op/src/main/java/com/github/moduth/blockcanary/BlockCanaryContext.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.moduth.blockcanary;
import android.content.Context;
import com.github.moduth.blockcanary.internal.BlockInfo;
import java.io.File;
import java.util.List;
/**
* No-op context.
*/
public class BlockCanaryContext {
private static Context sApplicationContext;
private static BlockCanaryContext sInstance = null;
public BlockCanaryContext() {
}
static void init(Context c, BlockCanaryContext g) {
sApplicationContext = c;
sInstance = g;
}
public static BlockCanaryContext get() {
if (sInstance == null) {
throw new RuntimeException("BlockCanaryContext not init");
} else {
return sInstance;
}
}
public Context provideContext() {
return sApplicationContext;
}
public String provideQualifier() {
return "Unspecified";
}
public String provideUid() {
return "0";
}
public String provideNetworkType() {
return "UNKNOWN";
}
public int provideMonitorDuration() {
return 99999;
}
public int provideBlockThreshold() {
return 1000;
}
public int provideDumpInterval() {
return provideBlockThreshold();
}
public String providePath() {
return "/blockcanary/";
}
public boolean displayNotification() {
return false;
}
public boolean zip(File[] src, File dest) {
return false;
}
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
public List concernPackages() {
return null;
}
public boolean filterNonConcernStack() {
return false;
}
public List provideWhiteList() {
return null;
}
public boolean deleteFilesInWhiteList() {
return false;
}
public void onBlock(Context context, BlockInfo blockInfo) {
}
public boolean stopWhenDebugging() {
return true;
}
}
================================================
FILE: blockcanary-android-no-op/src/main/java/com/github/moduth/blockcanary/internal/BlockInfo.java
================================================
package com.github.moduth.blockcanary.internal;
public class BlockInfo {
}
================================================
FILE: blockcanary-sample/.gitignore
================================================
/build
================================================
FILE: blockcanary-sample/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion LIBRARY_COMPILE_SDK_VERSION
buildToolsVersion LIBRARY_BUILD_TOOLS_VERSION
defaultConfig {
applicationId "com.example.blockcanary"
minSdkVersion LIBRARY_MIN_SDK_VERSION
targetSdkVersion LIBRARY_TARGET_SDK_VERSION
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
debugCompile project(':blockcanary-android')
releaseCompile project(':blockcanary-android-no-op')
compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:design:24.2.0'
}
================================================
FILE: blockcanary-sample/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/yifan/dev/sdk/adt-bundle-mac-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: blockcanary-sample/src/main/AndroidManifest.xml
================================================
================================================
FILE: blockcanary-sample/src/main/java/com/example/blockcanary/AppContext.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.blockcanary;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import com.github.moduth.blockcanary.BlockCanaryContext;
import java.util.List;
public class AppContext extends BlockCanaryContext {
private static final String TAG = "AppContext";
@Override
public String provideQualifier() {
String qualifier = "";
try {
PackageInfo info = DemoApplication.getAppContext().getPackageManager()
.getPackageInfo(DemoApplication.getAppContext().getPackageName(), 0);
qualifier += info.versionCode + "_" + info.versionName + "_YYB";
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "provideQualifier exception", e);
}
return qualifier;
}
@Override
public String provideUid() {
return "87224330";
}
@Override
public String provideNetworkType() {
return "4G";
}
@Override
public int provideMonitorDuration() {
return 9999;
}
@Override
public int provideBlockThreshold() {
return 500;
}
@Override
public boolean displayNotification() {
return BuildConfig.DEBUG;
}
@Override
public List concernPackages() {
List list = super.provideWhiteList();
list.add("com.example");
return list;
}
@Override
public List provideWhiteList() {
List list = super.provideWhiteList();
list.add("com.whitelist");
return list;
}
@Override
public boolean stopWhenDebugging() {
return true;
}
}
================================================
FILE: blockcanary-sample/src/main/java/com/example/blockcanary/DemoActivity.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.blockcanary;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
public class DemoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
getSupportFragmentManager().beginTransaction()
.add(R.id.container, DemoFragment.newInstance())
.commit();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showTipDialog();
}
});
}
private void showTipDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Tip");
builder.setMessage(getResources().getString(R.string.hello_world));
builder.setNegativeButton("ok", null);
builder.show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_demo, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
================================================
FILE: blockcanary-sample/src/main/java/com/example/blockcanary/DemoApplication.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.blockcanary;
import android.app.Application;
import android.content.Context;
import com.github.moduth.blockcanary.BlockCanary;
public class DemoApplication extends Application {
private static Context sContext;
@Override
public void onCreate() {
super.onCreate();
sContext = this;
BlockCanary.install(this, new AppContext()).start();
}
public static Context getAppContext() {
return sContext;
}
}
================================================
FILE: blockcanary-sample/src/main/java/com/example/blockcanary/DemoFragment.java
================================================
/*
* Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.blockcanary;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import java.io.FileInputStream;
import java.io.IOException;
public class DemoFragment extends Fragment implements View.OnClickListener {
private static final String DEMO_FRAGMENT = "DemoFragment";
public static DemoFragment newInstance() {
return new DemoFragment();
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(R.layout.activity_main, null);
}
@Override
public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Button button1 = (Button) view.findViewById(R.id.button1);
Button button2 = (Button) view.findViewById(R.id.button2);
Button button3 = (Button) view.findViewById(R.id.button3);
button1.setOnClickListener(this);
button2.setOnClickListener(this);
button3.setOnClickListener(this);
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
Log.e(DEMO_FRAGMENT, "onClick of R.id.button1: ", e);
}
break;
case R.id.button2:
for (int i = 0; i < 100; ++i) {
readFile();
}
break;
case R.id.button3:
double result = compute();
System.out.println(result);
break;
default:
break;
}
}
private static double compute() {
double result = 0;
for (int i = 0; i < 1000000; ++i) {
result += Math.acos(Math.cos(i));
result -= Math.asin(Math.sin(i));
}
return result;
}
private static void readFile() {
FileInputStream reader = null;
try {
reader = new FileInputStream("/proc/stat");
while (reader.read() != -1) ;
} catch (IOException e) {
Log.e(DEMO_FRAGMENT, "readFile: /proc/stat", e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
Log.e(DEMO_FRAGMENT, " on close reader ", e);
}
}
}
}
}
================================================
FILE: blockcanary-sample/src/main/res/drawable/background_splash.xml
================================================
================================================
FILE: blockcanary-sample/src/main/res/drawable/btn_nor.xml
================================================
================================================
FILE: blockcanary-sample/src/main/res/drawable/btn_pre.xml
================================================
================================================
FILE: blockcanary-sample/src/main/res/drawable/btn_select.xml
================================================
================================================
FILE: blockcanary-sample/src/main/res/layout/activity_demo.xml
================================================
================================================
FILE: blockcanary-sample/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: blockcanary-sample/src/main/res/menu/menu_demo.xml
================================================
================================================
FILE: blockcanary-sample/src/main/res/values/colors.xml
================================================
#FFF5F5F5#2196F3#1976D2
================================================
FILE: blockcanary-sample/src/main/res/values/dimens.xml
================================================
16dp16dp0dp48dp48dp0.5dp
================================================
FILE: blockcanary-sample/src/main/res/values/strings.xml
================================================
BlockCanaryAfter click button, main-thread consuming event will be triggered, which would be caught be BlockCanary, write to log file, and use notification to displayThread\nwait\nblockIO\nblockComputation\nblockWait\nfor\nmoreSettings
================================================
FILE: blockcanary-sample/src/main/res/values/styles.xml
================================================
================================================
FILE: blockcanary-sample/src/main/res/values-zh/strings.xml
================================================
BlockCanary点击按钮后,会触发主线程耗时时间,从而被BlockCanary,并记录卡慢日志,弹出notification,可以点击对应消息查看线程\n等待\n阻塞IO\n阻塞计算\n阻塞尽请\n期待\n更多Settings
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
}
}
allprojects {
repositories {
mavenCentral()
jcenter()
}
ext {
LIBRARY_COMPILE_SDK_VERSION = 23
LIBRARY_BUILD_TOOLS_VERSION = "23.0.3"
LIBRARY_MIN_SDK_VERSION = 9
LIBRARY_TARGET_SDK_VERSION = 22
}
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Apr 13 16:42:11 CST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
================================================
FILE: gradle.properties
================================================
## Project-wide Gradle settings.
#
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Sat Mar 05 00:07:08 CST 2016
org.gradle.jvmargs=-Xmx8192M
#org.gradle.jvmargs=-Xmx10248m -XX\:MaxPermSize\=512m -XX\:+HeapDumpOnOutOfMemoryError -Dfile.encoding\=UTF-8
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.parallel=true
android.useDeprecatedNdk=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
include ':blockcanary-android-no-op'
include ':blockcanary-analyzer'
include ':blockcanary-android'
include ':blockcanary-sample'