Skip to content

Commit 89b22a5

Browse files
committed
frame counter example
1 parent 24d5084 commit 89b22a5

File tree

6 files changed

+134
-2
lines changed

6 files changed

+134
-2
lines changed

Diff for: examples/android-gradle-kts/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ androidMethodHook {
4848
addConfig("./methodhook/descriptor.conf")
4949
addConfig("./methodhook/okhttp.conf")
5050
addConfig("./methodhook/auto_trace.conf")
51+
addConfig("./methodhook/frame_counter.conf")
5152
}
5253
}
5354
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
frameCounterContext {
2+
type = "descriptor"
3+
package = "androidx.appcompat.app"
4+
superClass = "*"
5+
interfaces = []
6+
class = "androidx.appcompat.app.AppCompatActivity"
7+
methods = [ "attachBaseContext" ]
8+
descriptor = "(Landroid/content/Context;)V"
9+
enter = "io.github.aleksrychkov.methodhook.FrameCounterHook.attachBaseContext"
10+
}

Diff for: examples/android-gradle-kts/src/main/AndroidManifest.xml

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
<service android:name="io.github.aleksrychkov.example.MainService" />
2727

28-
<activity android:name="io.github.aleksrychkov.example2.MainActivity2" />
2928
</application>
3029

3130
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package io.github.aleksrychkov.example
2+
3+
import android.annotation.SuppressLint
4+
import android.app.Activity
5+
import android.app.Application
6+
import android.content.Context
7+
import android.os.Bundle
8+
import android.view.Choreographer
9+
import android.view.Gravity
10+
import android.view.View
11+
import android.view.ViewGroup
12+
import android.view.WindowManager
13+
import android.widget.FrameLayout
14+
import android.widget.TextView
15+
import androidx.core.view.ViewCompat
16+
import androidx.core.view.setPadding
17+
import java.lang.ref.WeakReference
18+
19+
object FrameCounter : Application.ActivityLifecycleCallbacks {
20+
21+
private val tvId = ViewCompat.generateViewId()
22+
private val callbacksMap = mutableMapOf<String, FrameCallback>()
23+
private var frameCounter: Int = 0
24+
25+
private fun attach(container: Activity) {
26+
val counterView = buildCounterView(container)
27+
val rootView = container.findViewById<ViewGroup>(android.R.id.content)
28+
if (rootView.findViewById<View?>(tvId) == null) {
29+
rootView.addView(counterView, 0)
30+
}
31+
FrameCallback(WeakReference(counterView)).also {
32+
val identityHash = Integer.toHexString(System.identityHashCode(container))
33+
callbacksMap[identityHash] = it
34+
Choreographer.getInstance().postFrameCallback(it)
35+
}
36+
}
37+
38+
private fun detach(container: Activity) {
39+
val identityHash = Integer.toHexString(System.identityHashCode(container))
40+
callbacksMap[identityHash]?.let { Choreographer.getInstance().removeFrameCallback(it) }
41+
callbacksMap.remove(identityHash)
42+
val rootView = container.findViewById<ViewGroup>(android.R.id.content)
43+
val counterView = rootView.findViewById<View?>(tvId)
44+
rootView.removeView(counterView)
45+
}
46+
47+
private fun buildCounterView(context: Context): TextView =
48+
TextView(context).apply {
49+
layoutParams = FrameLayout.LayoutParams(
50+
WindowManager.LayoutParams.WRAP_CONTENT,
51+
WindowManager.LayoutParams.WRAP_CONTENT,
52+
).apply {
53+
gravity = Gravity.TOP or Gravity.END
54+
setPadding(10)
55+
}
56+
setBackgroundColor(0x88000000.toInt())
57+
setTextColor(0xFFFFFFFF.toInt())
58+
textSize = 14f
59+
id = tvId
60+
}
61+
62+
private class FrameCallback(
63+
private val tvCounter: WeakReference<TextView>,
64+
) : Choreographer.FrameCallback {
65+
66+
@SuppressLint("SetTextI18n")
67+
override fun doFrame(frameTimeNanos: Long) {
68+
val counterView = tvCounter.get() ?: return
69+
frameCounter++
70+
counterView.text = "Frame# $frameCounter"
71+
logTrace()
72+
Choreographer.getInstance().postFrameCallback(this)
73+
}
74+
75+
private fun logTrace() {
76+
android.os.Trace.beginSection("=>Frame# $frameCounter")
77+
android.os.Trace.endSection()
78+
}
79+
}
80+
81+
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
82+
}
83+
84+
override fun onActivityStarted(activity: Activity) {
85+
}
86+
87+
override fun onActivityResumed(activity: Activity) {
88+
attach(activity)
89+
}
90+
91+
override fun onActivityPaused(activity: Activity) {
92+
detach(activity)
93+
}
94+
95+
override fun onActivityStopped(activity: Activity) {
96+
}
97+
98+
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
99+
}
100+
101+
override fun onActivityDestroyed(activity: Activity) {
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.github.aleksrychkov.methodhook
2+
3+
import android.app.Application
4+
import android.content.Context
5+
import io.github.aleksrychkov.example.FrameCounter
6+
7+
@Suppress("unused")
8+
object FrameCounterHook {
9+
10+
private var isRegistered = false
11+
12+
@JvmStatic
13+
fun attachBaseContext(context: Context) {
14+
if (isRegistered) return
15+
isRegistered = true
16+
(context.applicationContext as Application).registerActivityLifecycleCallbacks(FrameCounter)
17+
}
18+
}

Diff for: methodhook/src/main/kotlin/io/github/aleksrychkov/methodhook/injects/InjectorContext.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ package io.github.aleksrychkov.methodhook.injects
55
*
66
* @property className The name of the class that contains the method being injected.
77
* @property methodName The name of the method being injected.
8-
* @property methodDescriptor The descriptor of the method being injected, providing information about the method's parameter and return types.
8+
* @property methodDescriptor The descriptor of the method being injected,
9+
* providing information about the method's parameter and return types.
910
* @property access The access modifiers of the method, represented as an integer (e.g., public, private).
1011
*/
1112
class InjectorContext(

0 commit comments

Comments
 (0)