├── .gitignore
├── .idea
├── .gitignore
├── .name
├── compiler.xml
├── gradle.xml
├── jarRepositories.xml
├── kotlinc.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── LICENSE
├── README.md
├── anr-library
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
├── publish_anr.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── me
│ └── bytebeats
│ └── anr
│ ├── AnrError.kt
│ ├── AnrInterceptor.kt
│ ├── AnrListener.kt
│ ├── AnrLog.kt
│ ├── AnrMonitor.kt
│ ├── AnrMonitor2.kt
│ ├── OnInterruptedListener.kt
│ ├── StackTraceCollector.kt
│ └── ThreadComparator.kt
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── me
│ │ └── bytebeats
│ │ └── anrmonitor
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── me
│ │ │ └── bytebeats
│ │ │ └── anrmonitor
│ │ │ ├── APMApplication.kt
│ │ │ ├── DeadLockUtils.kt
│ │ │ └── MainActivity.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── me
│ └── bytebeats
│ └── anrmonitor
│ └── ExampleUnitTest.kt
├── build.gradle
├── deadlock-library
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
├── publish_deadlock.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── cpp
│ ├── CMakeLists.txt
│ ├── deadlock_lib.cpp
│ ├── dl_open.c
│ └── dl_open.h
│ └── java
│ └── me
│ └── bytebeats
│ └── deadlock
│ ├── DeadLockError.java
│ ├── DeadLockListener.java
│ ├── DeadLockMonitor.java
│ ├── DeadLockStackTraces.java
│ └── ReflectUtil.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lags-library
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
├── publish_lags.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── me
│ └── bytebeats
│ └── lag
│ ├── AnrProsecutor.kt
│ ├── FrameJankDetector.kt
│ ├── LagLog.kt
│ ├── LagMonitor.kt
│ ├── LagMonitorLifecycle.kt
│ ├── OnFrameJankListener.kt
│ ├── OnProcessNotRespondingListener.kt
│ ├── OnUIThreadBlockListener.kt
│ └── UILooperPrinter.kt
├── local_setting_reader.gradle
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Anr-Monitor
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
23 |
24 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Chen Pan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Anr-Monitor
2 | Android Performance Tools. ANR, Dead locks and lags.
3 |
4 |
Android性能监控工具. 包括了对 ANR 信息的收集, 死锁时线程信息的收集以及卡顿的监测.
5 |
6 | ## ANR信息收集原理
7 |
通过在后台开启一条线程, 只要没有被打断, 就一直在循环监测.
8 |
每一次循环开始的时候, 向`Handler(Looper.getMainLooper())`发送一条消息, 用于取消收集 ANR 的开始的标签.
9 |
然后子线程休息指定时间(可以自由设置).
10 |
如果此时主线程卡顿了指定时间, 那么子线程从休眠中唤醒, 此时收集 ANR 的标签没有取消, 则开始收集线程(全部线程或者主线程或者特定类型的线程)的堆栈信息, 通过回调接收.
11 |
12 | ## 卡顿的监测原理
13 |
通过 `Choreographer#postFrameCallback` 或者 `Looper.gerMainLooper().setPrinter` 方式,
14 |
传入自己的 `FrameCallback` 或者 `Printer.
15 |
通过对`doFrame(frameTimeNanos)/println(message)` 计算每一次 `doFrame/println` 的时间间隔,
16 |
计算丢帧或者 `Looper` 中每一个 `Message` 执行的耗时
17 |
从而判断应用的卡顿情况.
18 |
19 | ## 死锁时线程信息的收集原理
20 |
主要是在发生死锁时, 通过 Android 系统底层函数 `Monitor#GetLockOwnerThreadId` 或者锁此时的持有者线程(通过反射该线程的 `nativePeer` 从当前线程查找)
21 |
通过 `Thread#enumerate(Thread[])` 获取全部线程, 并利用算法查找此时竞争同一把锁的线程竞争闭环, 从而找出死锁的信息.
22 |
因为需要 Hook 底层 `Monitor#GetLockOwnerThreadId` 函数, 所以使用了 `JNI` 和 `NDK` 技术.
23 |
24 | ## How to use?
25 |
使用指定地址的 `MavenCentral`:
26 |
在根`build.gradle`文件中, 添加:
27 | ```
28 | repositories {
29 | maven { url('https://repo1.maven.org/maven2/') }
30 | ...
31 | }
32 | ```
33 |
在 module `build.gradle` 文件中, 添加:
34 | ```
35 | //load from maven central
36 | implementation('io.github.bytebeats:anr:1.0.0')
37 | implementation('io.github.bytebeats:lags:1.0.1')
38 | implementation('io.github.bytebeats:deadlock:1.0.0')
39 | ```
40 |
在自定义的 `Application` 中:
41 | ```
42 | class APMApplication : Application() {
43 | val anrMonitor = AnrMonitor(3000)
44 | val anrMonitor = AnrMonitor2(3000)
45 |
46 | val silentAnrListener = object : AnrListener {
47 | override fun onAppNotResponding(error: AnrError) {
48 | Log.d("anr-log", "onAppNotResponding", error)
49 | }
50 | }
51 |
52 | var duration = 4L
53 |
54 | override fun onCreate() {
55 | super.onCreate()
56 | AnrLog.logStackTrace = false
57 | anrMonitor.setIgnoreDebugger(true)
58 | .setReportAllThreads()
59 | .setAnrListener(object : AnrListener {
60 | override fun onAppNotResponding(error: AnrError) {
61 | AnrLog.logd("onAppNotResponding")
62 | AnrLog.logd(error)
63 | try {
64 | ObjectOutputStream(ByteArrayOutputStream()).writeObject(error)
65 | } catch (e: IOException) {
66 | throw RuntimeException(e)
67 | }
68 | AnrLog.logd("Anr Error was successfully serialized")
69 | throw error
70 | }
71 | }).setAnrInterceptor(object : AnrInterceptor {
72 | override fun intercept(duration: Long): Long {
73 | val ret = this@APMApplication.duration - duration
74 | if (ret > 0) {
75 | AnrLog.logd(
76 | "Intercepted ANR that is too short ($duration ms), postponing for $ret ms."
77 | )
78 | }
79 | return ret
80 | }
81 | })
82 | .setOnInterruptedListener(object : OnInterruptedListener {
83 | override fun onInterrupted(e: InterruptedException) {
84 | throw e
85 | }
86 | })
87 | ProcessLifecycleOwner.get().lifecycle.addObserver(anrMonitor)
88 |
89 | val lagMonitor = LagMonitor.Builder(this.applicationContext)
90 | .setThresholdTimeMillis(3000L)
91 | .setLagLogEnabled(true)
92 | .setMonitorMode(LagMonitor.MonitorMode.UI)
93 | .setOnFrameJankListener(object : OnFrameJankListener {
94 | override fun onJank(janks: Int) {
95 | Log.d("lag-log", "janks: $janks")
96 | }
97 | })
98 | .setOnUIThreadRunListener(object : OnUIThreadBlockListener {
99 | override fun onBlock(lagTime: Long, uiRunTime: Long) {
100 | Log.d("lag-log", "lagTime: $lagTime, uiRunTime: $uiRunTime")
101 | }
102 | })
103 | .setOnProcessNotRespondingListener(object : OnProcessNotRespondingListener {
104 | override fun onNotResponding(processInfo: String?) {
105 | Log.d("lag-log", "processInfo: $processInfo")
106 | }
107 | })
108 | .build()
109 | ProcessLifecycleOwner.get().lifecycle.addObserver(lagMonitor)
110 | }
111 |
112 | override fun onTerminate() {
113 | super.onTerminate()
114 | anrMonitor.onAppTerminate()
115 | }
116 | }
117 | ```
118 |
119 | ## Stargazers over time
120 |
121 | [](https://starchart.cc/bytebeats/Anr-Monitor)
122 |
123 | ## MIT License
124 |
125 | Copyright (c) 2021 Chen Pan
126 |
127 | Permission is hereby granted, free of charge, to any person obtaining a copy
128 | of this software and associated documentation files (the "Software"), to deal
129 | in the Software without restriction, including without limitation the rights
130 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
131 | copies of the Software, and to permit persons to whom the Software is
132 | furnished to do so, subject to the following conditions:
133 |
134 | The above copyright notice and this permission notice shall be included in all
135 | copies or substantial portions of the Software.
136 |
137 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
138 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
139 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
140 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
141 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
142 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
143 | SOFTWARE.
144 |
145 |
--------------------------------------------------------------------------------
/anr-library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/anr-library/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 |
6 | ext {
7 | ANR_ARTIFACT_ID = 'anr'
8 | ANR_VERSION = '1.0.0'
9 | ANR_POM_DESCRIPTION = 'Android ANR Manager'
10 | }
11 |
12 | apply from: 'publish_anr.gradle'
13 |
14 | android {
15 | compileSdkVersion 30
16 | buildToolsVersion "30.0.3"
17 |
18 | defaultConfig {
19 | minSdkVersion 19
20 | targetSdkVersion 30
21 | versionCode 1
22 | versionName "1.0"
23 |
24 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
25 | consumerProguardFiles "consumer-rules.pro"
26 | }
27 |
28 | buildTypes {
29 | release {
30 | minifyEnabled false
31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
32 | }
33 | }
34 | compileOptions {
35 | sourceCompatibility JavaVersion.VERSION_1_8
36 | targetCompatibility JavaVersion.VERSION_1_8
37 | }
38 | }
39 |
40 | dependencies {
41 |
42 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
43 | implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
44 | }
--------------------------------------------------------------------------------
/anr-library/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/anr-library/consumer-rules.pro
--------------------------------------------------------------------------------
/anr-library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/anr-library/publish_anr.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | //only build and setting gradle can use `plugins`
5 | //plugins {
6 | // id('signing')
7 | // id('maven-publish')
8 | //}
9 |
10 | task androidSourcesJar(type: Jar) {
11 | from android.sourceSets.main.java.source
12 | classifier('sources')
13 | }
14 |
15 | apply from: '../local_setting_reader.gradle'
16 |
17 | project.publishing {
18 | publications {
19 | release(MavenPublication) {
20 | // The coordinates of the library, being set from variables that
21 | // we'll set up in a moment
22 | groupId GROUP_ID
23 | artifactId ANR_ARTIFACT_ID
24 | version ANR_VERSION
25 |
26 | // Two artifacts, the `aar` and the sources
27 | artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
28 | artifact androidSourcesJar
29 |
30 | // Self-explanatory metadata for the most part
31 | pom {
32 | name = ANR_ARTIFACT_ID
33 | description = ANR_POM_DESCRIPTION
34 | // If your project has a dedicated site, use its URL here
35 | url = POM_URL
36 | licenses {
37 | license {
38 | //协议类型,一般默认Apache License2.0的话不用改:
39 | name = LICENSE_NAME
40 | url = LICENSE_URL
41 | }
42 | }
43 | developers {
44 | developer {
45 | id = DEVELOPER_ID
46 | name = DEVELOPER_NAME
47 | email = DEVELOPER_EMAIL
48 | }
49 | }
50 | // Version control info, if you're using GitHub, follow the format as seen here
51 | scm {
52 | //修改成你的Git地址:
53 | connection = SCM_CONNECTION
54 | developerConnection = SCM_DEVELOPER_CONNECTION
55 | //分支地址:
56 | url = SCM_URL
57 | }
58 | // A slightly hacky fix so that your POM will include any transitive dependencies
59 | // that your library builds upon
60 | withXml {
61 | def dependenciesNode = asNode().appendNode('dependencies')
62 |
63 | project.configurations.implementation.allDependencies.each {
64 | def dependencyNode = dependenciesNode.appendNode('dependency')
65 | dependencyNode.appendNode('groupId', it.group)
66 | dependencyNode.appendNode('artifactId', it.name)
67 | dependencyNode.appendNode('version', it.version)
68 | }
69 | }
70 | }
71 | }
72 | }
73 | repositories {
74 | // The repository to publish to, Sonatype/MavenCentral
75 | maven {
76 | // This is an arbitrary name, you may also use "mavencentral" or
77 | // any other name that's descriptive for you
78 | name = "anr"
79 |
80 | // You only need this if you want to publish snapshots, otherwise just set the URL
81 | // to the release repo directly
82 | url = version.endsWith('SNAPSHOT') ? SNAPSHOTS_REPO_URL : RELEASES_REPO_URL
83 |
84 | // The username and password we've fetched earlier
85 | credentials {
86 | username ossrhUsername
87 | password ossrhPassword
88 | }
89 | }
90 | }
91 | }
92 | signing {
93 | sign publishing.publications
94 | }
--------------------------------------------------------------------------------
/anr-library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/anr-library/src/main/java/me/bytebeats/anr/AnrError.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anr
2 |
3 | import android.os.Looper
4 |
5 | /**
6 | * Created by bytebeats on 2021/6/30 : 11:42
7 | * E-mail: happychinapc@gmail.com
8 | * Quote: Peasant. Educated. Worker
9 | */
10 |
11 | class AnrError(
12 | private val stackTraceThrowable: StackTraceCollector.StackTraceThrowable,
13 | private val duration: Long
14 | ) : Error("Application Not Responding for at least $duration ms.", stackTraceThrowable) {
15 |
16 | override fun fillInStackTrace(): Throwable {
17 | stackTrace = emptyArray()
18 | return this
19 | }
20 |
21 | companion object {
22 | private fun threadTitle(thread: Thread): String {
23 | return "${thread.name} (state = ${thread.state})"
24 | }
25 |
26 | fun newMainInstance(duration: Long): AnrError {
27 | val mainThread = Looper.getMainLooper().thread
28 | val stackTraces = mainThread.stackTrace
29 | return AnrError(
30 | StackTraceCollector(
31 | threadTitle(mainThread),
32 | stackTraces
33 | ).StackTraceThrowable(null), duration
34 | )
35 | }
36 |
37 | fun newInstance(
38 | duration: Long,
39 | prefix: String,
40 | logThreadsWithoutStackTrace: Boolean
41 | ): AnrError {
42 | val mainThread = Looper.getMainLooper().thread
43 | val threadStackTraces =
44 | sortedMapOf>(ThreadComparator())
45 | for (entry in Thread.getAllStackTraces().entries) {
46 | if (entry.key == mainThread || entry.key.name.startsWith(prefix) && (logThreadsWithoutStackTrace || !entry.value.isNullOrEmpty())) {
47 | threadStackTraces[entry.key] = entry.value
48 | }
49 | }
50 | /**
51 | * Sometimes main thread is not returned by {@link Thread#getAllStackTraces()} -- ensure that we have it.
52 | */
53 | if (!threadStackTraces.containsKey(mainThread)) {
54 | threadStackTraces[mainThread] = mainThread.stackTrace
55 | }
56 | var throwable: StackTraceCollector.StackTraceThrowable? = null
57 | for (entry in threadStackTraces.entries) {
58 | throwable = StackTraceCollector(threadTitle(entry.key), entry.value).StackTraceThrowable(throwable)
59 | }
60 | return AnrError(throwable!!, duration)
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/anr-library/src/main/java/me/bytebeats/anr/AnrInterceptor.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anr
2 |
3 | /**
4 | * Created by bytebeats on 2021/6/30 : 11:29
5 | * E-mail: happychinapc@gmail.com
6 | * Quote: Peasant. Educated. Worker
7 | */
8 |
9 | /**
10 | * To report ANR immediately or lazily when ANRs is detected.
11 | */
12 | interface AnrInterceptor {
13 | /**
14 | *
15 | * Called when main thread has froze more time than defined by the timeout.
16 | * @param duration The minimum time (in ms) the main thread has been frozen (may be more).
17 | * @return 0 or negative if the ANR should be reported immediately. A positive number of ms to postpone the reporting.
18 | */
19 | fun intercept(duration: Long): Long
20 | }
--------------------------------------------------------------------------------
/anr-library/src/main/java/me/bytebeats/anr/AnrListener.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anr
2 |
3 | /**
4 | * Created by bytebeats on 2021/6/30 : 12:24
5 | * E-mail: happychinapc@gmail.com
6 | * Quote: Peasant. Educated. Worker
7 | */
8 |
9 | /**
10 | * To detect whether ANR happened
11 | */
12 | interface AnrListener {
13 | /**
14 | * Called when an ANR is detected.
15 | *
16 | * @param error The error describing the ANR.
17 | */
18 | fun onAppNotResponding(error: AnrError)
19 | }
--------------------------------------------------------------------------------
/anr-library/src/main/java/me/bytebeats/anr/AnrLog.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anr
2 |
3 | import android.util.Log
4 |
5 | /**
6 | * Created by bytebeats on 2021/6/30 : 14:27
7 | * E-mail: happychinapc@gmail.com
8 | * Quote: Peasant. Educated. Worker
9 | */
10 |
11 | object AnrLog {
12 | var logStackTrace = true
13 | private const val TAG = "anr-log"
14 |
15 | private enum class LEVEL {
16 | V, D, I, W, E;
17 | }
18 |
19 | fun logv(message: Any?) = logv(TAG, message)
20 | fun logd(message: Any?) = logd(TAG, message)
21 | fun logi(message: Any?) = logi(TAG, message)
22 | fun logw(message: Any?) = logw(TAG, message)
23 | fun loge(message: Any?) = loge(TAG, message)
24 |
25 | fun logv(tag: String, message: Any?) = log(LEVEL.V, tag, message.toString())
26 | fun logd(tag: String, message: Any?) = log(LEVEL.D, tag, message.toString())
27 | fun logi(tag: String, message: Any?) = log(LEVEL.I, tag, message.toString())
28 | fun logw(tag: String, message: Any?) = log(LEVEL.W, tag, message.toString())
29 | fun loge(tag: String, message: Any?) = log(LEVEL.E, tag, message.toString())
30 |
31 | private fun log(level: LEVEL, tag: String, message: String) {
32 | val tagBuilder = StringBuilder()
33 | tagBuilder.append(tag)
34 | if (logStackTrace) {
35 | val stackTrace = Thread.currentThread().stackTrace[5]
36 | tagBuilder.append(" ${stackTrace.methodName}(${stackTrace.fileName}:${stackTrace.lineNumber})")
37 | }
38 | when (level) {
39 | LEVEL.V -> Log.v(tagBuilder.toString(), message)
40 | LEVEL.D -> Log.d(tagBuilder.toString(), message)
41 | LEVEL.I -> Log.i(tagBuilder.toString(), message)
42 | LEVEL.W -> Log.w(tagBuilder.toString(), message)
43 | LEVEL.E -> Log.e(tagBuilder.toString(), message)
44 | }
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/anr-library/src/main/java/me/bytebeats/anr/AnrMonitor.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anr
2 |
3 | import android.os.Debug
4 | import android.os.Handler
5 | import android.os.Looper
6 | import androidx.lifecycle.Lifecycle
7 | import androidx.lifecycle.LifecycleEventObserver
8 | import androidx.lifecycle.LifecycleOwner
9 |
10 | /**
11 | * Created by bytebeats on 2021/6/30 : 12:26
12 | * E-mail: happychinapc@gmail.com
13 | * Quote: Peasant. Educated. Worker
14 | */
15 |
16 | /**
17 | * Constructs a watchdog that checks the ui thread every given interval
18 | *
19 | * @param timeoutInterval The interval, in milliseconds, between to checks of the UI thread.
20 | * It is therefore the maximum time the UI may freeze before being reported as ANR.
21 | */
22 | class AnrMonitor(private val timeoutInterval: Long = DEFAULT_ANR_TIMEOUT) : Thread(),
23 | LifecycleEventObserver {
24 |
25 | init {
26 | name = "||ANR-Monitor||"
27 | }
28 |
29 | private var mAnrInterceptor: AnrInterceptor? = DEFAULT_ANR_INTERCEPTOR
30 | private var mAnrListener: AnrListener? = DEFAULT_ANR_LISTENER
31 | private var mInterruptedListener: OnInterruptedListener? = DEFAULT_INTERRUPTION_LISTENER
32 |
33 | private val mainHandler = Handler(Looper.getMainLooper())
34 |
35 | private var mPrefix: String? = ""
36 | private var mLogThreadWithoutStackTrace = false
37 | private var mIgnoreDebugger = false
38 |
39 | @Volatile
40 | private var mTick = 0L
41 |
42 | @Volatile
43 | private var mReported = false
44 |
45 | @Volatile
46 | private var mStopped = false
47 |
48 | private val mTicker = Runnable {
49 | mTick = 0
50 | mReported = false
51 | }
52 |
53 | fun setAnrListener(anrListener: AnrListener?): AnrMonitor {
54 | mAnrListener = anrListener ?: DEFAULT_ANR_LISTENER
55 | return this
56 | }
57 |
58 | fun setAnrInterceptor(anrInterceptor: AnrInterceptor?): AnrMonitor {
59 | mAnrInterceptor = anrInterceptor ?: DEFAULT_ANR_INTERCEPTOR
60 | return this
61 | }
62 |
63 | fun setOnInterruptedListener(onInterruptedListener: OnInterruptedListener?): AnrMonitor {
64 | mInterruptedListener = onInterruptedListener ?: DEFAULT_INTERRUPTION_LISTENER
65 | return this
66 | }
67 |
68 | /**
69 | * Set the prefix that a thread's name must have for the thread to be reported.
70 | * Note that the main thread is always reported.
71 | * Default "".
72 | *
73 | * @param prefix The thread name's prefix for a thread to be reported.
74 | * @return itself for chaining.
75 | */
76 | fun setReportThreadNamePrefix(prefix: String): AnrMonitor {
77 | mPrefix = prefix
78 | return this
79 | }
80 |
81 | /**
82 | * Set that only the main thread will be reported.
83 | *
84 | * @return itself for chaining.
85 | */
86 | fun setReportMainThreadOnly(): AnrMonitor {
87 | mPrefix = null
88 | return this
89 | }
90 |
91 | /**
92 | * Set that all threads will be reported (default behaviour).
93 | *
94 | * @return itself for chaining.
95 | */
96 | fun setReportAllThreads(): AnrMonitor {
97 | mPrefix = ""
98 | return this
99 | }
100 |
101 | /**
102 | * Set that all running threads will be reported,
103 | * even those from which no stack trace could be extracted.
104 | * Default false.
105 | *
106 | * @param logThreadsWithoutStackTrace Whether or not all running threads should be reported
107 | * @return itself for chaining.
108 | */
109 | fun setLogThreadWithoutStackTrace(logThreadsWithoutStackTrace: Boolean): AnrMonitor {
110 | mLogThreadWithoutStackTrace = logThreadsWithoutStackTrace
111 | return this
112 | }
113 |
114 | /**
115 | * Set whether to ignore the debugger when detecting ANRs.
116 | * When ignoring the debugger, ANRWatchdog will detect ANRs even if the debugger is connected.
117 | * By default, it does not, to avoid interpreting debugging pauses as ANRs.
118 | * Default false.
119 | *
120 | * @param ignoreDebugger Whether to ignore the debugger.
121 | * @return itself for chaining.
122 | */
123 | fun setIgnoreDebugger(ignoreDebugger: Boolean): AnrMonitor {
124 | mIgnoreDebugger = ignoreDebugger
125 | return this
126 | }
127 |
128 | override fun run() {
129 | var interval = timeoutInterval
130 | while (!isInterrupted) {
131 | if (mStopped) {
132 | try {
133 | Thread.sleep(timeoutInterval)
134 | } catch (e: InterruptedException) {
135 | mInterruptedListener?.onInterrupted(e)
136 | return
137 | }
138 | continue
139 | }
140 | val needPost = mTick == 0L
141 | mTick += interval
142 | if (needPost) {
143 | mainHandler.post(mTicker)
144 | }
145 | try {
146 | Thread.sleep(interval)
147 | } catch (e: InterruptedException) {
148 | mInterruptedListener?.onInterrupted(e)
149 | return
150 | }
151 | // If the main thread has not handled _ticker, it is blocked. ANR.
152 | if (mTick != 0L && !mReported) {
153 | //noinspection ConstantConditions
154 | if (!mIgnoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
155 | AnrLog.logd("An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))")
156 | mReported = true
157 | continue
158 | }
159 | interval = mAnrInterceptor?.intercept(mTick) ?: 0
160 | if (interval > 0) {
161 | continue
162 | }
163 | val anrError = if (mPrefix == null) {
164 | AnrError.newMainInstance(mTick)
165 | } else {
166 | AnrError.newInstance(mTick, mPrefix!!, mLogThreadWithoutStackTrace)
167 | }
168 | mAnrListener?.onAppNotResponding(anrError)
169 | interval = timeoutInterval
170 | mReported = true
171 | }
172 | }
173 | }
174 |
175 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
176 | if (event == Lifecycle.Event.ON_CREATE) {//app is created
177 | onAppCreate()
178 | } else if (event == Lifecycle.Event.ON_STOP) {//no activities in stack
179 | onAppStop()
180 | } else if (event == Lifecycle.Event.ON_START) {
181 | onAppStart()
182 | }
183 | }
184 |
185 | private fun onAppStart() {
186 | mStopped = false
187 | }
188 |
189 | private fun onAppCreate() {
190 | this.start()
191 | }
192 |
193 | private fun onAppStop() {
194 | mStopped = true
195 | }
196 |
197 | /**
198 | * When {@see Application#onTerminate()} is called, invoke this method manually.
199 | */
200 | fun onAppTerminate() {
201 | interrupt()
202 | }
203 |
204 | @Synchronized
205 | override fun start() {
206 | if (isStarted()) {
207 | return
208 | }
209 | super.start()
210 | }
211 |
212 | private fun isStarted(): Boolean {
213 | return state.ordinal > State.NEW.ordinal && state.ordinal < State.TERMINATED.ordinal
214 | }
215 |
216 | companion object {
217 | const val DEFAULT_ANR_TIMEOUT = 5000L
218 |
219 | private val DEFAULT_ANR_LISTENER = object : AnrListener {
220 | override fun onAppNotResponding(error: AnrError) {
221 | throw error
222 | }
223 | }
224 |
225 | private val DEFAULT_INTERRUPTION_LISTENER = object : OnInterruptedListener {
226 | override fun onInterrupted(e: InterruptedException) {
227 | AnrLog.logd(e.message)
228 | }
229 | }
230 |
231 | private val DEFAULT_ANR_INTERCEPTOR = object : AnrInterceptor {
232 | override fun intercept(duration: Long): Long {
233 | return 0
234 | }
235 | }
236 | }
237 | }
--------------------------------------------------------------------------------
/anr-library/src/main/java/me/bytebeats/anr/AnrMonitor2.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anr
2 |
3 | import android.os.Debug
4 | import android.os.Handler
5 | import android.os.HandlerThread
6 | import android.os.Looper
7 | import androidx.lifecycle.Lifecycle
8 | import androidx.lifecycle.LifecycleEventObserver
9 | import androidx.lifecycle.LifecycleOwner
10 |
11 | /**
12 | * Created by bytebeats on 2021/6/30 : 12:26
13 | * E-mail: happychinapc@gmail.com
14 | * Quote: Peasant. Educated. Worker
15 | */
16 |
17 | /**
18 | * Constructs a watchdog that checks the ui thread every given interval
19 | *
20 | * @param timeoutInterval The interval, in milliseconds, between to checks of the UI thread.
21 | * It is therefore the maximum time the UI may freeze before being reported as ANR.
22 | */
23 | class AnrMonitor2(private val timeoutInterval: Long = DEFAULT_ANR_TIMEOUT) :
24 | LifecycleEventObserver {
25 | private val mainHandler: Handler = Handler(Looper.getMainLooper())
26 | private var handlerThread: HandlerThread? = null
27 | private var backgroundHandler: Handler? = null
28 |
29 | init {
30 | handlerThread = object : HandlerThread(TAG, Thread.NORM_PRIORITY) {
31 | override fun onLooperPrepared() {
32 | super.onLooperPrepared()
33 | backgroundHandler = Handler(handlerThread!!.looper)
34 | }
35 | }
36 | }
37 |
38 | private var mAnrInterceptor: AnrInterceptor? = DEFAULT_ANR_INTERCEPTOR
39 | private var mAnrListener: AnrListener? = DEFAULT_ANR_LISTENER
40 |
41 | private var mPrefix: String? = ""
42 | private var mLogThreadWithoutStackTrace = false
43 | private var mIgnoreDebugger = false
44 |
45 | @Volatile
46 | private var mTick = 0L
47 |
48 | @Volatile
49 | private var mReported = false
50 |
51 | @Volatile
52 | private var mInterval: Long = timeoutInterval
53 |
54 | private val mTicker = Runnable {
55 | mTick = 0
56 | mReported = false
57 | }
58 |
59 | private val mAnrCollector = Runnable {
60 | // If the main thread has not handled mTicker, it is blocked. ANR.
61 | if (mTick != 0L && !mReported) {
62 | //noinspection ConstantConditions
63 | if (!mIgnoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
64 | AnrLog.logd("An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))")
65 | mReported = true
66 | delayToTryCollectingAnr()
67 | return@Runnable
68 | }
69 | mInterval = mAnrInterceptor?.intercept(mTick) ?: 0
70 | if (mInterval > 0) {
71 | delayToTryCollectingAnr()
72 | return@Runnable
73 | }
74 | val anrError = if (mPrefix == null) {
75 | AnrError.newMainInstance(mTick)
76 | } else {
77 | AnrError.newInstance(mTick, mPrefix!!, mLogThreadWithoutStackTrace)
78 | }
79 | mAnrListener?.onAppNotResponding(anrError)
80 | mInterval = timeoutInterval
81 | mReported = true
82 | }
83 | delayToTryCollectingAnr()
84 | }
85 |
86 | fun setAnrListener(anrListener: AnrListener?): AnrMonitor2 {
87 | mAnrListener = anrListener ?: DEFAULT_ANR_LISTENER
88 | return this
89 | }
90 |
91 | fun setAnrInterceptor(anrInterceptor: AnrInterceptor?): AnrMonitor2 {
92 | mAnrInterceptor = anrInterceptor ?: DEFAULT_ANR_INTERCEPTOR
93 | return this
94 | }
95 |
96 | /**
97 | * Set the prefix that a thread's name must have for the thread to be reported.
98 | * Note that the main thread is always reported.
99 | * Default "".
100 | *
101 | * @param prefix The thread name's prefix for a thread to be reported.
102 | * @return itself for chaining.
103 | */
104 | fun setReportThreadNamePrefix(prefix: String): AnrMonitor2 {
105 | mPrefix = prefix
106 | return this
107 | }
108 |
109 | /**
110 | * Set that only the main thread will be reported.
111 | *
112 | * @return itself for chaining.
113 | */
114 | fun setReportMainThreadOnly(): AnrMonitor2 {
115 | mPrefix = null
116 | return this
117 | }
118 |
119 | /**
120 | * Set that all threads will be reported (default behaviour).
121 | *
122 | * @return itself for chaining.
123 | */
124 | fun setReportAllThreads(): AnrMonitor2 {
125 | mPrefix = ""
126 | return this
127 | }
128 |
129 | /**
130 | * Set that all running threads will be reported,
131 | * even those from which no stack trace could be extracted.
132 | * Default false.
133 | *
134 | * @param logThreadsWithoutStackTrace Whether or not all running threads should be reported
135 | * @return itself for chaining.
136 | */
137 | fun setLogThreadWithoutStackTrace(logThreadsWithoutStackTrace: Boolean): AnrMonitor2 {
138 | mLogThreadWithoutStackTrace = logThreadsWithoutStackTrace
139 | return this
140 | }
141 |
142 | /**
143 | * Set whether to ignore the debugger when detecting ANRs.
144 | * When ignoring the debugger, ANRWatchdog will detect ANRs even if the debugger is connected.
145 | * By default, it does not, to avoid interpreting debugging pauses as ANRs.
146 | * Default false.
147 | *
148 | * @param ignoreDebugger Whether to ignore the debugger.
149 | * @return itself for chaining.
150 | */
151 | fun setIgnoreDebugger(ignoreDebugger: Boolean): AnrMonitor2 {
152 | mIgnoreDebugger = ignoreDebugger
153 | return this
154 | }
155 |
156 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
157 | if (event == Lifecycle.Event.ON_CREATE) {//app is created
158 | onAppCreate()
159 | } else if (event == Lifecycle.Event.ON_STOP) {//no activities in stack
160 | onAppStop()
161 | } else if (event == Lifecycle.Event.ON_START) {//when first activity is started
162 | onAppStart()
163 | }
164 | }
165 |
166 | private fun onAppStart() {
167 | delayToTryCollectingAnr()
168 | }
169 |
170 | @Synchronized
171 | private fun delayToTryCollectingAnr() {
172 | val needPost = mTick == 0L
173 | mTick += mInterval
174 | if (needPost) {
175 | mainHandler.post(mTicker)
176 | }
177 | backgroundHandler?.postDelayed(mAnrCollector, ANR_COLLECTING_INTERVAL)
178 | }
179 |
180 | private fun onAppCreate() {
181 | handlerThread?.start()
182 | }
183 |
184 | private fun onAppStop() {
185 | backgroundHandler?.removeCallbacksAndMessages(null)
186 | mainHandler.removeCallbacksAndMessages(null)
187 | }
188 |
189 | /**
190 | * When {@see Application#onTerminate()} is called, invoke this method manually.
191 | * It will not be invoke. Because {@see Application#onTerminate()} is for use in emulated process environments.
192 | * It will never be called on a production Android device, where processes are
193 | * removed by simply killing them; no user code (including this callback)
194 | * is executed when doing so.
195 | */
196 | fun onAppTerminate() {
197 | handlerThread?.quitSafely()
198 | handlerThread = null
199 | }
200 |
201 | companion object {
202 | const val DEFAULT_ANR_TIMEOUT = 5000L
203 | const val ANR_COLLECTING_INTERVAL = 2000L
204 | private const val TAG = "||ANR-Monitor||"
205 |
206 | private val DEFAULT_ANR_LISTENER = object : AnrListener {
207 | override fun onAppNotResponding(error: AnrError) {
208 | throw error
209 | }
210 | }
211 |
212 | private val DEFAULT_ANR_INTERCEPTOR = object : AnrInterceptor {
213 | override fun intercept(duration: Long): Long {
214 | return 0
215 | }
216 | }
217 | }
218 | }
--------------------------------------------------------------------------------
/anr-library/src/main/java/me/bytebeats/anr/OnInterruptedListener.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anr
2 |
3 | /**
4 | * Created by bytebeats on 2021/6/30 : 11:36
5 | * E-mail: happychinapc@gmail.com
6 | * Quote: Peasant. Educated. Worker
7 | */
8 |
9 | /**
10 | * Anr-monitor run on a Thread which may be interrupted.
11 | */
12 | interface OnInterruptedListener {
13 | /**
14 | * Called when Anr-monitor Thread is interrupted.
15 | */
16 | fun onInterrupted(e: InterruptedException)
17 | }
--------------------------------------------------------------------------------
/anr-library/src/main/java/me/bytebeats/anr/StackTraceCollector.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anr
2 |
3 | import java.io.Serializable
4 |
5 | /**
6 | * Created by bytebeats on 2021/6/30 : 11:47
7 | * E-mail: happychinapc@gmail.com
8 | * Quote: Peasant. Educated. Worker
9 | */
10 |
11 | class StackTraceCollector(
12 | private val name: String,
13 | private val stackTraces: Array
14 | ) : Serializable {
15 | inner class StackTraceThrowable(private val other: StackTraceThrowable?) :
16 | Throwable(name, other) {
17 | override fun fillInStackTrace(): Throwable {
18 | stackTrace = stackTraces
19 | return this
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/anr-library/src/main/java/me/bytebeats/anr/ThreadComparator.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anr
2 |
3 | import android.os.Looper
4 | import java.util.*
5 |
6 | /**
7 | * Created by bytebeats on 2021/6/30 : 12:08
8 | * E-mail: happychinapc@gmail.com
9 | * Quote: Peasant. Educated. Worker
10 | */
11 |
12 | /**
13 | * Thread Comparator: Main Thread is first, others ordered by Thread names.
14 | */
15 | class ThreadComparator : Comparator {
16 | override fun compare(o1: Thread, o2: Thread): Int {
17 | if (o1 == o2) return 0
18 | if (o1 == Looper.getMainLooper().thread) return 1
19 | if (o2 == Looper.getMainLooper().thread) return -1
20 | return o2.name.compareTo(o1.name)
21 | }
22 | }
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 30
8 | buildToolsVersion "30.0.3"
9 |
10 | defaultConfig {
11 | applicationId "me.bytebeats.anrmonitor"
12 | minSdkVersion 19
13 | targetSdkVersion 30
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 | }
34 |
35 | dependencies {
36 |
37 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
38 | implementation 'androidx.core:core-ktx:1.5.0'
39 | implementation 'androidx.appcompat:appcompat:1.3.0'
40 | implementation 'com.google.android.material:material:1.3.0'
41 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
42 |
43 | implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
44 |
45 | // implementation(project(path: ':anr-library'))
46 | // implementation(project(path: ':lags-library'))
47 | // implementation project(path: ':deadlock-library')
48 |
49 | //load from maven central
50 | implementation('io.github.bytebeats:anr:1.0.0')
51 | implementation('io.github.bytebeats:lags:1.0.1')
52 | implementation('io.github.bytebeats:deadlock:1.0.0')
53 |
54 | testImplementation 'junit:junit:4.13.2'
55 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
56 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
57 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/me/bytebeats/anrmonitor/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anrmonitor
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("me.bytebeats.anrmonitor", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/me/bytebeats/anrmonitor/APMApplication.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anrmonitor
2 |
3 | import android.app.Application
4 | import android.util.Log
5 | import androidx.lifecycle.ProcessLifecycleOwner
6 | import me.bytebeats.anr.AnrError
7 | import me.bytebeats.anr.AnrInterceptor
8 | import me.bytebeats.anr.AnrListener
9 | import me.bytebeats.anr.AnrLog
10 | import me.bytebeats.anr.AnrMonitor
11 | import me.bytebeats.anr.AnrMonitor2
12 | import me.bytebeats.anr.OnInterruptedListener
13 | import me.bytebeats.lag.LagMonitor
14 | import me.bytebeats.lag.OnFrameJankListener
15 | import me.bytebeats.lag.OnProcessNotRespondingListener
16 | import me.bytebeats.lag.OnUIThreadBlockListener
17 | import java.io.ByteArrayOutputStream
18 | import java.io.IOException
19 | import java.io.ObjectOutputStream
20 |
21 | /**
22 | * Created by bytebeats on 2021/6/30 : 15:23
23 | * E-mail: happychinapc@gmail.com
24 | * Quote: Peasant. Educated. Worker
25 | */
26 |
27 | class APMApplication : Application() {
28 | // val anrMonitor = AnrMonitor(3000)
29 | val anrMonitor = AnrMonitor2(3000)
30 |
31 | val silentAnrListener = object : AnrListener {
32 | override fun onAppNotResponding(error: AnrError) {
33 | Log.d("anr-log", "onAppNotResponding", error)
34 | }
35 | }
36 |
37 | var duration = 4L
38 |
39 | override fun onCreate() {
40 | super.onCreate()
41 | AnrLog.logStackTrace = false
42 | anrMonitor.setIgnoreDebugger(true)
43 | .setReportAllThreads()
44 | .setAnrListener(object : AnrListener {
45 | override fun onAppNotResponding(error: AnrError) {
46 | AnrLog.logd("onAppNotResponding")
47 | AnrLog.logd(error)
48 | try {
49 | ObjectOutputStream(ByteArrayOutputStream()).writeObject(error)
50 | } catch (e: IOException) {
51 | throw RuntimeException(e)
52 | }
53 | AnrLog.logd("Anr Error was successfully serialized")
54 | // throw error
55 | }
56 | }).setAnrInterceptor(object : AnrInterceptor {
57 | override fun intercept(duration: Long): Long {
58 | val ret = this@APMApplication.duration - duration
59 | if (ret > 0) {
60 | AnrLog.logd(
61 | "Intercepted ANR that is too short ($duration ms), postponing for $ret ms."
62 | )
63 | }
64 | return ret
65 | }
66 | })
67 | // .setOnInterruptedListener(object : OnInterruptedListener {
68 | // override fun onInterrupted(e: InterruptedException) {
69 | // throw e
70 | // }
71 | // })
72 | ProcessLifecycleOwner.get().lifecycle.addObserver(anrMonitor)
73 |
74 | val lagMonitor = LagMonitor.Builder(this.applicationContext)
75 | .setThresholdTimeMillis(3000L)
76 | .setLagLogEnabled(true)
77 | .setMonitorMode(LagMonitor.MonitorMode.UI)
78 | .setOnFrameJankListener(object : OnFrameJankListener {
79 | override fun onJank(janks: Int) {
80 | Log.d("lag-log", "janks: $janks")
81 | }
82 | })
83 | .setOnUIThreadRunListener(object : OnUIThreadBlockListener {
84 | override fun onBlock(lagTime: Long, uiRunTime: Long) {
85 | Log.d("lag-log", "lagTime: $lagTime, uiRunTime: $uiRunTime")
86 | }
87 | })
88 | .setOnProcessNotRespondingListener(object : OnProcessNotRespondingListener {
89 | override fun onNotResponding(processInfo: String?) {
90 | Log.d("lag-log", "processInfo: $processInfo")
91 | }
92 | })
93 | .build()
94 | // ProcessLifecycleOwner.get().lifecycle.addObserver(lagMonitor)
95 | }
96 |
97 | override fun onTerminate() {
98 | super.onTerminate()
99 | anrMonitor.onAppTerminate()
100 | }
101 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/bytebeats/anrmonitor/DeadLockUtils.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anrmonitor
2 |
3 | import me.bytebeats.anr.AnrLog
4 | import kotlin.concurrent.thread
5 |
6 | /**
7 | * Created by bytebeats on 2021/6/30 : 18:30
8 | * E-mail: happychinapc@gmail.com
9 | * Quote: Peasant. Educated. Worker
10 | */
11 |
12 | /**
13 | * utility class to create dead lock and anr
14 | */
15 | object DeadLockUtils {
16 | private const val TAG = "DeadLockUtils"
17 |
18 | fun createDeadLockAnr() {
19 | val lock1 = Any()
20 | val lock2 = Any()
21 | thread() {
22 | synchronized(lock1) {
23 | Thread.sleep(100)
24 | synchronized(lock2) {
25 | AnrLog.logd("ANR: getLock2")
26 | }
27 | }
28 | }
29 | synchronized(lock2) {
30 | Thread.sleep(100)
31 | synchronized(lock1) {
32 | AnrLog.logd("ANR: getLock1")
33 | }
34 | }
35 | }
36 |
37 | fun createDeadLock() {
38 | val lock1 = Any()
39 | val lock2 = Any()
40 | thread(start = true) {
41 | synchronized(lock1) {
42 | Thread.sleep(100)
43 | synchronized(lock2) {
44 | AnrLog.logd("ANR: thead1")
45 | }
46 | }
47 | }
48 | thread(start = true) {
49 | synchronized(lock2) {
50 | Thread.sleep(100)
51 | synchronized(lock1) {
52 | AnrLog.logd("ANR: thead2")
53 | }
54 | }
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/bytebeats/anrmonitor/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anrmonitor
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.util.Log
6 | import android.widget.TextView
7 | import androidx.appcompat.app.AppCompatActivity
8 | import me.bytebeats.anr.AnrLog
9 | import me.bytebeats.deadlock.DeadLockMonitor
10 |
11 | /**
12 | * Created by bytebeats on 2021/6/28 : 19:55
13 | * E-mail: happychinapc@gmail.com
14 | * Quote: Peasant. Educated. Worker
15 | */
16 |
17 | class MainActivity : AppCompatActivity(R.layout.activity_main) {
18 | private val deadLockAnr by lazy { findViewById(R.id.dead_lock_anr) }
19 | private val minAnrDuration by lazy { findViewById(R.id.min_anr_duration) }
20 | private val reportMode by lazy { findViewById(R.id.report_mode) }
21 | private val behavior by lazy { findViewById(R.id.behavior) }
22 | private val threadSleep by lazy { findViewById(R.id.thread_sleep) }
23 | private val infiniteLoop by lazy { findViewById(R.id.infinite_loop) }
24 | private val deadLock by lazy { findViewById(R.id.dead_lock) }
25 | private val deadLockMonitorTV by lazy { findViewById(R.id.dead_lock_monitor) }
26 | private val killme by lazy { findViewById(R.id.kill_my_process) }
27 |
28 | private val deadLockMonitor by lazy {
29 | DeadLockMonitor().setDeadLockListener {
30 | Log.i(
31 | "deadlock",
32 | it.toString()
33 | )
34 | }
35 | }
36 |
37 | private val mMutex = Any()
38 |
39 | private fun sleepFor8s() {
40 | try {
41 | Thread.sleep(8L * 1000L)
42 | } catch (e: InterruptedException) {
43 | e.printStackTrace()
44 | }
45 | }
46 |
47 | private fun infiniteLoop() {
48 | var i = 0
49 | while (true) {
50 | i++
51 | }
52 | }
53 |
54 | private inner class LockerThread() : Thread("Activity: Locker") {
55 | override fun run() {
56 | synchronized(mMutex) {
57 | while (true) sleepFor8s()
58 | }
59 | }
60 | }
61 |
62 | private fun deadLock() {
63 | LockerThread().start()
64 | Handler().post { synchronized(mMutex) { AnrLog.logd("There should be a dead lock before this message") } }
65 | }
66 |
67 | private var mode = 0
68 | private var crash = true
69 |
70 | override fun onCreate(savedInstanceState: Bundle?) {
71 | super.onCreate(savedInstanceState)
72 | val anrApp = application as APMApplication
73 | deadLockAnr.setOnClickListener {
74 | AnrLog.logd("createDeadLockAnr")
75 | DeadLockUtils.createDeadLockAnr()
76 | }
77 | deadLock.setOnClickListener { deadLock() }
78 | infiniteLoop.setOnClickListener { infiniteLoop() }
79 | threadSleep.setOnClickListener { sleepFor8s() }
80 | behavior.text = "Crash"
81 | behavior.setOnClickListener {
82 | crash = !crash
83 | if (crash) {
84 | behavior.text = "Crash"
85 | anrApp.anrMonitor.setAnrListener(null)
86 | } else {
87 | behavior.text = "Silent"
88 | anrApp.anrMonitor.setAnrListener(anrApp.silentAnrListener)
89 | }
90 | }
91 | reportMode.text = "All Threads"
92 | reportMode.setOnClickListener {
93 | mode = (mode + 1) % 3
94 | when (mode) {
95 | 0 -> {
96 | reportMode.text = "All Threads"
97 | anrApp.anrMonitor.setReportAllThreads()
98 | }
99 | 1 -> {
100 | reportMode.text = "Main Thread only"
101 | anrApp.anrMonitor.setReportMainThreadOnly()
102 | }
103 | 2 -> {
104 | reportMode.text = "Filtered"
105 | anrApp.anrMonitor.setReportThreadNamePrefix("Activity:")
106 | }
107 | }
108 | }
109 | minAnrDuration.text = "${anrApp.duration} seconds"
110 | minAnrDuration.setOnClickListener {
111 | anrApp.duration = anrApp.duration % 6 + 2
112 | minAnrDuration.text = "${anrApp.duration} seconds"
113 | }
114 | deadLockMonitorTV.setOnClickListener {
115 | deadLockMonitorTV.postDelayed({
116 | deadLockMonitor.start()
117 | }, 200)
118 | DeadLockUtils.createDeadLock()
119 | }
120 |
121 | killme.setOnClickListener {
122 | finish()
123 | // android.os.Process.killProcess(android.os.Process.myPid());
124 | }
125 | }
126 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
3 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
9 |
11 |
13 |
15 |
17 |
19 |
21 |
23 |
25 |
27 |
29 |
31 |
33 |
35 |
37 |
39 |
41 |
43 |
45 |
47 |
49 |
51 |
53 |
55 |
57 |
59 |
61 |
63 |
65 |
67 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
30 |
31 |
42 |
43 |
54 |
55 |
56 |
67 |
68 |
79 |
80 |
91 |
92 |
103 |
104 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Anr-Monitor
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/test/java/me/bytebeats/anrmonitor/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package me.bytebeats.anrmonitor
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = "1.5.20"
4 | ext.lifecycle_version = "2.3.1"
5 | repositories {
6 | maven { url('https://repo1.maven.org/maven2/') }
7 | google()
8 | mavenCentral()
9 | jcenter()
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:4.2.2'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 |
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | maven { url('https://repo1.maven.org/maven2/') }
23 | google()
24 | mavenCentral()
25 | jcenter()
26 | }
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
33 | ext {
34 | GROUP_ID = 'io.github.bytebeats'
35 | DEVELOPER_ID = 'bytebeats'
36 | DEVELOPER_NAME = 'Chen Pan'
37 | DEVELOPER_EMAIL = 'happychinapc@gmail.com'
38 | SCM_CONNECTION = 'scm:git:github.com/bytebeats/Anr-Monitor.git'
39 | SCM_DEVELOPER_CONNECTION = 'scm:git:ssh:github.com/bytebeats/Anr-Monitor.git'
40 | SCM_URL = 'https://github.com/bytebeats/Anr-Monitor/tree/master'
41 | LICENSE_NAME = 'The Apache License, Version 2.0'
42 | LICENSE_URL = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
43 | POM_URL = 'https://github.com/bytebeats/Anr-Monitor'
44 | RELEASES_REPO_URL = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/'
45 | SNAPSHOTS_REPO_URL = 'https://s01.oss.sonatype.org/content/repositories/snapshots/'
46 | }
--------------------------------------------------------------------------------
/deadlock-library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/deadlock-library/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | }
4 |
5 | ext {
6 | LAGS_ARTIFACT_ID = 'deadlock'
7 | LAGS_VERSION = '1.0.0'
8 | LAGS_POM_DESCRIPTION = 'Android deadlock checking'
9 | }
10 |
11 | apply from: 'publish_deadlock.gradle'
12 |
13 | android {
14 | compileSdkVersion 30
15 | buildToolsVersion "30.0.3"
16 |
17 | defaultConfig {
18 | minSdkVersion 19
19 | targetSdkVersion 30
20 | versionCode 1
21 | versionName "1.0"
22 |
23 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
24 | consumerProguardFiles "consumer-rules.pro"
25 |
26 | externalNativeBuild {
27 | cmake {
28 | cppFlags ""
29 | }
30 | }
31 |
32 | }
33 |
34 | externalNativeBuild {
35 | cmake {
36 | path 'src/main/cpp/CMakeLists.txt'
37 | version('3.10.2')
38 | }
39 | }
40 |
41 | buildTypes {
42 | release {
43 | minifyEnabled false
44 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
45 | }
46 | }
47 | compileOptions {
48 | sourceCompatibility JavaVersion.VERSION_1_7
49 | targetCompatibility JavaVersion.VERSION_1_7
50 | }
51 | }
52 |
53 | dependencies {
54 | implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
55 | }
--------------------------------------------------------------------------------
/deadlock-library/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytebeats/Anr-Monitor/77d211b8e389134161e8697bce1b6569715d3287/deadlock-library/consumer-rules.pro
--------------------------------------------------------------------------------
/deadlock-library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/deadlock-library/publish_deadlock.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | //only build and setting gradle can use `plugins`
5 | //plugins {
6 | // id('signing')
7 | // id('maven-publish')
8 | //}
9 |
10 | task androidSourcesJar(type: Jar) {
11 | from android.sourceSets.main.java.source
12 | classifier('sources')
13 | }
14 |
15 | apply from: '../local_setting_reader.gradle'
16 |
17 | project.publishing {
18 | publications {
19 | release(MavenPublication) {
20 | // The coordinates of the library, being set from variables that
21 | // we'll set up in a moment
22 | groupId GROUP_ID
23 | artifactId LAGS_ARTIFACT_ID
24 | version LAGS_VERSION
25 |
26 | // Two artifacts, the `aar` and the sources
27 | artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
28 | artifact androidSourcesJar
29 |
30 | // Self-explanatory metadata for the most part
31 | pom {
32 | name = LAGS_ARTIFACT_ID
33 | description = LAGS_POM_DESCRIPTION
34 | // If your project has a dedicated site, use its URL here
35 | url = POM_URL
36 | licenses {
37 | license {
38 | //协议类型,一般默认Apache License2.0的话不用改:
39 | name = LICENSE_NAME
40 | url = LICENSE_URL
41 | }
42 | }
43 | developers {
44 | developer {
45 | id = DEVELOPER_ID
46 | name = DEVELOPER_NAME
47 | email = DEVELOPER_EMAIL
48 | }
49 | }
50 | // Version control info, if you're using GitHub, follow the format as seen here
51 | scm {
52 | //修改成你的Git地址:
53 | connection = SCM_CONNECTION
54 | developerConnection = SCM_DEVELOPER_CONNECTION
55 | //分支地址:
56 | url = SCM_URL
57 | }
58 | // A slightly hacky fix so that your POM will include any transitive dependencies
59 | // that your library builds upon
60 | withXml {
61 | def dependenciesNode = asNode().appendNode('dependencies')
62 |
63 | project.configurations.implementation.allDependencies.each {
64 | def dependencyNode = dependenciesNode.appendNode('dependency')
65 | dependencyNode.appendNode('groupId', it.group)
66 | dependencyNode.appendNode('artifactId', it.name)
67 | dependencyNode.appendNode('version', it.version)
68 | }
69 | }
70 | }
71 | }
72 | }
73 | repositories {
74 | // The repository to publish to, Sonatype/MavenCentral
75 | maven {
76 | // This is an arbitrary name, you may also use "mavencentral" or
77 | // any other name that's descriptive for you
78 | name = "deadlock"
79 |
80 | // You only need this if you want to publish snapshots, otherwise just set the URL
81 | // to the release repo directly
82 | url = version.endsWith('SNAPSHOT') ? SNAPSHOTS_REPO_URL : RELEASES_REPO_URL
83 |
84 | // The username and password we've fetched earlier
85 | credentials {
86 | username ossrhUsername
87 | password ossrhPassword
88 | }
89 | }
90 | }
91 | }
92 | signing {
93 | sign publishing.publications
94 | }
--------------------------------------------------------------------------------
/deadlock-library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/deadlock-library/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # For more information about using CMake with Android Studio, read the
2 | # documentation: https://d.android.com/studio/projects/add-native-code.html
3 |
4 | # Sets the minimum version of CMake required to build the native library.
5 |
6 | cmake_minimum_required(VERSION 3.10.2)
7 |
8 | # Declares and names the project.
9 |
10 | project("deadlock-lib")
11 |
12 | # Creates and names a library, sets it as either STATIC
13 | # or SHARED, and provides the relative paths to its source code.
14 | # You can define multiple libraries, and CMake builds them for you.
15 | # Gradle automatically packages shared libraries with your APK.
16 |
17 | add_library(
18 | # Sets the name of the library.
19 | deadlock-lib
20 | # Sets the library as a shared library.
21 | SHARED
22 | # Provides a relative path to your source file(s).
23 | deadlock_lib.cpp dl_open.c
24 | )
25 |
26 | # Searches for a specified prebuilt library and stores the path as a
27 | # variable. Because CMake includes system libraries in the search path by
28 | # default, you only need to specify the name of the public NDK library
29 | # you want to add. CMake verifies that the library exists before
30 | # completing its build.
31 |
32 | find_library(
33 | # Sets the name of the path variable.
34 | log-lib
35 | # Specifies the name of the NDK library that you want CMake to locate.
36 | log
37 | )
38 |
39 | # Specifies libraries CMake should link to your target library. You
40 | # can link multiple libraries, such as libraries you define in this
41 | # build script, prebuilt third-party libraries, or system libraries.
42 |
43 | target_link_libraries(
44 | # Specifies the target library.
45 | deadlock-lib
46 | # Links the target library to the log library included in the NDK.
47 | ${log-lib}
48 | )
49 |
50 | include_directories(dl_open.h)
--------------------------------------------------------------------------------
/deadlock-library/src/main/cpp/deadlock_lib.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Created by bytebeats on 2021/7/3 : 15:22
4 | * E-mail: happychinapc@gmail.com
5 | * Quote: Peasant. Educated. Worker
6 | *
7 | */
8 |
9 | #include
10 | #include
11 | #include "dl_open.h"
12 |
13 | #include
14 |
15 | #define TAG "deadlock-lib"
16 | #define Log_i(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
17 |
18 | void *get_contended_monitor;
19 | void *get_lock_owner_thread_id;
20 |
21 | jint android_sdk_int;
22 |
23 | const char *get_lock_owner_symbol_name(jint sdk_int);
24 |
25 | extern "C"
26 | JNIEXPORT jint
27 |
28 | JNICALL
29 | Java_me_bytebeats_deadlock_DeadLockMonitor_nativeInit(JNIEnv *env, jobject thiz, jint sdk_int) {
30 | android_sdk_int = sdk_int;
31 | //dlopen libart.so
32 | //init
33 | ndk_init(env);
34 |
35 | //load libart.so
36 | void *so_addr = ndk_dl_open("libart.so", RTLD_NOLOAD);
37 | if (so_addr == nullptr) return 1;
38 | // Monitor::GetContendedMonitor
39 | //c++方法地址跟c不一样,c++可以重载,方法描述符会变
40 | //http://androidxref.com/8.0.0_r4/xref/system/core/libbacktrace/testdata/arm/libart.so
41 |
42 | // nm xxx.so
43 |
44 | //获取Monitor::GetContendedMonitor函数符号地址
45 | get_contended_monitor = ndk_dl_symbol(so_addr,
46 | "_ZN3art7Monitor19GetContendedMonitorEPNS_6ThreadE");
47 | if (get_contended_monitor == nullptr) return 2;
48 | // Monitor::GetLockOwnerThreadId
49 | //这个函数是用来获取 Monitor的持有者,拥有monitor的是线程
50 | get_lock_owner_thread_id = ndk_dl_symbol(so_addr, get_lock_owner_symbol_name(android_sdk_int));
51 | if (get_lock_owner_thread_id == nullptr) return 3;
52 | return 0;
53 | }
54 |
55 | const char *get_lock_owner_symbol_name(jint
56 | sdk_int) {
57 | if (sdk_int <= 29) {
58 | //pre android 9.0
59 | //http://androidxref.com/9.0.0_r3/xref/system/core/libbacktrace/testdata/arm/libart.so 搜索 GetLockOwnerThreadId
60 | return "_ZN3art7Monitor20GetLockOwnerThreadIdEPNS_6mirror6ObjectE";
61 | } else {
62 | //android 10.0+
63 | //Sorry, androidxref/.../libart.so is no available, and in Google Git, I haven't found this so library.
64 | return "_ZN3art7Monitor20GetLockOwnerThreadIdEPNS_6mirror6ObjectE";
65 | }
66 | }
67 |
68 | extern "C"
69 | JNIEXPORT jint
70 |
71 | JNICALL
72 | Java_me_bytebeats_deadlock_DeadLockMonitor_getContendedThreadIdArt(JNIEnv *env, jobject thiz,
73 | jlong thd_native_peer) {
74 | Log_i("getContendedThreadIdArt");
75 | int monitor_thread_id = 0;
76 | if (get_contended_monitor != nullptr && get_lock_owner_thread_id != nullptr) {
77 | Log_i("get_contended_monitor != null");
78 | //调用一下获取monitor的函数
79 | int monitorObj = ((int (*)(long)) get_contended_monitor)(thd_native_peer);
80 | if (monitorObj != 0) {
81 | Log_i("monitorObj != 0");
82 | // 获取这个monitor的持有者,返回一个线程id
83 | monitor_thread_id = ((int (*)(long)) get_lock_owner_thread_id)(monitorObj);
84 | } else {
85 | monitor_thread_id = 0;
86 | }
87 | } else {
88 | Log_i("get_contended_monitor == null || get_lock_owner_thread_id == null");
89 | }
90 | return monitor_thread_id;
91 | }
92 |
93 | extern "C"
94 | JNIEXPORT jint
95 |
96 | JNICALL
97 | Java_me_bytebeats_deadlock_DeadLockMonitor_getThreadIdFromNativePeer(JNIEnv *env, jobject thiz,
98 | jlong thd_native_peer) {
99 | Log_i("getThreadIdFromNativePeer");
100 | if (thd_native_peer != 0) {
101 | Log_i("thread id != 0");
102 | if (android_sdk_int > 20) {//Android 5.0
103 | //reinterpret_cast 强制类型转换
104 | int *pInt = reinterpret_cast(thd_native_peer);
105 | //地址 +3,就是ThreadId,这个怎么来的呢?
106 | pInt = pInt + 3;
107 | return *pInt;//返回 monitor 所使用的Thread id
108 | }
109 | } else {
110 | Log_i("suspendThreadArt failed");
111 | }
112 | return 0;
113 | }
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/deadlock-library/src/main/cpp/dl_open.c:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Created by bytebeats on 2021/7/2 : 11:36
4 | * E-mail: happychinapc@gmail.com
5 | * Quote: Peasant. Educated. Worker
6 | *
7 | */
8 |
9 | #include "dl_open.h"
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #define TAG "deadlock-dl-open"
17 | #define UNSUPPORTED "Not Supported"
18 | #define PAGE_SIZE 4096
19 | #define Log_i(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
20 |
21 | static volatile int SDK_INT = 0;
22 | static void *quick_on_stack_back;
23 |
24 | static union {
25 | void *stub;
26 |
27 | void *
28 | (*quick_on_stack_replace)(const void *param1, const void *param2, const void *fake_trampoline,
29 | const void *called);
30 | } STUBS;
31 |
32 | void JNIEXPORT
33 | ndk_init(JNIEnv
34 | *env){
35 | if(SDK_INT <= 0) {
36 | char sdk[PROP_VALUE_MAX];
37 | __system_property_get("ro.build.version.sdk", sdk);
38 | SDK_INT = atoi(sdk);
39 | if(SDK_INT >= 24) {
40 | static __attribute__((__aligned__(PAGE_SIZE))) uint8_t __insns[PAGE_SIZE];
41 | STUBS.
42 | stub = __insns;
43 | mprotect(__insns,
44 | sizeof(__insns), PROT_READ | PROT_WRITE | PROT_EXEC);
45 | // we are currently hijacking "FatalError" as a fake system-call trampoline
46 | uintptr_t pv = (uintptr_t)(*env)->FatalError;
47 | uintptr_t pu = (pv | (PAGE_SIZE - 1)) + 1u;
48 | uintptr_t pd = pv & ~(PAGE_SIZE - 1);
49 | mprotect((void *)pd, pv + 8u >= pu ? PAGE_SIZE * 2U : PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC);
50 | quick_on_stack_back = (void *) pv;
51 | #if defined(__i386__)
52 | /*
53 | DEFINE_FUNCTION art_quick_on_stack_replace
54 | movl 12(REG_VAR(esp)), REG_VAR(eax)
55 | movl (REG_VAR(esp)), REG_VAR(edx)
56 | movl REG_VAR(eax), (REG_VAR(esp))
57 | movl REG_VAR(edx), 12(REG_VAR(esp))
58 | pushl 16(REG_VAR(esp))
59 | ret
60 | END_FUNCTION art_quick_on_stack_replace
61 | */
62 | memcpy(__insns, "\x8B\x44\x24\x0C\x8B\x14\x24\x89\x04\x24\x89\x54\x24\x0C\xFF\x74\x24\x10\xC3", 19);
63 | /*
64 | DEFINE_FUNCTION art_quick_on_stack_back
65 | push 8(REG_VAR(esp))
66 | ret
67 | END_FUNCTION art_quick_on_stack_back
68 | */
69 | memcpy(quick_on_stack_back, "\xC3\xFF\x74\x24\x08\xC3", 6);
70 | quick_on_stack_back = (void *)(pv + 1);// inserts `ret` at first
71 | #elif defined(__x86_64__)
72 | // rdi, rsi, rdx, rcx, r8, r9
73 | /*
74 | 0x0000000000000000: 52 push rdx
75 | 0x0000000000000001: 52 push rdx
76 | 0x0000000000000002: FF E1 jmp rcx
77 | */
78 | memcpy(__insns, "\x52\x52\xFF\xE1", 4);
79 | /*
80 | 0x0000000000000000: 5A pop rdx
81 | 0x0000000000000000: C3 ret
82 | */
83 | memcpy(quick_on_stack_back, "\x5A\xC3", 2);
84 | #elif defined(__aarch64__)
85 | // x0~x7
86 | /*
87 | 0x0000000000000000: FD 7B BF A9 stp x29, x30, [sp, #-0x10]!
88 | 0x0000000000000004: FD 03 00 91 mov x29, sp
89 | 0x0000000000000008: FE 03 02 AA mov x30, x2
90 | 0x000000000000000C: 60 00 1F D6 br x3
91 | */
92 | memcpy(__insns, "\xFD\x7B\xBF\xA9\xFD\x03\x00\x91\xFE\x03\x02\xAA\x60\x00\x1F\xD6", 16);
93 | /*
94 | 0x0000000000000000: FD 7B C1 A8 ldp x29, x30, [sp], #0x10
95 | 0x0000000000000004: C0 03 5F D6 ret
96 | */
97 | memcpy(quick_on_stack_back, "\xFD\x7B\xC1\xA8\xC0\x03\x5F\xD6", 8);
98 | #elif defined(__arm__)
99 | // r0~r3
100 | /*
101 | 0x0000000000000000: 08 E0 2D E5 str lr, [sp, #-8]!
102 | 0x0000000000000004: 02 E0 A0 E1 mov lr, r2
103 | 0x0000000000000008: 13 FF 2F E1 bx r3
104 | */
105 | memcpy(__insns, "\x08\xE0\x2D\xE5\x02\xE0\xA0\xE1\x13\xFF\x2F\xE1", 12);
106 | if((pv & 1u) != 0u) {//Thumb
107 | /*
108 | 0x0000000000000000: 0C BC pop {r2, r3}
109 | 0x0000000000000002: 10 47 bx r2
110 | */
111 | memcpy((void *)(pv - 1), "\x0C\xBC\x10\x47", 4);
112 | } else {
113 | /*
114 | 0x0000000000000000: 0C 00 BD E8 pop {r2, r3}
115 | 0x0000000000000004: 12 FF 2F E1 bx r2
116 | */
117 | memcpy(quick_on_stack_back, "\x0C\x00\xBD\xE8\x12\xFF\x2F\xE1", 8);
118 | }
119 | #else
120 | # error UNSUPPORTED
121 | #endif
122 | Log_i("init done! quick_on_stack_replace = %p, quick_on_stack_back = %p", STUBS.stub,
123 | quick_on_stack_back);
124 | }
125 | }
126 | }
127 |
128 | void *JNIEXPORT
129 |
130 | ndk_dl_open(const char *filename, int flag) {
131 | if (SDK_INT >= 24) {
132 | #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__)
133 | return STUBS.quick_on_stack_replace(filename, (void *)flag, quick_on_stack_back, dlopen);
134 | #else
135 | # error UNSUPPORTED
136 | #endif
137 | }
138 | return dlopen(filename, flag);
139 | }
140 |
141 | int JNIEXPORT
142 |
143 | ndk_dl_close(void *handle) {
144 | if (SDK_INT >= 24) {
145 | #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__)
146 | return (int)STUBS.quick_on_stack_replace(handle, NULL, quick_on_stack_back, dlclose);
147 | #else
148 | # error UNSUPPORTED
149 | #endif
150 | }
151 | return dlclose(handle);
152 | }
153 |
154 | const char *JNIEXPORT
155 |
156 | ndk_dl_error(void) {
157 | if (SDK_INT >= 24) {
158 | #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__)
159 | return STUBS.quick_on_stack_replace(NULL, NULL, quick_on_stack_back, dlerror);
160 | #else
161 | # error UNSUPPORTED
162 | #endif
163 | }
164 | return dlerror();
165 | }
166 |
167 | void *JNIEXPORT
168 |
169 | ndk_dl_symbol(void *handle, const char *symbol) {
170 | if (SDK_INT >= 24) {
171 | #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__)
172 | return STUBS.quick_on_stack_replace(handle, symbol, quick_on_stack_back, dlsym);
173 | #else
174 | # error UNSUPPORTED
175 | #endif
176 | }
177 | return dlsym(handle, symbol);
178 | }
179 |
180 | int JNIEXPORT
181 |
182 | ndk_dl_addr(const void *addr, Dl_info *info) {
183 | if (SDK_INT >= 24) {
184 | #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__)
185 | return (int)STUBS.quick_on_stack_replace(addr, info, quick_on_stack_back, dladdr);
186 | #else
187 | # error UNSUPPORTED
188 | #endif
189 | }
190 | return dladdr(addr, info);
191 | }
192 |
193 |
194 |
--------------------------------------------------------------------------------
/deadlock-library/src/main/cpp/dl_open.h:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Created by bytebeats on 2021/7/2 : 11:36
4 | * E-mail: happychinapc@gmail.com
5 | * Quote: Peasant. Educated. Worker
6 | *
7 | */
8 |
9 | #pragma once
10 |
11 | #include
12 | #include
13 |
14 | #ifdef __cplusplus
15 | extern "C" {
16 | #endif
17 |
18 | void ndk_init(JNIEnv *env);
19 |
20 | void *ndk_dl_open(const char *filename, int flag);
21 |
22 | int ndk_dl_close(void *handle);
23 |
24 | const char *ndk_dl_error(void);
25 |
26 | void *ndk_dl_symbol(void *handle, const char *symbol);
27 |
28 | int ndk_dl_addr(const void *addr, Dl_info *info);
29 |
30 | #ifdef __cplusplus
31 | }
32 | #endif
33 | //#ifndef ANR_MONITOR_DL_OPEN_H
34 | //#define ANR_MONITOR_DL_OPEN_H
35 |
36 | //#endif //ANR_MONITOR_DL_OPEN_H
37 |
--------------------------------------------------------------------------------
/deadlock-library/src/main/java/me/bytebeats/deadlock/DeadLockError.java:
--------------------------------------------------------------------------------
1 | package me.bytebeats.deadlock;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | /**
6 | *
7 | * Created by bytebeats on 2021/7/3 : 17:51
8 | * E-mail: happychinapc@gmail.com
9 | * Quote: Peasant. Educated. Worker
10 | *
11 | */
12 | public class DeadLockError extends Error {
13 | public DeadLockError(Thread thread, DeadLockStackTraces.Error error) {
14 | super("Thread(id: " + thread.getId() + ", state = " + thread.getState().toString() + ")", error);
15 | }
16 |
17 | @NonNull
18 | @Override
19 | public synchronized Throwable fillInStackTrace() {
20 | setStackTrace(new StackTraceElement[0]);
21 | return this;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/deadlock-library/src/main/java/me/bytebeats/deadlock/DeadLockListener.java:
--------------------------------------------------------------------------------
1 | package me.bytebeats.deadlock;
2 |
3 | /**
4 | * Created by bytebeats on 2021/7/3 : 19:27
5 | * E-mail: happychinapc@gmail.com
6 | * Quote: Peasant. Educated. Worker
7 | */
8 | public interface DeadLockListener {
9 | void onError(DeadLockError error);
10 | }
11 |
--------------------------------------------------------------------------------
/deadlock-library/src/main/java/me/bytebeats/deadlock/DeadLockMonitor.java:
--------------------------------------------------------------------------------
1 | package me.bytebeats.deadlock;
2 |
3 | import android.os.Build.VERSION;
4 | import android.os.Build.VERSION_CODES;
5 | import android.util.Log;
6 |
7 | import java.lang.Thread.State;
8 | import java.util.ArrayList;
9 | import java.util.HashMap;
10 | import java.util.HashSet;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.Set;
14 |
15 | /**
16 | * Created by bytebeats on 2021/7/2 : 11:24
17 | * E-mail: happychinapc@gmail.com
18 | * Quote: Peasant. Educated. Worker
19 | */
20 |
21 | public class DeadLockMonitor {
22 |
23 | public static boolean logEnabled = true;
24 | private static final String TAG = "deadlock-java";
25 |
26 | static {
27 | System.loadLibrary("deadlock-lib");
28 | }
29 |
30 | private DeadLockListener listener;
31 |
32 | public DeadLockMonitor setDeadLockListener(DeadLockListener listener) {
33 | this.listener = listener;
34 | return this;
35 | }
36 |
37 |
38 | private static String threadName(Thread thread) {
39 | return thread.getName() + " (state = " + thread.getState().toString() + ")";
40 | }
41 |
42 | Thread[] allThreads() {
43 | ThreadGroup group = Thread.currentThread().getThreadGroup();
44 | if (group == null) {
45 | return null;
46 | }
47 | while (group.getParent() != null) {
48 | group = group.getParent();
49 | }
50 | int threadCount = group.activeCount();
51 | Thread[] allThreads = new Thread[threadCount];
52 | group.enumerate(allThreads);
53 | return allThreads;
54 | }
55 |
56 | public void start() {
57 | if (VERSION.SDK_INT > VERSION_CODES.Q) {
58 | log("DeadLockMonitor won't work on Android 10, Sooooooorrrrrryyyyyyyy");
59 | return;
60 | }
61 | int initResult = nativeInit(VERSION.SDK_INT);
62 | log("native init result: " + initResult);
63 | Map blockedThreads = new HashMap<>();
64 | Thread[] allThreads = allThreads();
65 | if (allThreads != null) {
66 | for (Thread thread : allThreads) {
67 | if (thread != null && thread.getState() == State.BLOCKED) {
68 | Object nativePeer = ReflectUtil.invokeField(thread, "nativePeer");
69 | if (nativePeer == null) continue;
70 | long thdAddr = (long) nativePeer;
71 | if (thdAddr <= 0) {
72 | log("thread address not found");
73 | continue;
74 | }
75 | log("blocked Thread[id = " + thread.getId() + ", address = " + thdAddr + "]");
76 | int blockThreadId = getContendedThreadIdArt(thdAddr);
77 | int currentThreadId = getThreadIdFromNativePeer(thdAddr);
78 | log("current thread id = " + currentThreadId + ", blocked thread id = " + blockThreadId);
79 | if (currentThreadId != 0 && blockThreadId != 0) {
80 | blockedThreads.put(currentThreadId, new DeadLockThreadWrapper(currentThreadId, blockThreadId, thread));
81 | }
82 | }
83 | }
84 | }
85 | List