Showing preview only (206K chars total). Download the full file or copy to clipboard to get everything.
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
<integer name="block_canary_max_stored_count">1000</integer>
```
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
<string name="block_canary_display_activity_label">Blocks</string>
```
```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.
* <p>
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
* </p>
*
* @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<String> 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<String> provideWhiteList() {
LinkedList<String> 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.
* <p>
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
* </p>
*
* @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<String> 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<String> provideWhiteList() {
LinkedList<String> 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
================================================
<manifest
package="com.github.moduth.blockcanary.core"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- To store the heap dumps and leak analysis results. -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application/>
</manifest>
================================================
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.
* <p>
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
* </p>
*
* @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<String> 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<String> provideWhiteList() {
LinkedList<String> 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<BlockInterceptor> 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<String> 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<Long, String> 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<Long, String> 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<Long, String> 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<Long, String> 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<Long, String> 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<String> getThreadStackEntries(long startTime, long endTime) {
ArrayList<String> 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<String> 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<String> 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<ActivityManager.RunningAppProcessInfo> 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
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="block_canary_max_stored_count">500</integer>
</resources>
================================================
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
================================================
<manifest
package="com.github.moduth.blockcanary"
xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".ui.DisplayActivity"
android:enabled="false"
android:icon="@drawable/block_canary_icon"
android:label="@string/block_canary_display_activity_label"
android:taskAffinity="com.github.moduth.blockcanary"
android:theme="@style/block_canary_BlockCanary.Base">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
================================================
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<String> WHITE_LIST = new LinkedList<>();
private static final List<String> 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<String> 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 += " <font color='#919191'>" + "blocked" + "</font>";
}
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, "<br>");
switch (position) {
case POSITION_BASIC:
if (folding) {
htmlString = htmlString.substring(htmlString.indexOf(BlockInfo.KEY_CPU_CORE));
}
htmlString = String.format("<font color='#c48a47'>%s</font> ", htmlString);
break;
case POSITION_TIME:
if (folding) {
htmlString = htmlString.substring(0, htmlString.indexOf(BlockInfo.KEY_TIME_COST_START));
}
htmlString = String.format("<font color='#f3cf83'>%s</font> ", 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 = ", "<br>cpurate<br/>");
htmlString = String.format("<font color='#998bb5'>%s</font> ", htmlString);
htmlString = htmlString.replaceAll("]", "]<br>");
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("<font color='#ffffff'>%s</font> ", 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 extends View> 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<BlockInfoEx> 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<LoadBlocks> 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<BlockInfoEx> 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<BlockInfoEx>() {
@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
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:id="@+id/__leak_canary_row_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:textSize="18sp"/>
<TextView
android:id="@+id/__leak_canary_row_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="#919191"
android:textSize="14sp"/>
</LinearLayout>
================================================
FILE: blockcanary-android/src/main/res/layout/block_canary_display_leak.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#3c3c3c"
android:orientation="vertical">
<ListView
android:id="@+id/__leak_canary_display_leak_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:divider="@null"
android:dividerHeight="0dp"/>
<TextView
android:id="@+id/__leak_canary_display_leak_failure"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:textSize="12sp"
android:visibility="gone"/>
<Button
android:id="@+id/__leak_canary_action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
</LinearLayout>
================================================
FILE: blockcanary-android/src/main/res/layout/block_canary_ref_row.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:orientation="horizontal">
<com.github.moduth.blockcanary.ui.DisplayConnectorView
android:id="@+id/__leak_canary_row_connector"
android:layout_width="16dp"
android:layout_height="match_parent"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"/>
<TextView
android:id="@+id/__leak_canary_row_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"/>
<com.github.moduth.blockcanary.ui.MoreDetailsView
android:id="@+id/__leak_canary_row_more"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"/>
</LinearLayout>
================================================
FILE: blockcanary-android/src/main/res/layout/block_canary_ref_top_row.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:id="@+id/__leak_canary_row_text"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="#84a6c5"
android:padding="16dp"
android:textColor="#ffffff"
android:textSize="18sp"/>
================================================
FILE: blockcanary-android/src/main/res/values/strings.xml
================================================
<resources>
<string name="block_canary_display_activity_label">Blocks</string>
<string name="block_canary_share_leak">Share info</string>
<string name="block_canary_share_stack_dump">Share stack dump</string>
<string name="block_canary_share_with">Share with…</string>
<string name="block_canary_block_list_title">Blocks in %s</string>
<string name="block_canary_delete">Delete</string>
<string name="block_canary_delete_all">Delete all</string>
<string name="block_canary_delete_all_dialog_content">Are you sure to delete all records?</string>
<string name="block_canary_class_has_blocked">blocked %s ms</string>
<string name="block_canary_notification_message">Click for more details</string>
<string name="block_canary_yes">Yes</string>
<string name="block_canary_no">No</string>
</resources>
================================================
FILE: blockcanary-android/src/main/res/values/themes.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="block_canary_BlockCanary.Base" parent="android:Theme">
</style>
</resources>
================================================
FILE: blockcanary-android/src/main/res/values-v14/themes.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="block_canary_BlockCanary.Base" parent="android:Theme.Holo">
</style>
</resources>
================================================
FILE: blockcanary-android/src/main/res/values-v21/themes.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="block_canary_BlockCanary.Base" parent="android:Theme.Material">
</style>
</resources>
================================================
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
================================================
<manifest
package="com.github.moduth.blockcanary">
<application/>
</manifest>
================================================
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<String> concernPackages() {
return null;
}
public boolean filterNonConcernStack() {
return false;
}
public List<String> 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
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.example.blockcanary"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".DemoApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/ModuThreeHandsomeTheme">
<activity
android:name=".DemoActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
================================================
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<String> concernPackages() {
List<String> list = super.provideWhiteList();
list.add("com.example");
return list;
}
@Override
public List<String> provideWhiteList() {
List<String> 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
================================================
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@android:color/white"/>
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/ic_launcher"/>
</item>
</layer-list>
================================================
FILE: blockcanary-sample/src/main/res/drawable/btn_nor.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/material_blue" />
<corners
android:bottomLeftRadius="4dp"
android:bottomRightRadius="4dp"
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
</shape>
================================================
FILE: blockcanary-sample/src/main/res/drawable/btn_pre.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/material_blue_dark" />
<corners
android:bottomLeftRadius="4dp"
android:bottomRightRadius="4dp"
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
</shape>
================================================
FILE: blockcanary-sample/src/main/res/drawable/btn_select.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_focused="true" android:drawable="@drawable/btn_pre"></item>
<item android:state_pressed="true" android:drawable="@drawable/btn_pre"></item>
<item android:drawable="@drawable/btn_nor"></item>
</selector>
================================================
FILE: blockcanary-sample/src/main/res/layout/activity_demo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false"
android:orientation="vertical">
<View
android:id="@+id/view_state_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/state_bar_height"
android:background="@color/material_blue" />
<FrameLayout
android:layout_below="@+id/view_state_bar"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="MergeRootFrame" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="16dp"
android:src="@drawable/ic_done"/>
</RelativeLayout>
================================================
FILE: blockcanary-sample/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/top_bar_height"
android:background="@color/material_blue">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/app_name"
android:textColor="@android:color/white"
android:textSize="18sp"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Button
android:id="@+id/button1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_weight="1"
android:background="@drawable/btn_select"
android:gravity="center"
android:text="@string/thread_waiting"
android:textColor="@android:color/white"
android:textSize="24sp"/>
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_weight="1"
android:background="@drawable/btn_select"
android:gravity="center"
android:text="@string/io_waiting"
android:textColor="@android:color/white"
android:textSize="24sp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Button
android:id="@+id/button3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_weight="1"
android:background="@drawable/btn_select"
android:gravity="center"
android:text="@string/cpu_busy"
android:textColor="@android:color/white"
android:textSize="24sp"/>
<Button
android:id="@+id/button4"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_weight="1"
android:background="@drawable/btn_select"
android:gravity="center"
android:text="@string/more"
android:textColor="@android:color/white"
android:textSize="24sp"/>
</LinearLayout>
</LinearLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="16dp"
android:src="@drawable/ic_done"/>
</RelativeLayout>
================================================
FILE: blockcanary-sample/src/main/res/menu/menu_demo.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" tools:context=".DemoActivity">
<item android:id="@+id/action_settings" android:title="@string/action_settings"
android:orderInCategory="100" />
</menu>
================================================
FILE: blockcanary-sample/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="window_background">#FFF5F5F5</color>
<color name="material_blue">#2196F3</color>
<color name="material_blue_dark">#1976D2</color>
</resources>
================================================
FILE: blockcanary-sample/src/main/res/values/dimens.xml
================================================
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="state_bar_height">0dp</dimen>
<dimen name="top_bar_height">48dp</dimen>
<dimen name="top_bar_btn_wh">48dp</dimen>
<dimen name="divider_height">0.5dp</dimen>
</resources>
================================================
FILE: blockcanary-sample/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">BlockCanary</string>
<string name="hello_world">After click button, main-thread consuming event will be triggered, which would be caught be BlockCanary, write to log file, and use notification to display</string>
<string name="thread_waiting">Thread\nwait\nblock</string>
<string name="io_waiting">IO\nblock</string>
<string name="cpu_busy">Computation\nblock</string>
<string name="more">Wait\nfor\nmore</string>
<string name="action_settings">Settings</string>
</resources>
================================================
FILE: blockcanary-sample/src/main/res/values/styles.xml
================================================
<resources>
<style name="CjjBaseTheme" parent="Base.Theme.DesignDemo">
</style>
<style name="Base.Theme.DesignDemo" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#2196F3</item>
<item name="colorPrimaryDark">#1976D2</item>
<item name="colorAccent">#F44336</item>
<item name="android:textColorPrimary">#ffffff</item>
<item name="android:windowBackground">@color/window_background</item>
</style>
<style name="ModuThreeHandsomeTheme" parent="Theme.AppCompat.Light.NoActionBar">
</style>
</resources>
================================================
FILE: blockcanary-sample/src/main/res/values-zh/strings.xml
================================================
<resources>
<string name="app_name">BlockCanary</string>
<string name="hello_world">点击按钮后,会触发主线程耗时时间,从而被BlockCanary,并记录卡慢日志,弹出notification,可以点击对应消息查看</string>
<string name="thread_waiting">线程\n等待\n阻塞</string>
<string name="io_waiting">IO\n阻塞</string>
<string name="cpu_busy">计算\n阻塞</string>
<string name="more">尽请\n期待\n更多</string>
<string name="action_settings">Settings</string>
</resources>
================================================
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
:win9
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
SYMBOL INDEX (245 symbols across 31 files)
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/AbstractSampler.java
class AbstractSampler (line 23) | abstract class AbstractSampler {
method run (line 31) | @Override
method AbstractSampler (line 42) | public AbstractSampler(long sampleInterval) {
method start (line 49) | public void start() {
method stop (line 60) | public void stop() {
method doSample (line 68) | abstract void doSample();
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/BlockCanaryContext.java
class BlockCanaryContext (line 29) | public class BlockCanaryContext implements BlockInterceptor {
method BlockCanaryContext (line 34) | public BlockCanaryContext() {
method init (line 37) | static void init(Context context, BlockCanaryContext blockCanaryContex...
method get (line 42) | public static BlockCanaryContext get() {
method provideContext (line 53) | public Context provideContext() {
method provideQualifier (line 62) | public String provideQualifier() {
method provideUid (line 71) | public String provideUid() {
method provideNetworkType (line 80) | public String provideNetworkType() {
method provideMonitorDuration (line 90) | public int provideMonitorDuration() {
method provideBlockThreshold (line 100) | public int provideBlockThreshold() {
method provideDumpInterval (line 114) | public int provideDumpInterval() {
method providePath (line 123) | public String providePath() {
method displayNotification (line 132) | public boolean displayNotification() {
method zip (line 143) | public boolean zip(File[] src, File dest) {
method upload (line 152) | public void upload(File zippedFile) {
method concernPackages (line 162) | public List<String> concernPackages() {
method filterNonConcernStack (line 171) | public boolean filterNonConcernStack() {
method provideWhiteList (line 180) | public List<String> provideWhiteList() {
method deleteFilesInWhiteList (line 191) | public boolean deleteFilesInWhiteList() {
method onBlock (line 198) | @Override
method stopWhenDebugging (line 208) | public boolean stopWhenDebugging() {
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/BlockCanaryInternals.java
class BlockCanaryInternals (line 29) | public final class BlockCanaryInternals {
method BlockCanaryInternals (line 40) | public BlockCanaryInternals() {
method getInstance (line 82) | static BlockCanaryInternals getInstance() {
method setContext (line 98) | public static void setContext(BlockCanaryContext context) {
method getContext (line 102) | public static BlockCanaryContext getContext() {
method addBlockInterceptor (line 106) | void addBlockInterceptor(BlockInterceptor blockInterceptor) {
method setMonitor (line 110) | private void setMonitor(LooperMonitor looperPrinter) {
method getSampleDelay (line 114) | long getSampleDelay() {
method getPath (line 118) | static String getPath() {
method detectedBlockDirectory (line 130) | static File detectedBlockDirectory() {
method getLogFiles (line 138) | public static File[] getLogFiles() {
class BlockLogFileFilter (line 146) | private static class BlockLogFileFilter implements FilenameFilter {
method BlockLogFileFilter (line 150) | BlockLogFileFilter() {
method accept (line 154) | @Override
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/BlockInterceptor.java
type BlockInterceptor (line 22) | interface BlockInterceptor {
method onBlock (line 23) | void onBlock(Context context, BlockInfo blockInfo);
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/CpuSampler.java
class CpuSampler (line 32) | class CpuSampler extends AbstractSampler {
method CpuSampler (line 52) | public CpuSampler(long sampleInterval) {
method start (line 57) | @Override
method getCpuRateInfo (line 68) | public String getCpuRateInfo() {
method isCpuBusy (line 82) | public boolean isCpuBusy(long start, long end) {
method doSample (line 102) | @Override
method reset (line 142) | private void reset() {
method parse (line 151) | private void parse(String cpuRate, String pidCpuRate) {
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/HandlerThreadFactory.java
class HandlerThreadFactory (line 21) | final class HandlerThreadFactory {
method HandlerThreadFactory (line 26) | private HandlerThreadFactory() {
method getTimerThreadHandler (line 30) | public static Handler getTimerThreadHandler() {
method getWriteLogThreadHandler (line 34) | public static Handler getWriteLogThreadHandler() {
class HandlerThreadWrapper (line 38) | private static class HandlerThreadWrapper {
method HandlerThreadWrapper (line 41) | public HandlerThreadWrapper(String threadName) {
method getHandler (line 47) | public Handler getHandler() {
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/LogWriter.java
class LogWriter (line 32) | public class LogWriter {
method LogWriter (line 43) | private LogWriter() {
method save (line 53) | public static String save(String str) {
method cleanObsolete (line 64) | public static void cleanObsolete() {
method deleteAll (line 83) | public static void deleteAll() {
method save (line 98) | private static String save(String logFileName, String str) {
method generateTempZip (line 140) | public static File generateTempZip(String filename) {
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/LooperMonitor.java
class LooperMonitor (line 22) | class LooperMonitor implements Printer {
type BlockListener (line 33) | public interface BlockListener {
method onBlockEvent (line 34) | void onBlockEvent(long realStartTime,
method LooperMonitor (line 40) | public LooperMonitor(BlockListener blockListener, long blockThresholdM...
method println (line 49) | @Override
method isBlock (line 69) | private boolean isBlock(long endTime) {
method notifyBlockEvent (line 73) | private void notifyBlockEvent(final long endTime) {
method startDump (line 85) | private void startDump() {
method stopDump (line 95) | private void stopDump() {
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/StackSampler.java
class StackSampler (line 26) | class StackSampler extends AbstractSampler {
method StackSampler (line 34) | public StackSampler(Thread thread, long sampleIntervalMillis) {
method StackSampler (line 38) | public StackSampler(Thread thread, int maxEntryCount, long sampleInter...
method getThreadStackEntries (line 44) | public ArrayList<String> getThreadStackEntries(long startTime, long en...
method doSample (line 59) | @Override
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/internal/BlockInfo.java
class BlockInfo (line 34) | public class BlockInfo {
method BlockInfo (line 121) | public BlockInfo() {
method newInstance (line 124) | public static BlockInfo newInstance() {
method setCpuBusyFlag (line 151) | public BlockInfo setCpuBusyFlag(boolean busy) {
method setRecentCpuRate (line 156) | public BlockInfo setRecentCpuRate(String info) {
method setThreadStackEntries (line 161) | public BlockInfo setThreadStackEntries(ArrayList<String> threadStackEn...
method setMainThreadTimeCost (line 166) | public BlockInfo setMainThreadTimeCost(long realTimeStart, long realTi...
method flushString (line 174) | public BlockInfo flushString() {
method getBasicString (line 208) | public String getBasicString() {
method getCpuString (line 212) | public String getCpuString() {
method getTimeString (line 216) | public String getTimeString() {
method toString (line 220) | public String toString() {
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/internal/PerformanceUtils.java
class PerformanceUtils (line 31) | class PerformanceUtils {
method PerformanceUtils (line 37) | private PerformanceUtils() {
method getNumCores (line 46) | public static int getNumCores() {
method getFreeMemory (line 70) | public static long getFreeMemory() {
method getTotalMemory (line 77) | public static long getTotalMemory() {
FILE: blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/internal/ProcessUtils.java
class ProcessUtils (line 25) | public class ProcessUtils {
method ProcessUtils (line 30) | private ProcessUtils() {
method myProcessName (line 34) | public static String myProcessName() {
method obtainProcessName (line 47) | private static String obtainProcessName(Context context) {
FILE: blockcanary-android-no-op/src/main/java/com/github/moduth/blockcanary/BlockCanary.java
class BlockCanary (line 24) | public final class BlockCanary {
method BlockCanary (line 29) | private BlockCanary() {
method install (line 32) | public static BlockCanary install(Context context, BlockCanaryContext ...
method get (line 37) | public static BlockCanary get() {
method start (line 48) | public void start() {
method stop (line 52) | public void stop() {
method upload (line 56) | public void upload() {
method recordStartTime (line 60) | public void recordStartTime() {
method isMonitorDurationEnd (line 64) | public boolean isMonitorDurationEnd() {
FILE: blockcanary-android-no-op/src/main/java/com/github/moduth/blockcanary/BlockCanaryContext.java
class BlockCanaryContext (line 28) | public class BlockCanaryContext {
method BlockCanaryContext (line 33) | public BlockCanaryContext() {
method init (line 36) | static void init(Context c, BlockCanaryContext g) {
method get (line 41) | public static BlockCanaryContext get() {
method provideContext (line 49) | public Context provideContext() {
method provideQualifier (line 53) | public String provideQualifier() {
method provideUid (line 57) | public String provideUid() {
method provideNetworkType (line 61) | public String provideNetworkType() {
method provideMonitorDuration (line 65) | public int provideMonitorDuration() {
method provideBlockThreshold (line 69) | public int provideBlockThreshold() {
method provideDumpInterval (line 73) | public int provideDumpInterval() {
method providePath (line 77) | public String providePath() {
method displayNotification (line 81) | public boolean displayNotification() {
method zip (line 85) | public boolean zip(File[] src, File dest) {
method upload (line 89) | public void upload(File zippedFile) {
method concernPackages (line 93) | public List<String> concernPackages() {
method filterNonConcernStack (line 97) | public boolean filterNonConcernStack() {
method provideWhiteList (line 101) | public List<String> provideWhiteList() {
method deleteFilesInWhiteList (line 105) | public boolean deleteFilesInWhiteList() {
method onBlock (line 109) | public void onBlock(Context context, BlockInfo blockInfo) {
method stopWhenDebugging (line 113) | public boolean stopWhenDebugging() {
FILE: blockcanary-android-no-op/src/main/java/com/github/moduth/blockcanary/internal/BlockInfo.java
class BlockInfo (line 3) | public class BlockInfo {
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/BlockCanary.java
class BlockCanary (line 33) | public final class BlockCanary {
method BlockCanary (line 41) | private BlockCanary() {
method install (line 59) | public static BlockCanary install(Context context, BlockCanaryContext ...
method get (line 70) | public static BlockCanary get() {
method start (line 84) | public void start() {
method stop (line 94) | public void stop() {
method upload (line 106) | public void upload() {
method recordStartTime (line 114) | public void recordStartTime() {
method isMonitorDurationEnd (line 126) | public boolean isMonitorDurationEnd() {
method setEnabledBlocking (line 137) | private static void setEnabledBlocking(Context appContext,
method executeOnFileIoThread (line 148) | private static void executeOnFileIoThread(Runnable runnable) {
method newSingleThreadExecutor (line 152) | private static Executor newSingleThreadExecutor(String threadName) {
method setEnabled (line 156) | private static void setEnabled(Context context,
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/DisplayService.java
class DisplayService (line 37) | final class DisplayService implements BlockInterceptor {
method onBlock (line 41) | @Override
method show (line 52) | @TargetApi(HONEYCOMB)
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/SingleThreadFactory.java
class SingleThreadFactory (line 23) | final class SingleThreadFactory implements ThreadFactory {
method SingleThreadFactory (line 27) | SingleThreadFactory(String threadName) {
method newThread (line 31) | @Override
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/Uploader.java
class Uploader (line 26) | final class Uploader {
method Uploader (line 32) | private Uploader() {
method zip (line 36) | private static File zip() {
method zipAndUpload (line 49) | public static void zipAndUpload() {
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockCanaryUi.java
class BlockCanaryUi (line 23) | final class BlockCanaryUi {
method BlockCanaryUi (line 30) | private BlockCanaryUi() {
method dpToPixel (line 47) | static float dpToPixel(float dp, Resources resources) {
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockCanaryUtils.java
class BlockCanaryUtils (line 12) | final class BlockCanaryUtils {
method concernStackString (line 31) | public static String concernStackString(BlockInfo blockInfo) {
method isBlockInfoValid (line 48) | public static boolean isBlockInfoValid(BlockInfo blockInfo) {
method isInWhiteList (line 54) | public static boolean isInWhiteList(BlockInfo info) {
method getConcernPackages (line 70) | public static List<String> getConcernPackages() {
method concernStackString (line 74) | private static String concernStackString(String line) {
method classSimpleName (line 83) | private static String classSimpleName(String stackLine) {
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockInfoCorruptException.java
class BlockInfoCorruptException (line 9) | public class BlockInfoCorruptException extends Exception {
method BlockInfoCorruptException (line 11) | public BlockInfoCorruptException(BlockInfoEx blockInfo) {
method BlockInfoCorruptException (line 16) | public BlockInfoCorruptException(String detailMessage) {
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockInfoEx.java
class BlockInfoEx (line 12) | final class BlockInfoEx extends BlockInfo {
method newInstance (line 25) | public static BlockInfoEx newInstance(File file) {
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/DetailAdapter.java
class DetailAdapter (line 31) | final class DetailAdapter extends BaseAdapter {
method getView (line 45) | @Override
method connectorViewType (line 80) | private DisplayConnectorView.Type connectorViewType(int position) {
method elementToHtmlString (line 86) | private String elementToHtmlString(String element, int position, boole...
method update (line 129) | public void update(BlockInfo blockInfo) {
method toggleRow (line 140) | public void toggleRow(int position) {
method getCount (line 145) | @Override
method getItem (line 153) | @Override
method getViewTypeCount (line 171) | @Override
method getItemViewType (line 176) | @Override
method getItemId (line 184) | @Override
method findById (line 189) | @SuppressWarnings("unchecked")
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/DisplayActivity.java
class DisplayActivity (line 67) | public class DisplayActivity extends Activity {
method createPendingIntent (line 82) | public static PendingIntent createPendingIntent(Context context, Strin...
method onCreate (line 89) | @Override
method onRetainNonConfigurationInstance (line 114) | @Override
method onSaveInstanceState (line 119) | @Override
method onResume (line 125) | @Override
method setTheme (line 131) | @Override
method onDestroy (line 142) | @Override
method onCreateOptionsMenu (line 148) | @Override
method onOptionsItemSelected (line 173) | @Override
method onBackPressed (line 182) | @Override
method shareBlock (line 192) | private void shareBlock(BlockInfoEx blockInfo) {
method shareHeapDump (line 200) | private void shareHeapDump(BlockInfoEx blockInfo) {
method updateUi (line 212) | private void updateUi() {
method renderBlockList (line 229) | private void renderBlockList() {
method renderBlockDetail (line 276) | private void renderBlockDetail(final BlockInfoEx blockInfo) {
method getBlock (line 315) | private BlockInfoEx getBlock(String startTime) {
class BlockListAdapter (line 327) | class BlockListAdapter extends BaseAdapter {
method getCount (line 329) | @Override
method getItem (line 334) | @Override
method getItemId (line 339) | @Override
method getView (line 344) | @Override
class LoadBlocks (line 372) | static class LoadBlocks implements Runnable {
method LoadBlocks (line 379) | LoadBlocks(DisplayActivity activity) {
method load (line 384) | static void load(DisplayActivity activity) {
method forgetActivity (line 390) | static void forgetActivity() {
method run (line 397) | @Override
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/DisplayConnectorView.java
class DisplayConnectorView (line 28) | public final class DisplayConnectorView extends View {
type Type (line 43) | public enum Type {
method DisplayConnectorView (line 50) | public DisplayConnectorView(Context context, AttributeSet attrs) {
method onDraw (line 56) | @SuppressWarnings("SuspiciousNameCombination")
method setType (line 106) | public void setType(Type type) {
FILE: blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/MoreDetailsView.java
class MoreDetailsView (line 24) | public final class MoreDetailsView extends View {
method MoreDetailsView (line 29) | public MoreDetailsView(Context context, AttributeSet attrs) {
method onDraw (line 36) | @Override protected void onDraw(Canvas canvas) {
method setFolding (line 48) | public void setFolding(boolean folding) {
FILE: blockcanary-sample/src/main/java/com/example/blockcanary/AppContext.java
class AppContext (line 26) | public class AppContext extends BlockCanaryContext {
method provideQualifier (line 29) | @Override
method provideUid (line 42) | @Override
method provideNetworkType (line 47) | @Override
method provideMonitorDuration (line 52) | @Override
method provideBlockThreshold (line 57) | @Override
method displayNotification (line 62) | @Override
method concernPackages (line 67) | @Override
method provideWhiteList (line 74) | @Override
method stopWhenDebugging (line 81) | @Override
FILE: blockcanary-sample/src/main/java/com/example/blockcanary/DemoActivity.java
class DemoActivity (line 26) | public class DemoActivity extends AppCompatActivity {
method onCreate (line 28) | @Override
method showTipDialog (line 46) | private void showTipDialog() {
method onCreateOptionsMenu (line 54) | @Override
method onOptionsItemSelected (line 61) | @Override
FILE: blockcanary-sample/src/main/java/com/example/blockcanary/DemoApplication.java
class DemoApplication (line 23) | public class DemoApplication extends Application {
method onCreate (line 27) | @Override
method getAppContext (line 34) | public static Context getAppContext() {
FILE: blockcanary-sample/src/main/java/com/example/blockcanary/DemoFragment.java
class DemoFragment (line 30) | public class DemoFragment extends Fragment implements View.OnClickListen...
method newInstance (line 34) | public static DemoFragment newInstance() {
method onCreate (line 38) | @Override
method onCreateView (line 43) | @Nullable
method onViewCreated (line 49) | @Override
method onDestroyView (line 61) | @Override
method onActivityCreated (line 66) | @Override
method onClick (line 71) | @Override
method compute (line 96) | private static double compute() {
method readFile (line 105) | private static void readFile() {
Condensed preview — 86 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (201K chars).
[
{
"path": ".gitignore",
"chars": 432,
"preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbi"
},
{
"path": "CHANGELOG.md",
"chars": 783,
"preview": "# Change Log\n\nYou can watch releases [on Maven](https://oss.sonatype.org/content/groups/public/com/github/markzhai/).\n\n#"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 7729,
"preview": "[Chinese README](https://github.com/markzhai/AndroidPerformanceMonitor/blob/master/README_CN.md)\n\n# Android Performance "
},
{
"path": "README_CN.md",
"chars": 7073,
"preview": "[English](https://github.com/markzhai/AndroidPerformanceMonitor/blob/master/README.md)\n\n# Android Performance Monitor [!"
},
{
"path": "blockcanary-analyzer/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "blockcanary-analyzer/build.gradle",
"chars": 609,
"preview": "apply plugin: 'com.android.library'\napply from: 'gradle-mvn-push.gradle'\n\nandroid {\n compileSdkVersion LIBRARY_COMPIL"
},
{
"path": "blockcanary-analyzer/gradle-mvn-push.gradle",
"chars": 3669,
"preview": "/*\n * Copyright 2013 Chris Banes\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
},
{
"path": "blockcanary-analyzer/gradle.properties",
"chars": 752,
"preview": "POM_NAME=Android BlockCanary Analyzer Library\nPOM_ARTIFACT_ID=blockcanary-analyzer\nPOM_PACKAGING=aar\nVERSION_NAME=1.5.0\n"
},
{
"path": "blockcanary-analyzer/proguard-rules.pro",
"chars": 670,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "blockcanary-analyzer/src/main/AndroidManifest.xml",
"chars": 375,
"preview": "<manifest\n package=\"com.github.moduth.blockcanary.core\"\n xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/AbstractSampler.java",
"chars": 2103,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/BlockCanaryContext.java",
"chars": 5503,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/BlockCanaryInternals.java",
"chars": 5278,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/BlockInterceptor.java",
"chars": 837,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/CpuSampler.java",
"chars": 7125,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/HandlerThreadFactory.java",
"chars": 1691,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/LogWriter.java",
"chars": 4638,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/LooperMonitor.java",
"chars": 3680,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/StackSampler.java",
"chars": 2676,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/internal/BlockInfo.java",
"chars": 8801,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/internal/PerformanceUtils.java",
"chars": 3739,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/java/com/github/moduth/blockcanary/internal/ProcessUtils.java",
"chars": 2085,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-analyzer/src/main/res/values/int.xml",
"chars": 127,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <integer name=\"block_canary_max_stored_count\">500</integer>\n</res"
},
{
"path": "blockcanary-android/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "blockcanary-android/build.gradle",
"chars": 717,
"preview": "apply plugin: 'com.android.library'\napply from: 'gradle-mvn-push.gradle'\n\nandroid {\n compileSdkVersion LIBRARY_COMPIL"
},
{
"path": "blockcanary-android/gradle-mvn-push.gradle",
"chars": 3669,
"preview": "/*\n * Copyright 2013 Chris Banes\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
},
{
"path": "blockcanary-android/gradle.properties",
"chars": 733,
"preview": "POM_NAME=Android BlockCanary Library\nPOM_ARTIFACT_ID=blockcanary-android\nPOM_PACKAGING=aar\nVERSION_NAME=1.5.0\nVERSION_CO"
},
{
"path": "blockcanary-android/proguard-rules.pro",
"chars": 712,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "blockcanary-android/src/main/AndroidManifest.xml",
"chars": 749,
"preview": "<manifest\n package=\"com.github.moduth.blockcanary\"\n xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n "
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/BlockCanary.java",
"chars": 5882,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/DisplayService.java",
"chars": 4000,
"preview": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may n"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/SingleThreadFactory.java",
"chars": 1085,
"preview": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may n"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/Uploader.java",
"chars": 1949,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockCanaryUi.java",
"chars": 1811,
"preview": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may n"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockCanaryUtils.java",
"chars": 3031,
"preview": "package com.github.moduth.blockcanary.ui;\n\nimport android.text.TextUtils;\n\nimport com.github.moduth.blockcanary.BlockCan"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockInfoCorruptException.java",
"chars": 469,
"preview": "package com.github.moduth.blockcanary.ui;\n\nimport java.util.Locale;\n\n/**\n * @author markzhai on 16/8/24\n * @version 1.3."
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/BlockInfoEx.java",
"chars": 5223,
"preview": "package com.github.moduth.blockcanary.ui;\n\nimport android.util.Log;\n\nimport com.github.moduth.blockcanary.internal.Block"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/DetailAdapter.java",
"chars": 7113,
"preview": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may n"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/DisplayActivity.java",
"chars": 17308,
"preview": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may n"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/DisplayConnectorView.java",
"chars": 4262,
"preview": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may n"
},
{
"path": "blockcanary-android/src/main/java/com/github/moduth/blockcanary/ui/MoreDetailsView.java",
"chars": 1744,
"preview": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may n"
},
{
"path": "blockcanary-android/src/main/res/layout/block_canary_block_row.xml",
"chars": 841,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "blockcanary-android/src/main/res/layout/block_canary_display_leak.xml",
"chars": 983,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "blockcanary-android/src/main/res/layout/block_canary_ref_row.xml",
"chars": 1099,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "blockcanary-android/src/main/res/layout/block_canary_ref_top_row.xml",
"chars": 393,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n android:id=\"@+id/__leak_canary_row_text\"\n xmlns:android=\"http://"
},
{
"path": "blockcanary-android/src/main/res/values/strings.xml",
"chars": 844,
"preview": "<resources>\n <string name=\"block_canary_display_activity_label\">Blocks</string>\n <string name=\"block_canary_share_"
},
{
"path": "blockcanary-android/src/main/res/values/themes.xml",
"chars": 148,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"block_canary_BlockCanary.Base\" parent=\"android:Theme"
},
{
"path": "blockcanary-android/src/main/res/values-v14/themes.xml",
"chars": 153,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"block_canary_BlockCanary.Base\" parent=\"android:Theme"
},
{
"path": "blockcanary-android/src/main/res/values-v21/themes.xml",
"chars": 157,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"block_canary_BlockCanary.Base\" parent=\"android:Theme"
},
{
"path": "blockcanary-android-no-op/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "blockcanary-android-no-op/build.gradle",
"chars": 609,
"preview": "apply plugin: 'com.android.library'\napply from: 'gradle-mvn-push.gradle'\n\nandroid {\n compileSdkVersion LIBRARY_COMPIL"
},
{
"path": "blockcanary-android-no-op/gradle-mvn-push.gradle",
"chars": 3669,
"preview": "/*\n * Copyright 2013 Chris Banes\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
},
{
"path": "blockcanary-android-no-op/gradle.properties",
"chars": 743,
"preview": "POM_NAME=Android BlockCanary NO-OP Library\nPOM_ARTIFACT_ID=blockcanary-no-op\nPOM_PACKAGING=jar\nVERSION_NAME=1.5.0\nVERSIO"
},
{
"path": "blockcanary-android-no-op/proguard-rules.pro",
"chars": 670,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "blockcanary-android-no-op/src/main/AndroidManifest.xml",
"chars": 88,
"preview": "<manifest\n package=\"com.github.moduth.blockcanary\">\n\n <application/>\n\n</manifest>\n"
},
{
"path": "blockcanary-android-no-op/src/main/java/com/github/moduth/blockcanary/BlockCanary.java",
"chars": 1741,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-android-no-op/src/main/java/com/github/moduth/blockcanary/BlockCanaryContext.java",
"chars": 2608,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-android-no-op/src/main/java/com/github/moduth/blockcanary/internal/BlockInfo.java",
"chars": 76,
"preview": "package com.github.moduth.blockcanary.internal;\n\npublic class BlockInfo {\n}\n"
},
{
"path": "blockcanary-sample/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "blockcanary-sample/build.gradle",
"chars": 833,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion LIBRARY_COMPILE_SDK_VERSION\n buildToolsVersi"
},
{
"path": "blockcanary-sample/proguard-rules.pro",
"chars": 670,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "blockcanary-sample/src/main/AndroidManifest.xml",
"chars": 821,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.example.blockcanary\"\n xmlns:android=\"http://schem"
},
{
"path": "blockcanary-sample/src/main/java/com/example/blockcanary/AppContext.java",
"chars": 2316,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-sample/src/main/java/com/example/blockcanary/DemoActivity.java",
"chars": 2578,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-sample/src/main/java/com/example/blockcanary/DemoApplication.java",
"chars": 1099,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-sample/src/main/java/com/example/blockcanary/DemoFragment.java",
"chars": 3811,
"preview": "/*\n * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "blockcanary-sample/src/main/res/drawable/background_splash.xml",
"chars": 311,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <ite"
},
{
"path": "blockcanary-sample/src/main/res/drawable/btn_nor.xml",
"chars": 337,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n <solid a"
},
{
"path": "blockcanary-sample/src/main/res/drawable/btn_pre.xml",
"chars": 342,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n <solid a"
},
{
"path": "blockcanary-sample/src/main/res/drawable/btn_select.xml",
"chars": 346,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n <item"
},
{
"path": "blockcanary-sample/src/main/res/layout/activity_demo.xml",
"chars": 1178,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "blockcanary-sample/src/main/res/layout/activity_main.xml",
"chars": 3832,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "blockcanary-sample/src/main/res/menu/menu_demo.xml",
"chars": 280,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/tools\" tool"
},
{
"path": "blockcanary-sample/src/main/res/values/colors.xml",
"chars": 219,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"window_background\">#FFF5F5F5</color>\n\n <color nam"
},
{
"path": "blockcanary-sample/src/main/res/values/dimens.xml",
"chars": 398,
"preview": "<resources>\n <!-- Default screen margins, per the Android Design guidelines. -->\n <dimen name=\"activity_horizontal"
},
{
"path": "blockcanary-sample/src/main/res/values/strings.xml",
"chars": 541,
"preview": "<resources>\n <string name=\"app_name\">BlockCanary</string>\n\n <string name=\"hello_world\">After click button, main-th"
},
{
"path": "blockcanary-sample/src/main/res/values/styles.xml",
"chars": 588,
"preview": "<resources>\n\n <style name=\"CjjBaseTheme\" parent=\"Base.Theme.DesignDemo\">\n </style>\n\n <style name=\"Base.Theme.De"
},
{
"path": "blockcanary-sample/src/main/res/values-zh/strings.xml",
"chars": 422,
"preview": "<resources>\n <string name=\"app_name\">BlockCanary</string>\n\n <string name=\"hello_world\">点击按钮后,会触发主线程耗时时间,从而被BlockCa"
},
{
"path": "build.gradle",
"chars": 528,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n r"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 233,
"preview": "#Wed Apr 13 16:42:11 CST 2016\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle.properties",
"chars": 1004,
"preview": "## Project-wide Gradle settings.\n#\n# For more details on how to configure your build environment visit\n# http://www.grad"
},
{
"path": "gradlew",
"chars": 4971,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2314,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
},
{
"path": "settings.gradle",
"chars": 129,
"preview": "include ':blockcanary-android-no-op'\ninclude ':blockcanary-analyzer'\ninclude ':blockcanary-android'\ninclude ':blockcanar"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the markzhai/AndroidPerformanceMonitor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 86 files (182.8 KB), approximately 44.4k tokens, and a symbol index with 245 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.