-
Notifications
You must be signed in to change notification settings - Fork 15
Add auto start instrumentation #2098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package io.embrace.android.embracesdk; | ||
|
||
import androidx.annotation.NonNull; | ||
|
||
import io.embrace.android.embracesdk.annotation.InternalApi; | ||
import io.embrace.android.embracesdk.internal.EmbraceInternalApi; | ||
|
||
/** | ||
* @hide | ||
*/ | ||
@InternalApi | ||
public final class AutoStartInstrumentationHook { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why Java? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't really instrumentation, but starting the SDK, so maybe call it And technically this functionality is just wrapping SDK start, and the "auto" part is the result of doing the bytecode instrumentation. Maybe you can even call it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's probably worth looking at the conventions I've adopted on this integration branch for some of the bytecode instrumentation. Once 7.3.0 is out I'll merge that down: #2103 |
||
|
||
private AutoStartInstrumentationHook() { | ||
} | ||
|
||
public static void _preOnCreate(android.app.Application application) { | ||
try { | ||
Embrace.getInstance().start(application); | ||
} catch (Exception exception) { | ||
logError(exception); | ||
} | ||
} | ||
|
||
private static void logError(@NonNull Throwable throwable) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't do anything if the SDK isn't started so IMO could be deleted, and we just swallow any unexpected exception |
||
EmbraceInternalApi.getInstance().getInternalInterface().logInternalError(throwable); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package io.embrace.test.fixtures | ||
|
||
import android.app.Application | ||
|
||
class TestApplication : Application() { | ||
override fun onCreate() { | ||
super.onCreate() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// class version 55.0 (55) | ||
// access flags 0x31 | ||
public final class io/embrace/test/fixtures/TestApplication extends android/app/Application { | ||
|
||
// compiled from: TestApplication.kt | ||
|
||
@Lkotlin/Metadata;(mv={1, 8, 0}, k=1, xi=48, d1={"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0008\u0002\n\u0002\u0010\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002J\u0008\u0010\u0003\u001a\u00020\u0004H\u0016\u00a8\u0006\u0005"}, d2={"Lio/embrace/test/fixtures/TestApplication;", "Landroid/app/Application;", "()V", "onCreate", "", "embrace-bytecode-instrumentation-tests_release"}) | ||
|
||
// access flags 0x1 | ||
public <init>()V | ||
L0 | ||
LINENUMBER 5 L0 | ||
ALOAD 0 | ||
INVOKESPECIAL android/app/Application.<init> ()V | ||
RETURN | ||
L1 | ||
LOCALVARIABLE this Lio/embrace/test/fixtures/TestApplication; L0 L1 0 | ||
MAXSTACK = 1 | ||
MAXLOCALS = 1 | ||
|
||
// access flags 0x1 | ||
public onCreate()V | ||
ALOAD 0 | ||
INVOKESTATIC io/embrace/android/embracesdk/AutoStartInstrumentationHook._preOnCreate (Landroid/app/Application;)V | ||
L0 | ||
LINENUMBER 7 L0 | ||
ALOAD 0 | ||
INVOKESPECIAL android/app/Application.onCreate ()V | ||
L1 | ||
LINENUMBER 8 L1 | ||
RETURN | ||
L2 | ||
LOCALVARIABLE this Lio/embrace/test/fixtures/TestApplication; L0 L2 0 | ||
MAXSTACK = 1 | ||
MAXLOCALS = 1 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.example.app | ||
|
||
import android.app.Application | ||
|
||
class ApplicationFixture : Application() { | ||
override fun onCreate() { | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,11 @@ abstract class EmbraceBytecodeInstrumentation @Inject internal constructor(objec | |
val firebasePushNotificationsEnabled: Property<Boolean> = | ||
objectFactory.property(Boolean::class.java) | ||
|
||
/** | ||
* Whether Embrace should automatically start in the Application class. Defaults to true. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Defaulting this to true feels like a breaking change as folks will already have setup their own place where Embrace is initialized, which will probably be in a different location to where bytecode instrumentation assumes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably default to false initially until we do a major version bump. But whether we ever flip it to true is probably something product should decide. Having the "no code start" opt-in-able via a gradle property already seems like a big win. |
||
*/ | ||
val autoStartEnabled: Property<Boolean> = objectFactory.property(Boolean::class.java) | ||
|
||
/** | ||
* A list of string patterns that are used to filter classes during bytecode instrumentation. For example, 'com.example.foo.*' | ||
* would avoid instrumenting any classes in the 'com.example.foo' package. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -106,7 +106,13 @@ data class SdkLocalConfig( | |
val appExitInfoConfig: AppExitInfoLocalConfig? = null, | ||
|
||
@Json(name = "app_framework") | ||
val appFramework: String? = null | ||
val appFramework: String? = null, | ||
|
||
/** | ||
* Whether auto-start instrumentation should be enabled or not | ||
*/ | ||
@Json(name = "auto_start") | ||
val autoStart: Boolean? = null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this required as well as a property on the extension? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I would just go with property so there isn't two sources of truth. Going with the property also allows customers to use the SDK without a config file, which will make trying it out easier. |
||
) : Serializable { | ||
|
||
private companion object { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package io.embrace.android.gradle.plugin.instrumentation.visitor | ||
|
||
import com.android.build.api.instrumentation.ClassContext | ||
import org.objectweb.asm.ClassVisitor | ||
import org.objectweb.asm.MethodVisitor | ||
|
||
/** | ||
* Visits the Application class and returns an [ApplicationMethodAdapter] for the onCreate method. | ||
*/ | ||
class ApplicationClassAdapter( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This current implementation only works if an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another approach would be to stick this in a content provider. But yeah I'd just go with this first and document it - this seems much less intrusive and likely covers most production cases anyway. |
||
api: Int, | ||
nextClassVisitor: ClassVisitor?, | ||
) : ClassVisitor(api, nextClassVisitor) { | ||
|
||
companion object : ClassVisitFilter { | ||
private const val METHOD_NAME = "onCreate" | ||
private const val METHOD_DESC = "()V" | ||
|
||
override fun accept(classContext: ClassContext) = true | ||
} | ||
|
||
override fun visitMethod( | ||
access: Int, | ||
name: String, | ||
desc: String, | ||
signature: String?, | ||
exceptions: Array<String>? | ||
): MethodVisitor? { | ||
val nextMethodVisitor = super.visitMethod(access, name, desc, signature, exceptions) | ||
|
||
return if (METHOD_NAME == name && METHOD_DESC == desc) { | ||
ApplicationMethodAdapter(api, nextMethodVisitor) | ||
} else { | ||
nextMethodVisitor | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package io.embrace.android.gradle.plugin.instrumentation.visitor | ||
|
||
import org.objectweb.asm.MethodVisitor | ||
import org.objectweb.asm.Opcodes | ||
|
||
/** | ||
* Visits the onCreate method and inserts a call to AutoStartInstrumentationHook._preOnCreate at the very start | ||
* of the method. This ensures that Embrace is started before any other initialization code runs. | ||
*/ | ||
internal class ApplicationMethodAdapter( | ||
api: Int, | ||
methodVisitor: MethodVisitor? | ||
) : MethodVisitor(api, methodVisitor) { | ||
|
||
override fun visitCode() { | ||
// invoke AutoStartInstrumentationHook$Application._preOnCreate() | ||
visitVarInsn(Opcodes.ALOAD, 0) // load 'this' onto the stack | ||
visitMethodInsn( | ||
Opcodes.INVOKESTATIC, | ||
"io/embrace/android/embracesdk/AutoStartInstrumentationHook", | ||
"_preOnCreate", | ||
"(Landroid/app/Application;)V", | ||
false | ||
) | ||
|
||
// call super last to reduce chance of interference with other bytecode instrumentation | ||
super.visitCode() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally this would be under an
internal
package (but have public visibility) and the function would not contain an underscore