Milan Jurkulak 3 lat temu
rodzic
commit
3391484056

+ 2 - 0
tvlib/build.gradle.kts

@@ -158,6 +158,8 @@ dependencies {
     implementation("com.airbnb.android:lottie-compose:6.1.0")
     // exif info
     implementation("androidx.exifinterface:exifinterface:1.3.6")
+    // anr
+    implementation("com.github.anrwatchdog:anrwatchdog:1.4.0")
     // constraints
     constraints {
         implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0")?.apply {

+ 9 - 37
tvlib/src/main/kotlin/org/mjdev/tvlib/activity/ComposableActivity.kt

@@ -28,14 +28,13 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.MaterialTheme
 import androidx.tv.material3.NonInteractiveSurfaceDefaults
 import androidx.tv.material3.Surface
+import com.github.anrwatchdog.ANRWatchDog
+import com.google.ads.interactivemedia.v3.internal.anr
 import org.mjdev.tvlib.BuildConfig
 import org.mjdev.tvlib.annotations.TvPreview
 import org.mjdev.tvlib.extensions.ContextExt.isATv
 import org.mjdev.tvlib.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvlib.extensions.NavGraphBuilderExt.screen
-import org.mjdev.tvlib.helpers.anr.agent.ANRSpyAgent
-import org.mjdev.tvlib.helpers.anr.agent.ANRSpyListener
-import org.mjdev.tvlib.helpers.anr.models.MethodModel
 import org.mjdev.tvlib.navigation.NavGraphBuilderEx
 import org.mjdev.tvlib.navigation.NavHostControllerEx
 import org.mjdev.tvlib.ui.components.navigation.NavHostEx
@@ -46,47 +45,23 @@ import timber.log.Timber
 @Suppress("MemberVisibilityCanBePrivate", "unused", "LeakingThis")
 open class ComposableActivity : ComponentActivity() {
 
+    @Suppress("PropertyName")
+    val ANR_TIMEOUT = 5000
+
     val activityResultListeners = mutableListOf<ActivityResultHandler<*>>()
 
     open val navGraphBuilder: NavGraphBuilderEx.() -> Unit = {
         screen(route = EmptyScreen())
     }
 
-    private var anrCallback = object : ANRSpyListener {
-        override fun onWait(ms: Long) {
-        }
-
-        @Deprecated("This callback will be removed in future")
-        override fun onAnrStackTrace(stackstrace: Array<StackTraceElement>) {
-        }
-
-        override fun onReportAvailable(methodList: List<MethodModel>) {
-        }
-
-        override fun onAnrDetected(
-            details: String,
-            stackTrace: Array<StackTraceElement>,
-            packageMethods: List<String>?
-        ) {
-            Timber.e(details)
-            Timber.e(stackTrace.toString())
-            Timber.e(packageMethods.toString())
-        }
-    }
-
-    private val anrSpyAgent = ANRSpyAgent.Builder(this)
-        .setTimeOut(500)
-        .setSpyListener(anrCallback)
-        .setThrowException(false)
-        .enableReportAnnotatedMethods(true)
-        .build()
-
     open val backgroundColor = Color(0xff202020)
     open val roundCornerSize: Dp = 0.dp
     open val backgroundShape: Shape = RoundedCornerShape(roundCornerSize)
 
     fun reportANR() {
-        anrSpyAgent.start()
+        ANRWatchDog(ANR_TIMEOUT).setANRListener { anr ->
+            anr.printStackTrace()
+        }.start()
     }
 
     @TvPreview
@@ -94,9 +69,7 @@ open class ComposableActivity : ComponentActivity() {
     @Composable
     @CallSuper
     open fun Compose() {
-
         val navController = rememberNavControllerEx()
-
         MaterialTheme {
             Surface(
                 modifier = Modifier
@@ -114,15 +87,14 @@ open class ComposableActivity : ComponentActivity() {
                 )
             }
         }
-
         onIntent(navController, intent)
-
     }
 
     open fun onIntent(navController: NavHostControllerEx, intent: Intent?) {
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
+        Timber.d("Activity ${this::class.simpleName} created.")
         super.onCreate(savedInstanceState)
         if (BuildConfig.DEBUG) {
             reportANR()

+ 0 - 217
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/anr/agent/ANRSpyAgent.kt

@@ -1,217 +0,0 @@
-@file:Suppress("PrivatePropertyName", "LocalVariableName")
-
-package org.mjdev.tvlib.helpers.anr.agent
-
-import android.content.Context
-import android.os.Build
-import android.os.Handler
-import android.os.Looper
-import android.os.MessageQueue.IdleHandler
-import org.mjdev.tvlib.helpers.anr.annotations.TraceClass
-import org.mjdev.tvlib.helpers.anr.annotations.TraceMethod
-import org.mjdev.tvlib.helpers.anr.models.MethodModel
-import timber.log.Timber
-
-class ANRSpyAgent constructor(
-    builder: Builder
-) : Thread() {
-
-    private var mContext: Context
-    private var mListener: ANRSpyListener? = null
-    private var mShouldThrowException: Boolean = false
-    private var TIME_OUT = 5000L
-    private var mReportAnnotatedMethods: Boolean = false
-    private var mListAnnotatedMedhods = mutableListOf<String>()
-    private var mReportMethods = mutableListOf<MethodModel>()
-    private val INTERVAL = 500L
-    private val mHandler = Handler(Looper.getMainLooper())
-    private var _timeWaited = 0L
-    private val _mTesterWorker = Runnable {
-        _timeWaited = 0L
-    }
-    private val mIdleHandler = IdleHandler {
-        mListener?.onReportAvailable(mReportMethods)
-        mReportMethods = mutableListOf()
-        true
-    }
-
-    init {
-        this.mListener = builder.getSpyListener()
-        this.mShouldThrowException = builder.getThrowException()
-        this.TIME_OUT = builder.getTimeOout()
-        this.mReportAnnotatedMethods = builder.getReportAnnotatedMethods()
-        this.mContext = builder.mContext
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            Looper.getMainLooper().queue.addIdleHandler(mIdleHandler)
-        } else {
-            Timber.e("Can not load queue handler.")
-            // todo iddle handler
-        }
-    }
-
-    class Builder(var mContext: Context) {
-
-        private var mListener: ANRSpyListener? = null
-        private var mShouldThrowException: Boolean = false
-        private var TIME_OUT = 5000L
-        private var mEnableInstantReport: Boolean = false
-
-        fun setSpyListener(listener: ANRSpyListener) = apply { this.mListener = listener }
-
-        fun getSpyListener() = this.mListener
-
-        fun setThrowException(throwexception: Boolean) = apply {
-            this.mShouldThrowException = throwexception
-        }
-
-        fun getThrowException() = this.mShouldThrowException
-
-        fun setTimeOut(timeout: Long) = apply { TIME_OUT = timeout }
-
-        fun getTimeOout() = TIME_OUT
-
-        fun enableReportAnnotatedMethods(enable: Boolean) = apply { mEnableInstantReport = enable }
-
-        fun getReportAnnotatedMethods() = this.mEnableInstantReport
-
-        fun build(): ANRSpyAgent = ANRSpyAgent(this)
-
-    }
-
-    @Suppress("UNUSED_VARIABLE")
-    override fun run() {
-        while (!isInterrupted) {
-            val stacktrace = Looper.getMainLooper().thread.stackTrace
-            isResumeExists(stacktrace)
-            _timeWaited += INTERVAL
-            mListener?.onWait(_timeWaited)
-            mHandler.post(_mTesterWorker)
-            sleep(INTERVAL)
-            if (_timeWaited > TIME_OUT) {
-                val listPackageMethods = findPackagMethods(stacktrace)
-                mListener?.onAnrDetected(
-                    "$THREAD_TITLE Main thread blocked for: $_timeWaited ms",
-                    stackTrace,
-                    findPackagMethods(stacktrace)
-                )
-                if (mShouldThrowException) {
-                    throwException(Looper.getMainLooper().thread.stackTrace)
-                }
-            }
-            if (mReportAnnotatedMethods) {
-                instantReport()
-            }
-        }
-    }
-
-    private fun isResumeExists(stacktrace: Array<StackTraceElement>) {
-        val allstacks = getAllStackTraces()
-        Timber.w("Threads------------")
-        for (entity in allstacks) {
-            Timber.e("${entity.key}")
-        }
-        Timber.w("Threads------------\nn")
-        Timber.w("Threads------------\n")
-        for (item in stacktrace) {
-            Timber.e(item.toString())
-        }
-        Timber.w("Threads------------" + "\n")
-    }
-
-    private fun throwException(stackTrace: Array<StackTraceElement>) {
-        throw ANRSpyException(THREAD_TITLE, stackTrace)
-    }
-
-    private fun instantReport() {
-        val stacktrace = Looper.getMainLooper().thread.stackTrace
-        for (element in stacktrace) {
-            addAnnotatedMethods(element.className)
-            val methodexists = mListAnnotatedMedhods.find {
-                element.methodName.lowercase().startsWith(it.lowercase())
-            }
-            if (methodexists != null) {
-                addMethod(methodexists, Looper.getMainLooper().thread)
-                return
-            }
-        }
-    }
-
-    @Synchronized
-    fun addMethod(methodName: String, thread: Thread) {
-        val exists = mReportMethods.find {
-            (it.name.lowercase() == methodName.lowercase())
-        }
-        if (exists != null) {
-            for (item in mReportMethods) {
-                if (item.name.lowercase() == exists.name.lowercase()) {
-                    item.elapsedTime += INTERVAL
-                    return
-                }
-            }
-        } else {
-            mReportMethods.add(
-                MethodModel(
-                    System.currentTimeMillis(), methodName, thread, 0
-                )
-            )
-        }
-    }
-
-    private fun addAnnotatedMethods(className: String) {
-        try {
-            val clazz = Class.forName(className)
-            val annotation = clazz.getAnnotation(TraceClass::class.java)
-            annotation?.let { tc ->
-                if (tc.traceAllMethods) {
-                    for (m in clazz.declaredMethods) {
-                        val exists = mListAnnotatedMedhods.find { s ->
-                            s.lowercase() == m.name.lowercase()
-                        }
-                        if (exists == null) {
-                            mListAnnotatedMedhods.add(m.name)
-                        }
-                    }
-                } else {
-                    for (m in clazz.declaredMethods) {
-                        m.getAnnotation(TraceMethod::class.java).let { ann ->
-                            ann?.let {
-                                val exists = mListAnnotatedMedhods.find {
-                                    it.lowercase() == m.name.lowercase()
-                                }
-                                if (exists == null) {
-                                    mListAnnotatedMedhods.add(m.name)
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        } catch (ex: Exception) {
-            mListener?.onError(ex.message.toString())
-        }
-    }
-
-    private fun findPackagMethods(stacktrace: Array<StackTraceElement>): List<String>? {
-        val list = mutableListOf<String>()
-        val seq_strack = stacktrace.toList()
-        val filtered = seq_strack.filter {
-            it.className.lowercase().contains(mContext.packageName.lowercase())
-        }
-        if (filtered.isNotEmpty()) {
-            filtered.forEach { st ->
-                val body =
-                    "Class: ${st.className} Method: ${st.methodName} LineNumber<${st.lineNumber}>(${st.fileName})"
-                list.add(body)
-            }
-            return list
-        }
-        return null
-    }
-
-    companion object {
-
-        const val THREAD_TITLE = "[ ++ ANR Spy ++ ]"
-
-    }
-
-}

+ 0 - 9
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/anr/agent/ANRSpyException.kt

@@ -1,9 +0,0 @@
-package org.mjdev.tvlib.helpers.anr.agent
-
-class ANRSpyException(title:String, stacktrace:Array<StackTraceElement>):Throwable(title) {
-
-    init {
-        stackTrace = stacktrace
-    }
-
-}

+ 0 - 24
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/anr/agent/ANRSpyListener.kt

@@ -1,24 +0,0 @@
-package org.mjdev.tvlib.helpers.anr.agent
-
-import org.mjdev.tvlib.helpers.anr.models.MethodModel
-
-interface ANRSpyListener {
-
-    fun onWait(ms: Long) {}
-
-    @Deprecated("This callback will be removed in future")
-    fun onAnrStackTrace(stackstrace: Array<StackTraceElement>) {
-    }
-
-    fun onAnrDetected(
-        details: String,
-        stackTrace: Array<StackTraceElement>,
-        packageMethods: List<String>?
-    ) {
-    }
-
-    fun onReportAvailable(methodList: List<MethodModel>) {}
-
-    fun onError(error: String) {}
-
-}

+ 0 - 52
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/anr/agent/SpyLooper.kt

@@ -1,52 +0,0 @@
-package org.mjdev.tvlib.helpers.anr.agent
-
-import android.annotation.SuppressLint
-import android.os.Looper
-import android.os.Message
-import android.os.MessageQueue
-import timber.log.Timber
-import java.lang.reflect.Field
-
-@SuppressLint("PrivateApi")
-class SpyLooper {
-
-    var messagesField: Field
-    var nextField: Field
-    var mainMessageQueue: MessageQueue
-
-    init {
-        try {
-            val queueField = Looper::class.java.getDeclaredField("mQueue")
-            queueField.isAccessible = true
-            messagesField = MessageQueue::class.java.getDeclaredField("mMessages")
-            messagesField.isAccessible = true
-            nextField = Message::class.java.getDeclaredField("next")
-            nextField.isAccessible = true
-            val mainLooper = Looper.getMainLooper()
-            mainMessageQueue = queueField[mainLooper] as MessageQueue
-        } catch (e: Exception) {
-            throw RuntimeException(e)
-        }
-    }
-
-    fun dumpQueue() {
-        try {
-            val nextMessage = messagesField[mainMessageQueue] as Message
-            Timber.d("Begin dumping queue")
-            dumpMessages(nextMessage)
-            Timber.d("End dumping queue")
-        } catch (e: IllegalAccessException) {
-            throw RuntimeException(e)
-        }
-    }
-
-    @Throws(IllegalAccessException::class)
-    fun dumpMessages(message: Message?) {
-        message?.let {
-            Timber.w("Message: $message")
-            val next = nextField[message] as Message?
-            dumpMessages(next)
-        }
-    }
-
-}

+ 0 - 7
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/anr/annotations/TraceClass.kt

@@ -1,7 +0,0 @@
-package org.mjdev.tvlib.helpers.anr.annotations
-
-@Target(AnnotationTarget.CLASS)
-@Retention(AnnotationRetention.RUNTIME)
-annotation class TraceClass(
-    val traceAllMethods:Boolean = true
-)

+ 0 - 5
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/anr/annotations/TraceMethod.kt

@@ -1,5 +0,0 @@
-package org.mjdev.tvlib.helpers.anr.annotations
-
-@Target(AnnotationTarget.FUNCTION)
-@Retention(AnnotationRetention.RUNTIME)
-annotation class TraceMethod()

+ 0 - 11
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/anr/models/MethodModel.kt

@@ -1,11 +0,0 @@
-package org.mjdev.tvlib.helpers.anr.models
-
-import androidx.annotation.Keep
-
-@Keep
-data class MethodModel(
-    val id: Long,
-    val name: String,
-    val thread: Thread,
-    var elapsedTime: Long
-)