温馨提示:这篇文章已超过483天没有更新,请注意相关的内容是否还可用!
摘要:,,本文解析了Sentry(Android)源码,介绍了其架构、模块和功能。通过对源码的深入研究,揭示了Sentry在Android平台上的工作原理、核心组件以及性能优化等方面。文章旨在帮助开发者更好地理解Sentry源码,从而更有效地进行开发、调试和性能优化。
本文字数:16030字
预计阅读时间:40分钟
01
前言
Sentry是一个日志记录、错误上报、性能监控的开源框架,支持众多平台:

其使用方式在本文不进行说明了,大家可参照官方文档:https://docs.sentry.io/platforms/android/?original_referrer=https%3A%2F%2Fsentry.io%2F
目前大部分免费的三方APM平台限制较多,好用的又收费。在降本增效的大环境下,免费开源是开发者们的目标。因此开源的Sentry平台,在基础能力上已经满足了绝大多数开发场景。而对于想深入定制,打造自己APM平台的同学们来说,Sentry也是个可以依托并以此为基础进行改造的捷径。
本文将对Sentry Android SDK(6.31.0)进行源码解析,给大家一些改造拓展的思路。
02
基础结构说明
在讲解Sentry之前,先介绍一些基础的结构,方便之后的理解。
2.1.SentryEvent和SentryTransaction
先介绍两个基本概念,即SentryEvent和SentryTransaction。它们俩就是Sentry支持的事件,继承自同一个父类:SentryBaseEvent。简单来说其中我们在后台看到的Issues就是SentryEvent,Performance就是SentryTransaction。一次发送一个事件,每个SentryBaseEvent都有唯一的eventId。
2.2.Scope
Scope是保存与event一起发送的有用信息。如context,breadCrumb等。当设置了Scope里的某个属性,那么在整个Scope中都会将此属性赋值给event。
2.3.SentryClient
SentryClient是客户端用来真正处理各种事件发送逻辑的。比方说我们调用captureEvent(),最终的实现就是在SentryClient里。
2.4.Hub
Hub是用来管理Scope和SentryClient的。在Sentry初始化时,会创建Hub对象,Hub创建Scope和SentryClient并进行管理。了解了这些之后,我们来看源码。
03
初始化
在详细梳理初始化流程之前,我们把关键步骤梳理成图方便大家理解:
接下来我们分析具体的实现。
3.1.SentryAndroid.init()
我们先从初始化开始分析。SentryAndroid.java位于sentry-android-core这个包内。
从类的结构来看我们发现,SentryAndroid实际上只做了初始化这个操作:
//SentryAndroid.java //Sentry initialization with a configuration handler and custom logger //Params: //context – Application. context //logger – your custom logger that implements ILogger //configuration – Sentry.OptionsConfiguration configuration handler public static synchronized void init( @NotNull final Context context, @NotNull ILogger logger, @NotNull Sentry.OptionsConfiguration configuration) { // if SentryPerformanceProvider was disabled or removed, we set the App Start when // the SDK is called. AppStartState.getInstance().setAppStartTime(appStart, appStartTime); try { Sentry.init( OptionsContainer.create(SentryAndroidOptions.class), options -> { final LoadClass classLoader = new LoadClass(); final boolean isTimberUpstreamAvailable = classLoader.isClassAvailable(TIMBER_CLASS_NAME, options); final boolean isFragmentUpstreamAvailable = classLoader.isClassAvailable(FRAGMENT_CLASS_NAME, options); final boolean isFragmentAvailable = (isFragmentUpstreamAvailable && classLoader.isClassAvailable( SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME, options)); final boolean isTimberAvailable = (isTimberUpstreamAvailable && classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options)); final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger); final LoadClass loadClass = new LoadClass(); final ActivityFramesTracker activityFramesTracker = new ActivityFramesTracker(loadClass, options); AndroidOptionsInitializer.loadDefaultAndMetadataOptions( options, context, logger, buildInfoProvider); // We install the default integrations before the option configuration, so that the user // can remove any of them. Integrations will not evaluate the options immediately, but // will use them later, after being configured. AndroidOptionsInitializer.installDefaultIntegrations( context, options, buildInfoProvider, loadClass, activityFramesTracker, isFragmentAvailable, isTimberAvailable); configuration.configure(options); AndroidOptionsInitializer.initializeIntegrationsAndProcessors( options, context, buildInfoProvider, loadClass, activityFramesTracker); deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable); }, true); final @NotNull IHub hub = Sentry.getCurrentHub(); if (hub.getOptions().isEnableAutoSessionTracking() && ContextUtils.isForegroundImportance(context)) { hub.addBreadcrumb(BreadcrumbFactory.forSession("session.start")); hub.startSession(); } } catch (IllegalAccessException e) { //.. } catch (InstantiationException e) { //... } catch (NoSuchMethodException e) { //... } catch (InvocationTargetException e) { //... } }
我们看到在执行Sentry.init()之前先执行了:
AppStartState.getInstance().setAppStartTime(appStart, appStartTime);
synchronized void setAppStartTime( final long appStartMillis, final @NotNull SentryDate appStartTime) { // method is synchronized because the SDK may by init. on a background thread. if (this.appStartTime != null && this.appStartMillis != null) { return; } this.appStartTime = appStartTime; this.appStartMillis = appStartMillis; }
记录了appStartTime和appStartMillis。从类型上来看,一个是日期,一个是时间戳。我们看一下这两个变量的获取规则:
appStartMillis:
//SentryAndroid.java // SystemClock.uptimeMillis() isn't affected by phone provider or clock changes. private static final long appStart = SystemClock.uptimeMillis();
记录了自开机以来的运行时间(毫秒级)。
appStartTime:
//SentryAndroid.java // static to rely on Class load init. private static final @NotNull SentryDate appStartTime = AndroidDateUtils.getCurrentSentryDateTime();
//AndroidDateUtils.java public final class AndroidDateUtils { private static final SentryDateProvider dateProvider = new SentryAndroidDateProvider(); public static @NotNull SentryDate getCurrentSentryDateTime() { return dateProvider.now(); } }
//SentryNanotimeDateProvider.java public final class SentryNanotimeDateProvider implements SentryDateProvider { @Override public SentryDate now() { return new SentryNanotimeDate(); } }
//SentryNanotimeDate.java private final @NotNull Date date; private final long nanos; public SentryNanotimeDate() { this(DateUtils.getCurrentDateTime(), System.nanoTime()); }
//DateUtils.java public static @NotNull Date getCurrentDateTime() { final Calendar calendar = Calendar.getInstance(TIMEZONE_UTC); return calendar.getTime(); }
到这里,我们可以看到:appStartTime记录了当前时区的日期,和当前的高精度时间戳(精确到纳秒级)。之后SentryAndroid主要执行了Sentry.init()方法。
我们继续分析Sentry.init()的实现。
3.2.Sentry.init()
Sentry.java位于sentry-6.31.0这个包下:
我们先来看看Sentry.init()的实现:
//Sentry.java public static void init( final @NotNull OptionsContainer clazz, final @NotNull OptionsConfiguration optionsConfiguration, final boolean globalHubMode) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { final T options = clazz.createInstance(); applyOptionsConfiguration(optionsConfiguration, options); init(options, globalHubMode); }
首先三个参数,类型分别是OptionsContainer,OptionsConfiguration和boolean。其中最后一个参数globalHubMode传的是true。然后调用applyOptionsConfiguration(),最后再执行init()方法。我们再来看看头两个参数是如何定义的。
3.2.1 final @NotNull OptionsContaineclazz:**
//SentryAndroid.java OptionsContainer.create(SentryAndroidOptions.class)
//OptionsContainer.java public final class OptionsContainer { public @NotNull static OptionsContainer create(final @NotNull Class clazz) { return new OptionsContainer(clazz); } private final @NotNull Class clazz; private OptionsContainer(final @NotNull Class clazz) { super(); this.clazz = clazz; } public @NotNull T createInstance() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { return clazz.getDeclaredConstructor().newInstance(); } }
OptionsContainer.create()传的是SentryAndroidOptions这个class,返回的是OptionsContainer。在Sentry.java中调用clazz.createInstance()方法执行了SentryAndroidOptions的构造方法:
//SentryAndroidOptions.java public SentryAndroidOptions() { setSentryClientName(BuildConfig.SENTRY_ANDROID_SDK_NAME + "/" + BuildConfig.VERSION_NAME); setSdkVersion(createSdkVersion()); setAttachServerName(false); // enable scope sync for Android by default setEnableScopeSync(true); }
我们看做了一些Android相关的基础配置。SentryAndroidOptions的父类OptionsContainer的构造方法如下:
//SentryOptions.java private SentryOptions(final boolean empty) { if (!empty) { // SentryExecutorService should be initialized before any // SendCachedEventFireAndForgetIntegration executorService = new SentryExecutorService(); // UncaughtExceptionHandlerIntegration should be inited before any other Integration. // if there's an error on the setup, we are able to capture it integrations.add(new UncaughtExceptionHandlerIntegration()); integrations.add(new ShutdownHookIntegration()); eventProcessors.add(new MainEventProcessor(this)); eventProcessors.add(new DuplicateEventDetectionEventProcessor(this)); if (Platform.isJvm()) { eventProcessors.add(new SentryRuntimeEventProcessor()); } setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME); setSdkVersion(createSdkVersion()); addPackageInfo(); } }
3.2.1.1.SentryExecutorService
首先初始化了一个SentryExecutorService:
//SentryExecutorService.java SentryExecutorService() { this(Executors.newSingleThreadScheduledExecutor(new SentryExecutorServiceThreadFactory())); }
//SentryExecutorService.java @Override public @NotNull Future submit(final @NotNull Runnable runnable) { return executorService.submit(runnable); }
这个service开启了一个新线程执行了submit()方法,我们追踪一下代码发现这个方法有多处调用,最主要的是SendCachedEnvelopeFireAndForgetIntegration调用了,而SendCachedEnvelopeFireAndForgetIntegration的作用是在App启动的时候发送在cache中的event用的。
我们继续看SentryOptions.java的构造方法,发现会为integrations列表添加各种Integration,我们看一个最常见的UncaughtExceptionHandlerIntegration来分析,从命名上来看这个Integration就是用来抓抛出来的异常用的。
3.2.1.2. Integration
//UncaughtExceptionHandlerIntegration.java public UncaughtExceptionHandlerIntegration() { this(UncaughtExceptionHandler.Adapter.getInstance()); } UncaughtExceptionHandlerIntegration(final @NotNull UncaughtExceptionHandler threadAdapter) { this.threadAdapter = Objects.requireNonNull(threadAdapter, "threadAdapter is required."); }
//UncaughtExceptionHandler.java interface UncaughtExceptionHandler { Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(); void setDefaultUncaughtExceptionHandler(@Nullable Thread.UncaughtExceptionHandler handler); final class Adapter implements UncaughtExceptionHandler { static UncaughtExceptionHandler getInstance() { return Adapter.INSTANCE; } private static final Adapter INSTANCE = new Adapter(); private Adapter() {} @Override public Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() { return Thread.getDefaultUncaughtExceptionHandler(); } @Override public void setDefaultUncaughtExceptionHandler( final @Nullable Thread.UncaughtExceptionHandler handler) { Thread.setDefaultUncaughtExceptionHandler(handler); } } }
其中Adapter实现了UncaughtExceptionHandler接口。我们回到UncaughtExceptionHandlerIntegration.java,它实现了Integration和Thread.UncaughtExceptionHandler接口,其中Integration的定义如下:
//Integration.java
public interface Integration extends IntegrationName {
/**
* Registers an integration
*
* @param hub the Hub
* @param options the options
*/
void register(@NotNull IHub hub, @NotNull SentryOptions options);
}
只有一个register()方法,我们回到UncaughtExceptionHandlerIntegration,看一下它的结构:
先看一下register()都做了什么:
//UncaughtExceptionHandlerIntegration.java @Override public final void register(final @NotNull IHub hub, final @NotNull SentryOptions options) { if (registered) { //... return; } registered = true; this.hub = Objects.requireNonNull(hub, "Hub is required"); this.options = Objects.requireNonNull(options, "SentryOptions is required"); //... if (this.options.isEnableUncaughtExceptionHandler()) { final Thread.UncaughtExceptionHandler currentHandler = threadAdapter.getDefaultUncaughtExceptionHandler(); if (currentHandler != null) { //... defaultExceptionHandler = currentHandler; } threadAdapter.setDefaultUncaughtExceptionHandler(this); //... } }
初始化了hub和options。最主要的逻辑就是注册了Thread.UncaughtExceptionHandler等待异常抛出时作处理。那么我们再来看uncaughtException()的实现:
//UncaughtExceptionHandlerIntegration.java @Override public void uncaughtException(Thread thread, Throwable thrown) { if (options != null && hub != null) { options.getLogger().log(SentryLevel.INFO, "Uncaught exception received."); try { final UncaughtExceptionHint exceptionHint = new UncaughtExceptionHint(options.getFlushTimeoutMillis(), options.getLogger()); final Throwable throwable = getUnhandledThrowable(thread, thrown); final SentryEvent event = new SentryEvent(throwable); event.setLevel(SentryLevel.FATAL); final Hint hint = HintUtils.createWithTypeCheckHint(exceptionHint); final @NotNull SentryId sentryId = hub.captureEvent(event, hint); final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID); final EventDropReason eventDropReason = HintUtils.getEventDropReason(hint); //... } catch (Throwable e) { //... } if (defaultExceptionHandler != null) { options.getLogger().log(SentryLevel.INFO, "Invoking inner uncaught exception handler."); defaultExceptionHandler.uncaughtException(thread, thrown); } else { if (options.isPrintUncaughtStackTrace()) { thrown.printStackTrace(); } } } }
主要逻辑是创建了一个SentryEvent并将Throwable包进去,然后调用hub.captureEvent(event, hint)(之后再讲),将event上报到Sentry。到此,我们知道了UncaughtExceptionHandlerIntegration的作用就是为了将异常上报给Sentry后台的,而它实现了Integration接口,会在合适的时候将自己注册给Sentry。其他实现了Integration接口的类,目的也是将自己注册给Sentry进行绑定,并提供相应的方法去hook一些自己想要的逻辑。Integration的注册时机我们之后再讲,接下来看回到SentryOptions.java:
//SentryOptions.java private SentryOptions(final boolean empty) { if (!empty) { // SentryExecutorService should be initialized before any // SendCachedEventFireAndForgetIntegration executorService = new SentryExecutorService(); // UncaughtExceptionHandlerIntegration should be inited before any other Integration. // if there's an error on the setup, we are able to capture it integrations.add(new UncaughtExceptionHandlerIntegration()); integrations.add(new ShutdownHookIntegration()); eventProcessors.add(new MainEventProcessor(this)); eventProcessors.add(new DuplicateEventDetectionEventProcessor(this)); if (Platform.isJvm()) { eventProcessors.add(new SentryRuntimeEventProcessor()); } setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME); setSdkVersion(createSdkVersion()); addPackageInfo(); } }
看eventProcessors.add()都干了什么,以MainEventProcessor为例。
3.2.1.3.EventProcessor
MainEventProcessor实现了EventProcessor接口,EventProcessor是为SentryEvent或SentryTransaction服务的,目的是在发送事件时插入一些附属信息:
//EventProcessor.java
public interface EventProcessor {
/**
* May mutate or drop a SentryEvent
*
* @param event the SentryEvent
* @param hint the Hint
* @return the event itself, a mutated SentryEvent or null
*/
@Nullable
default SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) {
return event;
}
/**
* May mutate or drop a SentryTransaction
*
* @param transaction the SentryTransaction
* @param hint the Hint
* @return the event itself, a mutated SentryTransaction or null
*/
@Nullable
default SentryTransaction process(@NotNull SentryTransaction transaction, @NotNull Hint hint) {
return transaction;
}
}
两个process()方法分别作用于SentryEvent或SentryTransaction。回到MainEventProcessor,看看process()的实现:
//MainEventProcessor.java @Override public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { setCommons(event); setExceptions(event); setDebugMeta(event); setModules(event); if (shouldApplyScopeData(event, hint)) { processNonCachedEvent(event); setThreads(event, hint); } return event; } @Override public @NotNull SentryTransaction process( final @NotNull SentryTransaction transaction, final @NotNull Hint hint) { setCommons(transaction); setDebugMeta(transaction); if (shouldApplyScopeData(transaction, hint)) { processNonCachedEvent(transaction); } return transaction; }
逻辑很简单,就是做默认配置用的,通常如果自定义了一些信息就走自定义的,没有的话就配置默认信息。process()的调用时机是发送一个事件到Sentry后台时将基础信息进行配置,代码我们之后再来看。回到Sentry.init()方法:
//Sentry.java public static void init( final @NotNull OptionsContainer clazz, final @NotNull OptionsConfiguration optionsConfiguration, final boolean globalHubMode) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { final T options = clazz.createInstance(); applyOptionsConfiguration(optionsConfiguration, options); init(options, globalHubMode); }
clazz怎么创建的,并且clazz.createInstance()都干了什么我们就清楚了。
总结一下:
1.初始化了SentryAndroidOptions做各种基础配置并返回;
2.定义了各种Integration和EventProcessor,hook需要的时机,获取基础参数,为之后发送事件作准备。接下来我们看一下optionsConfiguration。
3.2.2 @NotNullOptionsConfigurationoptionsConfiguration
OptionsConfiguration是个接口,applyOptionsConfiguration()调用其configure()方法做一些基础的配置,所以回到SentryAndroid.java:
//SentryAndroid.java options -> { final LoadClass classLoader = new LoadClass(); final boolean isTimberUpstreamAvailable = classLoader.isClassAvailable(TIMBER_CLASS_NAME, options); final boolean isFragmentUpstreamAvailable = classLoader.isClassAvailable(FRAGMENT_CLASS_NAME, options); final boolean isFragmentAvailable = (isFragmentUpstreamAvailable && classLoader.isClassAvailable( SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME, options)); final boolean isTimberAvailable = (isTimberUpstreamAvailable && classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options)); final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger); final LoadClass loadClass = new LoadClass(); final ActivityFramesTracker activityFramesTracker = new ActivityFramesTracker(loadClass, options); AndroidOptionsInitializer.loadDefaultAndMetadataOptions( options, context, logger, buildInfoProvider); // We install the default integrations before the option configuration, so that the user // can remove any of them. Integrations will not evaluate the options immediately, but // will use them later, after being configured. AndroidOptionsInitializer.installDefaultIntegrations( context, options, buildInfoProvider, loadClass, activityFramesTracker, isFragmentAvailable, isTimberAvailable); configuration.configure(options); AndroidOptionsInitializer.initializeIntegrationsAndProcessors( options, context, buildInfoProvider, loadClass, activityFramesTracker); deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable); },
前面的几个isClassAvailable()方法就是检查是否能找到那几个类,正常情况下返回true。BuildInfoProvider是为了读取android.os.Build下的信息,包括判断是否为模拟器:

ActivityFramesTracker是利用FrameMetricsAggregator来收集app帧渲染的时间从而观察是否有掉帧的情况发生。我们继续看AndroidOptionsInitializer.loadDefaultAndMetadataOptions()的实现:
//AndroidOptionsInitializer.java static void loadDefaultAndMetadataOptions( final @NotNull SentryAndroidOptions options, @NotNull Context context, final @NotNull ILogger logger, final @NotNull BuildInfoProvider buildInfoProvider) { Objects.requireNonNull(context, "The context is required."); // it returns null if ContextImpl, so let's check for nullability if (context.getApplicationContext() != null) { context = context.getApplicationContext(); } Objects.requireNonNull(options, "The options object is required."); Objects.requireNonNull(logger, "The ILogger object is required."); // Firstly set the logger, if `debug=true` configured, logging can start asap. options.setLogger(logger); options.setDateProvider(new SentryAndroidDateProvider()); ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider); initializeCacheDirs(context, options); readDefaultOptionValues(options, context, buildInfoProvider); }
还是继续为SentryAndroidOptions做基础的配置。包括设置时间日期,读取Manifest里的配置信息,初始化cache目录和Android独有信息,如包名等。接着调用AndroidOptionsInitializer.installDefaultIntegrations()方法:
//AndroidOptionsInitializer.java static void installDefaultIntegrations( final @NotNull Context context, final @NotNull SentryAndroidOptions options, final @NotNull BuildInfoProvider buildInfoProvider, final @NotNull LoadClass loadClass, final @NotNull ActivityFramesTracker activityFramesTracker, final boolean isFragmentAvailable, final boolean isTimberAvailable) { // Integration MUST NOT cache option values in ctor, as they will be configured later by the // user // read the startup crash marker here to avoid doing double-IO for the SendCachedEnvelope // integrations below LazyEvaluator startupCrashMarkerEvaluator = new LazyEvaluator(() -> AndroidEnvelopeCache.hasStartupCrashMarker(options)); options.addIntegration( new SendCachedEnvelopeIntegration( new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()), startupCrashMarkerEvaluator)); // Integrations are registered in the same order. NDK before adding Watch outbox, // because sentry-native move files around and we don't want to watch that. final Class sentryNdkClass = isNdkAvailable(buildInfoProvider) ? loadClass.loadClass(SENTRY_NDK_CLASS_NAME, options.getLogger()) : null; options.addIntegration(new NdkIntegration(sentryNdkClass)); // this integration uses android.os.FileObserver, we can't move to sentry // before creating a pure java impl. options.addIntegration(EnvelopeFileObserverIntegration.getOutboxFileObserver()); // Send cached envelopes from outbox path // this should be executed after NdkIntegration because sentry-native move files on init. // and we'd like to send them right away options.addIntegration( new SendCachedEnvelopeIntegration( new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()), startupCrashMarkerEvaluator)); // AppLifecycleIntegration has to be installed before AnrIntegration, because AnrIntegration // relies on AppState set by it options.addIntegration(new AppLifecycleIntegration()); options.addIntegration(AnrIntegrationFactory.create(context, buildInfoProvider)); // registerActivityLifecycleCallbacks is only available if Context is an AppContext if (context instanceof Application) { options.addIntegration( new ActivityLifecycleIntegration( (Application) context, buildInfoProvider, activityFramesTracker)); options.addIntegration(new CurrentActivityIntegration((Application) context)); options.addIntegration(new UserInteractionIntegration((Application) context, loadClass)); if (isFragmentAvailable) { options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true)); } } else { options .getLogger() .log( SentryLevel.WARNING, "ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need an Application class to be installed."); } if (isTimberAvailable) { options.addIntegration(new SentryTimberIntegration()); } options.addIntegration(new AppComponentsBreadcrumbsIntegration(context)); options.addIntegration(new SystemEventsBreadcrumbsIntegration(context)); options.addIntegration( new NetworkBreadcrumbsIntegration(context, buildInfoProvider, options.getLogger())); options.addIntegration(new TempSensorBreadcrumbsIntegration(context)); options.addIntegration(new PhoneStateBreadcrumbsIntegration(context)); }
我们发现为options添加了一堆Integration。之前我们知道已经添加了一个UncaughtExceptionHandlerIntegration用来捕获Java异常,我们再一个个看看,弄清楚Sentry都给Android带来了哪些额外的能力。
(1)SendCachedEnvelopeIntegration:SendCachedEnvelopeIntegration有两处。一个是SendFireAndForgetEnvelopeSender,这个我们之前提到过,在App启动的时候将cache中的event发送出去。另一个是SendFireAndForgetOutboxSender,还在发件箱里未被发送的event。
(2)NdkIntegration:顾名思义,就是抓取NDK的异常。其中sentryNdkClass去的是上面定义的SENTRY_NDK_CLASS_NAME这个类,即io.sentry.android.ndk.SentryNdk。SentryNdk有个init()方法:
//SentryNdk.java public static void init(@NotNull final SentryAndroidOptions options) { SentryNdkUtil.addPackage(options.getSdkVersion()); initSentryNative(options); // only add scope sync observer if the scope sync is enabled. if (options.isEnableScopeSync()) { options.addScopeObserver(new NdkScopeObserver(options)); } options.setDebugImagesLoader(new DebugImagesLoader(options, new NativeModuleListLoader())); }
将options传入。initSentryNative()是个native方法,用来做初始化。接着为options添加IScopeObserver用来为当前Scope设置参数。这个init()方法是在NdkIntegration的register()中执行的:
//NdkIntegration.java @Override public final void register(final @NotNull IHub hub, final @NotNull SentryOptions options) { Objects.requireNonNull(hub, "Hub is required"); this.options = Objects.requireNonNull( (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, "SentryAndroidOptions is required"); final boolean enabled = this.options.isEnableNdk(); this.options.getLogger().log(SentryLevel.DEBUG, "NdkIntegration enabled: %s", enabled); // Note: `hub` isn't used here because the NDK integration writes files to disk which are picked // up by another integration (EnvelopeFileObserverIntegration). if (enabled && sentryNdkClass != null) { //... try { final Method method = sentryNdkClass.getMethod("init", SentryAndroidOptions.class); final Object[] args = new Object[1]; args[0] = this.options; method.invoke(null, args); //... addIntegrationToSdkVersion(); } catch (NoSuchMethodException e) { //... } catch (Throwable e) { //... } } else { disableNdkIntegration(this.options); } }
我们可以看到,通过反射的方式,将options传给Sentryndk的init()方法。
(3)EnvelopeFileObserverIntegration:在发送Event到Sentry后台之前,会先把它保存到本地。这个Integration时用来监听文件读写完毕后,进行网络请求,具体流程就不进行分析了。
(4)AppLifecycleIntegration监听App前后台切换,并添加BreadCrumb给Sentry,register()的主要实现如下:
//AppLifecycleIntegration.java @Override public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) { Objects.requireNonNull(hub, "Hub is required"); //... if (this.options.isEnableAutoSessionTracking() || this.options.isEnableAppLifecycleBreadcrumbs()) { try { Class.forName("androidx.lifecycle.DefaultLifecycleObserver"); Class.forName("androidx.lifecycle.ProcessLifecycleOwner"); if (AndroidMainThreadChecker.getInstance().isMainThread()) { addObserver(hub); } else { //... } } catch (ClassNotFoundException e) { //... } catch (IllegalStateException e) { //... } } }
//AppLifecycleIntegration.java private void addObserver(final @NotNull IHub hub) { //... watcher = new LifecycleWatcher( hub, this.options.getSessionTrackingIntervalMillis(), this.options.isEnableAutoSessionTracking(), this.options.isEnableAppLifecycleBreadcrumbs()); try { ProcessLifecycleOwner.get().getLifecycle().addObserver(watcher); //... addIntegrationToSdkVersion(); } catch (Throwable e) { //... } }
//LifecycleWatcher.java @Override public void onStart(final @NotNull LifecycleOwner owner) { startSession(); addAppBreadcrumb("foreground"); // Consider using owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED); // in the future. AppState.getInstance().setInBackground(false); } @Override public void onStop(final @NotNull LifecycleOwner owner) { if (enableSessionTracking) { final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis(); this.lastUpdatedSession.set(currentTimeMillis); scheduleEndSession(); } AppState.getInstance().setInBackground(true); addAppBreadcrumb("background"); }
(5)AnrIntegrationFactory.create():ANR在Android 11之前和之后的监测方式不同:
//AnrIntegrationFactory.java public final class AnrIntegrationFactory { @NotNull public static Integration create( final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R) { return new AnrV2Integration(context); } else { return new AnrIntegration(context); } } }
先看Android 11及之后的处理方式:
//AnrV2Integration.java @SuppressLint("NewApi") // we do the check in the AnrIntegrationFactory @Override public void register(@NotNull IHub hub, @NotNull SentryOptions options) { //... if (this.options.isAnrEnabled()) { try { options .getExecutorService() .submit(new AnrProcessor(context, hub, this.options, dateProvider)); } catch (Throwable e) { //... } options.getLogger().log(SentryLevel.DEBUG, "AnrV2Integration installed."); addIntegrationToSdkVersion(); } }
创建了一个AnrProcessor,实现了Runnable接口:
//AnrProcessor.java @SuppressLint("NewApi") // we check this in AnrIntegrationFactory @Override public void run() { final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); final List applicationExitInfoList = activityManager.getHistoricalProcessExitReasons(null, 0, 0); //... final IEnvelopeCache cache = options.getEnvelopeDiskCache(); if (cache instanceof EnvelopeCache) { if (options.isEnableAutoSessionTracking() && !((EnvelopeCache) cache).waitPreviousSessionFlush()) { //... ((EnvelopeCache) cache).flushPreviousSession(); } } // making a deep copy as we're modifying the list final List exitInfos = new ArrayList(applicationExitInfoList); final @Nullable Long lastReportedAnrTimestamp = AndroidEnvelopeCache.lastReportedAnr(options); ApplicationExitInfo latestAnr = null; for (ApplicationExitInfo applicationExitInfo : exitInfos) { if (applicationExitInfo.getReason() == ApplicationExitInfo.REASON_ANR) { latestAnr = applicationExitInfo; // remove it, so it's not reported twice exitInfos.remove(applicationExitInfo); break; } } //... if (options.isReportHistoricalAnrs()) { reportNonEnrichedHistoricalAnrs(exitInfos, lastReportedAnrTimestamp); } reportAsSentryEvent(latestAnr, true); }
不去深究代码的细节,只看方案,我们可以看到Android 11及以上是通过ActivityManager.getHistoricalProcessExitReasons()来得到Anr的相关信息,最终通过reportAsSentryEvent()来进行上报:
private void reportAsSentryEvent( final @NotNull ApplicationExitInfo exitInfo, final boolean shouldEnrich) { final long anrTimestamp = exitInfo.getTimestamp(); final boolean isBackground = exitInfo.getImportance() != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; final ParseResult result = parseThreadDump(exitInfo, isBackground); //... final AnrV2Hint anrHint = new AnrV2Hint( options.getFlushTimeoutMillis(), options.getLogger(), anrTimestamp, shouldEnrich, isBackground); final Hint hint = HintUtils.createWithTypeCheckHint(anrHint); final SentryEvent event = new SentryEvent(); if (result.type == ParseResult.Type.ERROR) { final Message sentryMessage = new Message(); sentryMessage.setFormatted( "Sentry Android SDK failed to parse system thread dump for " + "this ANR. We recommend enabling [SentryOptions.isAttachAnrThreadDump] option " + "to attach the thread dump as plain text and report this issue on GitHub."); event.setMessage(sentryMessage); } else if (result.type == ParseResult.Type.DUMP) { event.setThreads(result.threads); } event.setLevel(SentryLevel.FATAL); event.setTimestamp(DateUtils.getDateTime(anrTimestamp)); if (options.isAttachAnrThreadDump()) { if (result.dump != null) { hint.setThreadDump(Attachment.fromThreadDump(result.dump)); } } final @NotNull SentryId sentryId = hub.captureEvent(event, hint); final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID); //... }
这段代码我们先不去看具体的实现,它的作用就是将Anr信息封装成Hint,再构造SentryEvent,通过hub.captureEvent(event, hint)进行上报。我们再来看看Android 11以下是如何处理的:
//AnrIntegrationFactory.java public static Integration create( final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) { if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R) { return new AnrV2Integration(context); } else { return new AnrIntegration(context); } } }
//AnrIntegration.java private void register(final @NotNull IHub hub, final @NotNull SentryAndroidOptions options) { //... if (options.isAnrEnabled()) { synchronized (watchDogLock) { if (anrWatchDog == null) { //... anrWatchDog = new ANRWatchDog( options.getAnrTimeoutIntervalMillis(), options.isAnrReportInDebug(), error -> reportANR(hub, options, error), options.getLogger(), context); anrWatchDog.start(); options.getLogger().log(SentryLevel.DEBUG, "AnrIntegration installed."); addIntegrationToSdkVersion(); } } } }
封装了一个ANRWatchDog继承了Thread,这个方案就是传统的Anr监测方案:启动一个异步线程,在while循环中,使用主线程的Handler发送一个消息,线程休眠指定的时间5s,当线程唤醒之后,如果发送的消息还没被主线程执行,即认为主线程发生了卡顿。具体流程不再描述了,最终也是将Anr信息封装成Hint,再构造SentryEvent,通过hub.captureEvent(event, hint)进行上报。
(6)ActivityLifecycleIntegration:实现了Application.ActivityLifecycleCallbacks用来监测Activity生命周期。ActivityLifecycleIntegration主要干了三件事:
1.计算冷启动时间;
2.用来将这Activity的生命周期变化及信息添加到BreadCrumb中去;
3.计算Activity的启动时间。
先看一下register()方法的实现:
//ActivityLifecycleIntegration.java @Override public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) { this.options = Objects.requireNonNull( (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null, "SentryAndroidOptions is required"); this.hub = Objects.requireNonNull(hub, "Hub is required"); //... performanceEnabled = isPerformanceEnabled(this.options); fullyDisplayedReporter = this.options.getFullyDisplayedReporter(); timeToFullDisplaySpanEnabled = this.options.isEnableTimeToFullDisplayTracing(); application.registerActivityLifecycleCallbacks(this); this.options.getLogger().log(SentryLevel.DEBUG, "ActivityLifecycleIntegration installed."); addIntegrationToSdkVersion(); }
通过application.registerActivityLifecycleCallbacks(this)注册生命周期的监听,当执行了onActivityCreated():
//ActivityLifecycleIntegration.java @Override public synchronized void onActivityCreated( final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { setColdStart(savedInstanceState); addBreadcrumb(activity, "created"); startTracing(activity); final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); firstActivityCreated = true; if (fullyDisplayedReporter != null) { fullyDisplayedReporter.registerFullyDrawnListener(() -> onFullFrameDrawn(ttfdSpan)); } }
我们看首先setColdStart()设置了是否为冷启动的标志位,addBreadcrumb()设置面包屑,startTracing()开始追踪这个Activity。具体代码实现不在此展开。除了加面包屑这件事,主要就是为了区分是否为冷启动,为了之后统计冷启动速度和页面加载速度作区分。而这两个都是以onActivityCreated()作为起始点(冷启动其实是以SentryPerformanceProvider作为起点的,但如果SentryPerformanceProvider被disable了,那就以第一个Activity走到onCreate()作为起点,在onActivityResumed()作为统计的终点:
//ActivityLifecycleIntegration.java public synchronized void onActivityResumed(final @NotNull Activity activity) { if (performanceEnabled) { // app start span @Nullable final SentryDate appStartStartTime = AppStartState.getInstance().getAppStartTime(); @Nullable final SentryDate appStartEndTime = AppStartState.getInstance().getAppStartEndTime(); if (appStartStartTime != null && appStartEndTime == null) { AppStartState.getInstance().setAppStartEnd(); } finishAppStartSpan(); final @Nullable ISpan ttidSpan = ttidSpanMap.get(activity); final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); final View rootView = activity.findViewById(android.R.id.content); if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN && rootView != null) { FirstDrawDoneListener.registerForNextDraw( rootView, () -> onFirstFrameDrawn(ttfdSpan, ttidSpan), buildInfoProvider); } else { mainHandler.post(() -> onFirstFrameDrawn(ttfdSpan, ttidSpan)); } } addBreadcrumb(activity, "resumed"); }
其中finishAppStartSpan()方法最终会构造一个SentryTransaction,通过captureTransaction()将启动信息上报到Sentry后台。
(7)CurrentActivityIntegration:这个Integration也实现了Application.ActivityLifecycleCallbacks接口,目的是为CurrentActivityHolder添加目前活跃Activity的引用的。
(8)UserInteractionIntegration:为了记录用户的交互信息,依然是实现了Application.ActivityLifecycleCallbacks接口,仅对onActivityResumed()和onActivityPaused()做了实现:
//UserInteractionIntegration.java @Override public void onActivityResumed(@NotNull Activity activity) { startTracking(activity); } @Override public void onActivityPaused(@NotNull Activity activity) { stopTracking(activity); }
//UserInteractionIntegration.java private void startTracking(final @NotNull Activity activity) { final Window window = activity.getWindow(); if (window == null) { //... return; } if (hub != null && options != null) { Window.Callback delegate = window.getCallback(); if (delegate == null) { delegate = new NoOpWindowCallback(); } final SentryGestureListener gestureListener = new SentryGestureListener(activity, hub, options); window.setCallback(new SentryWindowCallback(delegate, activity, gestureListener, options)); } }
在startTracking()方法中构建了SentryGestureListener和SentryWindowCallback,用来监听手势事件,从而记录用户行为。
(9)FragmentLifecycleIntegration:不知为什么看不到源码,但从命名来看就是监听Fragment生命周期的。
(10)SentryTimberIntegration:也看不到源码。
(11)AppComponentsBreadcrumbsIntegration:实现了ComponentCallbacks2接口监听内存不足的情况。
(12)SystemEventsBreadcrumbsIntegration:构造了一个SystemEventsBroadcastReceiver,监听了一系列系统相关的事件:
private static @NotNull List getDefaultActions() { final List actions = new ArrayList(); actions.add(ACTION_APPWIDGET_DELETED); actions.add(ACTION_APPWIDGET_DISABLED); actions.add(ACTION_APPWIDGET_ENABLED); actions.add("android.appwidget.action.APPWIDGET_HOST_RESTORED"); actions.add("android.appwidget.action.APPWIDGET_RESTORED"); actions.add(ACTION_APPWIDGET_UPDATE); actions.add("android.appwidget.action.APPWIDGET_UPDATE_OPTIONS"); actions.add(ACTION_POWER_CONNECTED); actions.add(ACTION_POWER_DISCONNECTED); actions.add(ACTION_SHUTDOWN); actions.add(ACTION_AIRPLANE_MODE_CHANGED); actions.add(ACTION_BATTERY_LOW); actions.add(ACTION_BATTERY_OKAY); actions.add(ACTION_BOOT_COMPLETED); actions.add(ACTION_CAMERA_BUTTON); actions.add(ACTION_CONFIGURATION_CHANGED); actions.add("android.intent.action.CONTENT_CHANGED"); actions.add(ACTION_DATE_CHANGED); actions.add(ACTION_DEVICE_STORAGE_LOW); actions.add(ACTION_DEVICE_STORAGE_OK); actions.add(ACTION_DOCK_EVENT); actions.add("android.intent.action.DREAMING_STARTED"); actions.add("android.intent.action.DREAMING_STOPPED"); actions.add(ACTION_INPUT_METHOD_CHANGED); actions.add(ACTION_LOCALE_CHANGED); actions.add(ACTION_REBOOT); actions.add(ACTION_SCREEN_OFF); actions.add(ACTION_SCREEN_ON); actions.add(ACTION_TIMEZONE_CHANGED); actions.add(ACTION_TIME_CHANGED); actions.add("android.os.action.DEVICE_IDLE_MODE_CHANGED"); actions.add("android.os.action.POWER_SAVE_MODE_CHANGED"); // The user pressed the "Report" button in the crash/ANR dialog. actions.add(ACTION_APP_ERROR); // Show activity for reporting a bug. actions.add(ACTION_BUG_REPORT); // consider if somebody mounted or ejected a sdcard actions.add(ACTION_MEDIA_BAD_REMOVAL); actions.add(ACTION_MEDIA_MOUNTED); actions.add(ACTION_MEDIA_UNMOUNTABLE); actions.add(ACTION_MEDIA_UNMOUNTED); return actions; }
收到相应的action将被添加至BreadCrumb。(13)NetworkBreadcrumbsIntegration:通过ConnectivityManager监听网络状态的变化并添加至BreadCrumb。
(14)TempSensorBreadcrumbsIntegration:实现了SensorEventListener接口,监听Sensor的状态变化。
(15)PhoneStateBreadcrumbsIntegration:通过TelephonyManager监听TelephonyManager.CALL_STATE_RINGING状态。好了, AndroidOptionsInitializer.installDefaultIntegrations()方法分析完了。这个方法就是为Android添加的Integration。
到此为止,这些Integration便是Sentry为Android添加的能力。我们再回到SentryAndroid.java继续来看Sentry.init()方法中applyOptionsConfiguration()针对options还干了什么,主要还剩最后一个方法:AndroidOptionsInitializer.initializeIntegrationsAndProcessors():
static void initializeIntegrationsAndProcessors( final @NotNull SentryAndroidOptions options, final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider, final @NotNull LoadClass loadClass, final @NotNull ActivityFramesTracker activityFramesTracker) { if (options.getCacheDirPath() != null && options.getEnvelopeDiskCache() instanceof NoOpEnvelopeCache) { options.setEnvelopeDiskCache(new AndroidEnvelopeCache(options)); } options.addEventProcessor(new DeduplicateMultithreadedEventProcessor(options)); options.addEventProcessor( new DefaultAndroidEventProcessor(context, buildInfoProvider, options)); options.addEventProcessor(new PerformanceAndroidEventProcessor(options, activityFramesTracker)); options.addEventProcessor(new ScreenshotEventProcessor(options, buildInfoProvider)); options.addEventProcessor(new ViewHierarchyEventProcessor(options)); options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider)); options.setTransportGate(new AndroidTransportGate(context, options.getLogger())); final SentryFrameMetricsCollector frameMetricsCollector = new SentryFrameMetricsCollector(context, options, buildInfoProvider); options.setTransactionProfiler( new AndroidTransactionProfiler(context, options, buildInfoProvider, frameMetricsCollector)); options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger())); options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger())); final boolean isAndroidXScrollViewAvailable = loadClass.isClassAvailable("androidx.core.view.ScrollingView", options); final boolean isComposeUpstreamAvailable = loadClass.isClassAvailable(COMPOSE_CLASS_NAME, options); if (options.getGestureTargetLocators().isEmpty()) { final List gestureTargetLocators = new ArrayList(2); gestureTargetLocators.add(new AndroidViewGestureTargetLocator(isAndroidXScrollViewAvailable)); final boolean isComposeAvailable = (isComposeUpstreamAvailable && loadClass.isClassAvailable( SENTRY_COMPOSE_GESTURE_INTEGRATION_CLASS_NAME, options)); if (isComposeAvailable) { gestureTargetLocators.add(new ComposeGestureTargetLocator(options.getLogger())); } options.setGestureTargetLocators(gestureTargetLocators); } if (options.getViewHierarchyExporters().isEmpty() && isComposeUpstreamAvailable && loadClass.isClassAvailable( SENTRY_COMPOSE_VIEW_HIERARCHY_INTEGRATION_CLASS_NAME, options)) { final List viewHierarchyExporters = new ArrayList(1); viewHierarchyExporters.add(new ComposeViewHierarchyExporter(options.getLogger())); options.setViewHierarchyExporters(viewHierarchyExporters); } options.setMainThreadChecker(AndroidMainThreadChecker.getInstance()); if (options.getCollectors().isEmpty()) { options.addCollector(new AndroidMemoryCollector()); options.addCollector(new AndroidCpuCollector(options.getLogger(), buildInfoProvider)); } options.setTransactionPerformanceCollector(new DefaultTransactionPerformanceCollector(options)); if (options.getCacheDirPath() != null) { options.addScopeObserver(new PersistingScopeObserver(options)); options.addOptionsObserver(new PersistingOptionsObserver(options)); } }
首先我们可以看到,添加了很多EventProcessor,在之前的章节我们介绍过,EventProcessor的目的是在发送事件时插入一些附属信息的,其中process()方法是具体的实现。我们选取ScreenshotEventProcessor看看都实现了什么,其他EventProcessor就不介绍了:
//ScreenshotEventProcessor.java @Override public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { //... final byte[] screenshot = takeScreenshot( activity, options.getMainThreadChecker(), options.getLogger(), buildInfoProvider); if (screenshot == null) { return event; } hint.setScreenshot(Attachment.fromScreenshot(screenshot)); hint.set(ANDROID_ACTIVITY, activity); return event; }
主要实现是如果开启了ScreenShot模式,发送事件之前会截图并添加至hint中,待发送事件时将截图作为附件上传至服务端。除了EventProcessor,还有其他几项配置会添加到options里,我们来看看几个重要的配置:
(1)AndroidTransportGate用来判断设备的网络是否是connected的状态,如果是的话发送事件到Sentry后台,否则存在cache中。
(2)AndroidTransactionProfiler用来管理Transaction的,实现了ITransactionProfiler接口,重写了onTransactionStart()和onTransactionFinish()方法。设置了buffer,如果在跟踪的过程中到了buffer的阈值,那么新的记录将会被丢弃。默认每个traces会跟踪30s,总共给30MB的buffer。大概能记录3次的Transaction。其中onTransactionStart()设置了traceFilesDir。而onTransactionFinish()主要构件了一个ProfilingTraceData,除了记录了常规的设备相关信息外,还有三个重要的参数:File traceFile,List transactions和measurementsMap。traceFile是在onTransactionStart()时调用系统的VMDebug.startMethodTracing()生成的trace文件。transactions记录了每一个Transaction相关的基础信息,其数据结构如下:
//ProfilingTransactionData.java private @NotNull String id; // transaction event id (the current transaction_id) private @NotNull String traceId; // trace id (the current trace_id) private @NotNull String name; // transaction name private @NotNull Long relativeStartNs; // timestamp in nanoseconds this transaction started private @Nullable Long relativeEndNs; // timestamp in nanoseconds this transaction ended private @NotNull Long relativeStartCpuMs; // cpu time in milliseconds this transaction started private @Nullable Long relativeEndCpuMs; // cpu time in milliseconds this transaction ended
measurementsMap包含了screenFrameRateMeasurements,slowFrameRenderMeasurements和frozenFrameRenderMeasurements三个信息。
(3)AndroidMemoryCollector,AndroidCpuCollector和DefaultTransactionPerformanceCollector:AndroidMemoryCollector和AndroidCpuCollector实现了ICollector接口,重写了collect()方法用来记录一个Transaction里的内存和cpu使用情况。并交由DefaultTransactionPerformanceCollector进行处理。DefaultTransactionPerformanceCollector每100ms进行一次collect()操作总共持续30s,收集各个Collector的信息,在发送事件时一并发送到Sentry后来。
(4)PersistingScopeObserver:持久的Scope observer参数的实现。
(5)PersistingOptionsObserver:持久的SentryOptions observer的实现。到此为止,Sentry.init()中的applyOptionsConfiguration()方法的解析终于完成了。总结一下:applyOptionsConfiguration()最重要的事情就是初始化了开发者赋予Sentry的能力Intergration,和每个事件所需要的额外硬件/软件/环境等相关基础信息EventProcessor,及不同Intergration对应的信息和其他随着事件一起发送的额外数据。另外,进行了一些cache的初始化,做了一些硬件相关的检测等。总而言之,就是在我们真正产生事件之前做好一切基础准备,在上报各种事件时,相关的信息都会最终被封装在SentryOptions中。
我们再回到Sentry.init():
//Sentry.java public static void init( final @NotNull OptionsContainer clazz, final @NotNull OptionsConfiguration optionsConfiguration, final boolean globalHubMode) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { final T options = clazz.createInstance(); applyOptionsConfiguration(optionsConfiguration, options); init(options, globalHubMode); }
还剩最后一行代码:init(options, globalHubMode)
3.2.3.init(options, globalHubMode)
通过上一章节的分析,我们知道Sentry会先初始化好各种所需的能力,以及随事件需要上报的各种参数,并最终构建一个SentryOptions对象。init()这个方法就是根据我们上一步构建的SentryOptions真正的去初始化SDK:
//Sentry.java private static synchronized void init( final @NotNull SentryOptions options, final boolean globalHubMode) { //... if (!initConfigurations(options)) { return; } Sentry.globalHubMode = globalHubMode; final IHub hub = getCurrentHub(); mainHub = new Hub(options); currentHub.set(mainHub); hub.close(); final ISentryExecutorService sentryExecutorService = options.getExecutorService(); // If the passed executor service was previously called we set a new one if (sentryExecutorService.isClosed()) { options.setExecutorService(new SentryExecutorService()); } for (final Integration integration : options.getIntegrations()) { integration.register(HubAdapter.getInstance(), options); } notifyOptionsObservers(options); finalizePreviousSession(options, HubAdapter.getInstance()); }
首先initConfigurations()读取了Sentry的配置信息,包括DNS,Host信息等,然后创建了一个Hub对象,用来管理Scope和SentryClient:
//Hub.java public Hub(final @NotNull SentryOptions options) { this(options, createRootStackItem(options)); // Integrations are no longer registered on Hub ctor, but on Sentry.init } private static StackItem createRootStackItem(final @NotNull SentryOptions options) { validateOptions(options); final Scope scope = new Scope(options); final ISentryClient client = new SentryClient(options); return new StackItem(options, client, scope); }
返回一个StackItem对象。
//Hub.java private Hub(final @NotNull SentryOptions options, final @NotNull StackItem rootStackItem) { this(options, new Stack(options.getLogger(), rootStackItem)); } private Hub(final @NotNull SentryOptions options, final @NotNull Stack stack) { validateOptions(options); this.options = options; this.tracesSampler = new TracesSampler(options); this.stack = stack; this.lastEventId = SentryId.EMPTY_ID; this.transactionPerformanceCollector = options.getTransactionPerformanceCollector(); // Integrations will use this Hub instance once registered. // Make sure Hub ready to be used then. this.isEnabled = true; }
创建了Stack对象。接着如果SentryExecutorService是关闭的状态,那么创建一个SentryExecutorService对象并交由options。然后就是执行上一章节我们分析过的各种Integration的注册方法,这样Sentry就真正拥有了各种能力。notifyOptionsObservers()方法是为了获得我们上一章节讲的为PersistingOptionsObserver进行赋值。好了到此为止SentryAndroid.init()方法就分析完了。现在Sentry已经都准备好了,等待着我们发送各种事件了。接下来帮大家梳理一下事件发送的流程。
04
事件发送
在文章的最初介绍概念时,我们知道Sentry的事件分为SentryEvent和SentryTransaction。它们俩其实继承自同一个父类:SentryBaseEvent。我们最常用到的崩溃日志的上报是在UncaughtExceptionHandlerIntegration中调用了hub.captureEvent(event, hint)。Sentry还为我们封装了个captureException(final @NotNull Throwable throwable)的方法方便我们直接上报Throwable。而captureException()方法最终也是调到captureEvent()里。实际上只要构建了一个SentryEvent,最终都会调用到captureEvent()里。我们以UncaughtExceptionHandlerIntegration中的处理为例,看一下captureEvent()的流程。
4.1.IHub.captureEvent()
在UncaughtExceptionHandlerIntegration里,由于实现了UncaughtExceptionHandler接口,当有exception出现时,会回调至uncaughtException()方法中进行处理:
//UncaughtExceptionHandlerIntegration.java @Override public void uncaughtException(Thread thread, Throwable thrown) { if (options != null && hub != null) { options.getLogger().log(SentryLevel.INFO, "Uncaught exception received."); try { final UncaughtExceptionHint exceptionHint = new UncaughtExceptionHint(options.getFlushTimeoutMillis(), options.getLogger()); final Throwable throwable = getUnhandledThrowable(thread, thrown); final SentryEvent event = new SentryEvent(throwable); event.setLevel(SentryLevel.FATAL); final Hint hint = HintUtils.createWithTypeCheckHint(exceptionHint); final @NotNull SentryId sentryId = hub.captureEvent(event, hint); final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID); final EventDropReason eventDropReason = HintUtils.getEventDropReason(hint); //... } catch (Throwable e) { //... } if (defaultExceptionHandler != null) { //... defaultExceptionHandler.uncaughtException(thread, thrown); } else { if (options.isPrintUncaughtStackTrace()) { thrown.printStackTrace(); } } } }
先看SentryEvent的创建,将Throwable作为参数构造SentryEvent:
//SentryEvent.java public SentryEvent(final @Nullable Throwable throwable) { this(); this.throwable = throwable; } public SentryEvent() { this(new SentryId(), DateUtils.getCurrentDateTime()); } SentryEvent(final @NotNull SentryId eventId, final @NotNull Date timestamp) { super(eventId); this.timestamp = timestamp; }
//SentryId.java public SentryId() { this((UUID) null); } public SentryId(@Nullable UUID uuid) { if (uuid == null) { uuid = UUID.randomUUID(); } this.uuid = uuid; }
创建了一个uuid作为SentryId,记录上报的日期和Throwable。通过event.setLevel(SentryLevel.FATAL)将事件等级设置为FATAL。然后创建hint调用hub.captureEvent(event, hint):
//IHub.java @NotNull SentryId captureEvent(@NotNull SentryEvent event, @Nullable Hint hint);
//Hub.java @Override public @NotNull SentryId captureEvent( final @NotNull SentryEvent event, final @Nullable Hint hint) { return captureEventInternal(event, hint, null); }
//Hub.java private @NotNull SentryId captureEventInternal( final @NotNull SentryEvent event, final @Nullable Hint hint, final @Nullable ScopeCallback scopeCallback) { SentryId sentryId = SentryId.EMPTY_ID; if (!isEnabled()) { //... } else if (event == null) { //... } else { try { assignTraceContext(event); final StackItem item = stack.peek(); final Scope scope = buildLocalScope(item.getScope(), scopeCallback); sentryId = item.getClient().captureEvent(event, scope, hint); this.lastEventId = sentryId; } catch (Throwable e) { //... } } return sentryId; }
最终调到SentryClient中的captureEvent()方法,在看captureEvent()的实现之前,我们先来看一下SentryClient的构造方法:
//SentryClient.java SentryClient(final @NotNull SentryOptions options) { this.options = Objects.requireNonNull(options, "SentryOptions is required."); this.enabled = true; ITransportFactory transportFactory = options.getTransportFactory(); if (transportFactory instanceof NoOpTransportFactory) { transportFactory = new AsyncHttpTransportFactory(); options.setTransportFactory(transportFactory); } final RequestDetailsResolver requestDetailsResolver = new RequestDetailsResolver(options); transport = transportFactory.create(options, requestDetailsResolver.resolve()); this.random = options.getSampleRate() == null ? null : new SecureRandom(); }
最重要的就是初始化了网络的部分,构造了一个AsyncHttpTransportFactory,创建了AsyncHttpTransport对象并赋值给transport,而AsyncHttpTransport负责缓存事件到本地和发送事件到服务端。接下来我们来看captureEvent()的实现:
//SentryClient.java @Override public @NotNull SentryId captureEvent( @NotNull SentryEvent event, final @Nullable Scope scope, @Nullable Hint hint) { //... event = processEvent(event, hint, options.getEventProcessors()); if (event != null) { event = executeBeforeSend(event, hint); //... } //... @Nullable Session sessionBeforeUpdate = scope != null ? scope.withSession((@Nullable Session session) -> {}) : null; @Nullable Session session = null; if (event != null) { // https://develop.sentry.dev/sdk/sessions/#terminal-session-states if (sessionBeforeUpdate == null || !sessionBeforeUpdate.isTerminated()) { session = updateSessionData(event, hint, scope); } //... } final boolean shouldSendSessionUpdate = shouldSendSessionUpdateForDroppedEvent(sessionBeforeUpdate, session); //... SentryId sentryId = SentryId.EMPTY_ID; if (event != null && event.getEventId() != null) { sentryId = event.getEventId(); } try { @Nullable TraceContext traceContext = null; if (HintUtils.hasType(hint, Backfillable.class)) { // for backfillable hint we synthesize Baggage from event values if (event != null) { final Baggage baggage = Baggage.fromEvent(event, options); traceContext = baggage.toTraceContext(); } } else if (scope != null) { final @Nullable ITransaction transaction = scope.getTransaction(); if (transaction != null) { traceContext = transaction.traceContext(); } else { final @NotNull PropagationContext propagationContext = TracingUtils.maybeUpdateBaggage(scope, options); traceContext = propagationContext.traceContext(); } } final boolean shouldSendAttachments = event != null; List attachments = shouldSendAttachments ? getAttachments(hint) : null; final SentryEnvelope envelope = buildEnvelope(event, attachments, session, traceContext, null); hint.clear(); if (envelope != null) { transport.send(envelope, hint); } } catch (IOException | SentryEnvelopeException e) { //... } //... return sentryId; }
这段代码非常长,我们截取了核心部分。首先调用了processEvent()方法:
//SentryClient.java @Nullable private SentryEvent processEvent( @NotNull SentryEvent event, final @NotNull Hint hint, final @NotNull List eventProcessors) { for (final EventProcessor processor : eventProcessors) { try { // only wire backfillable events through the backfilling processors, skip from others, and // the other way around final boolean isBackfillingProcessor = processor instanceof BackfillingEventProcessor; final boolean isBackfillable = HintUtils.hasType(hint, Backfillable.class); if (isBackfillable && isBackfillingProcessor) { event = processor.process(event, hint); } else if (!isBackfillable && !isBackfillingProcessor) { event = processor.process(event, hint); } } catch (Throwable e) { //... } //... } return event; }
我们可以看到传的参数:options.getEventProcessors()就是在初始化阶段创建的EventProcessor列表,用来在发送事件时添加一些信息的。在processEvent()方法中主要是就是执行了各个EventProcessor的process()方法去添加额外信息。executeBeforeSend(event, hint)实际上是个callback,用来给用户提供一个发送事件之前的时机进行额外的处理。之后的代码实际上都是为了构建一个SentryEnvelope对象envelope交给transport去处理。envelope会把SentryEvent,Attachment(比如截图),session,traceContext进行封装,最终调用transport.send(envelope, hint)方法。
我们来看transport.send()方法的实现:
//AsyncHttpTransport.java @Override public void send(final @NotNull SentryEnvelope envelope, final @NotNull Hint hint) throws IOException { // For now no caching on envelopes IEnvelopeCache currentEnvelopeCache = envelopeCache; boolean cached = false; if (HintUtils.hasType(hint, Cached.class)) { currentEnvelopeCache = NoOpEnvelopeCache.getInstance(); cached = true; options.getLogger().log(SentryLevel.DEBUG, "Captured Envelope is already cached"); } final SentryEnvelope filteredEnvelope = rateLimiter.filter(envelope, hint); if (filteredEnvelope == null) { if (cached) { envelopeCache.discard(envelope); } } else { SentryEnvelope envelopeThatMayIncludeClientReport; if (HintUtils.hasType( hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) { envelopeThatMayIncludeClientReport = options.getClientReportRecorder().attachReportToEnvelope(filteredEnvelope); } else { envelopeThatMayIncludeClientReport = filteredEnvelope; } final Future future = executor.submit( new EnvelopeSender(envelopeThatMayIncludeClientReport, hint, currentEnvelopeCache)); if (future != null && future.isCancelled()) { options .getClientReportRecorder() .recordLostEnvelope(DiscardReason.QUEUE_OVERFLOW, envelopeThatMayIncludeClientReport); } } }
这段代码我们讲一下重点:先初始化currentEnvelopeCache,再将envelope封装成envelopeThatMayIncludeClientReport,最终将envelopeThatMayIncludeClientReport,hint和currentEnvelopeCache封装成EnvelopeSender交给QueuedThreadPoolExecutor处理。其中EnvelopeSender是个Runnable,我们看看其run()方法的实现:
//AsyncHttpTransport.java @Override public void run() { TransportResult result = this.failedResult; try { result = flush(); options.getLogger().log(SentryLevel.DEBUG, "Envelope flushed"); } catch (Throwable e) { options.getLogger().log(SentryLevel.ERROR, e, "Envelope submission failed"); throw e; } finally { final TransportResult finalResult = result; HintUtils.runIfHasType( hint, SubmissionResult.class, (submissionResult) -> { //... }); } }
执行了flush()方法:
//AsyncHttpTransport.java private @NotNull TransportResult flush() { TransportResult result = this.failedResult; envelope.getHeader().setSentAt(null); envelopeCache.store(envelope, hint); HintUtils.runIfHasType( hint, DiskFlushNotification.class, (diskFlushNotification) -> { diskFlushNotification.markFlushed(); options.getLogger().log(SentryLevel.DEBUG, "Disk flush envelope fired"); }); if (transportGate.isConnected()) { final SentryEnvelope envelopeWithClientReport = options.getClientReportRecorder().attachReportToEnvelope(envelope); try { @NotNull SentryDate now = options.getDateProvider().now(); envelopeWithClientReport .getHeader() .setSentAt(DateUtils.nanosToDate(now.nanoTimestamp())); result = connection.send(envelopeWithClientReport); if (result.isSuccess()) { envelopeCache.discard(envelope); } else { final String message = "The transport failed to send the envelope with response code " + result.getResponseCode(); //... if (result.getResponseCode() >= 400 && result.getResponseCode() != 429) { HintUtils.runIfDoesNotHaveType( hint, Retryable.class, (hint) -> { //... }); } throw new IllegalStateException(message); } } catch (IOException e) { //... } } else { // If transportGate is blocking from sending, allowed to retry HintUtils.runIfHasType( hint, Retryable.class, (retryable) -> { retryable.setRetry(true); }, (hint, clazz) -> { //... }); } return result; }
我们可以看到,首先就是通过envelopeCache.store(envelope, hint)将这个事件保存在本地。然后通过connection.send(envelopeWithClientReport)将事件发送至服务端,如果事件发送成功的话,再调用envelopeCache.discard(envelope)将保存在本地的事件删除。到此为止一个SentryEvent的发送流程就分析完毕了。
简单地梳理一下发送的流程:
下面我们再来分析一下SentryTransaction的发送流程。
4.2.IHub.captureTransaction()
之前我们在分析ActivityLifecycleIntegration的实现时提到过,在onActivityCreated()作为一个Transaction起始点,在onActivityResumed()时作为这个Transaction统计的终点,通过调用finishAppStartSpan()来进行Transaction的上报。我们先来看看在onActivityCreated()是如何创建一个SentryTransaction的:
//ActivityLifecycleIntegration.java @Override public synchronized void onActivityCreated( final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { setColdStart(savedInstanceState); addBreadcrumb(activity, "created"); startTracing(activity); final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); firstActivityCreated = true; if (fullyDisplayedReporter != null) { fullyDisplayedReporter.registerFullyDrawnListener(() -> onFullFrameDrawn(ttfdSpan)); } }
继续追踪startTracing()方法:
//ActivityLifecycleIntegration.java private void startTracing(final @NotNull Activity activity) { WeakReference weakActivity = new WeakReference(activity); if (hub != null && !isRunningTransactionOrTrace(activity)) { if (!performanceEnabled) { activitiesWithOngoingTransactions.put(activity, NoOpTransaction.getInstance()); TracingUtils.startNewTrace(hub); } else if (performanceEnabled) { // as we allow a single transaction running on the bound Scope, we finish the previous ones stopPreviousTransactions(); final String activityName = getActivityName(activity); final SentryDate appStartTime = foregroundImportance ? AppStartState.getInstance().getAppStartTime() : null; final Boolean coldStart = AppStartState.getInstance().isColdStart(); final TransactionOptions transactionOptions = new TransactionOptions(); if (options.isEnableActivityLifecycleTracingAutoFinish()) { transactionOptions.setIdleTimeout(options.getIdleTimeout()); transactionOptions.setTrimEnd(true); } transactionOptions.setWaitForChildren(true); transactionOptions.setTransactionFinishedCallback( (finishingTransaction) -> { @Nullable Activity unwrappedActivity = weakActivity.get(); if (unwrappedActivity != null) { activityFramesTracker.setMetrics( unwrappedActivity, finishingTransaction.getEventId()); } else { if (options != null) { options .getLogger() .log( SentryLevel.WARNING, "Unable to track activity frames as the Activity %s has been destroyed.", activityName); } } }); // This will be the start timestamp of the transaction, as well as the ttid/ttfd spans final @NotNull SentryDate ttidStartTime; if (!(firstActivityCreated || appStartTime == null || coldStart == null)) { // The first activity ttid/ttfd spans should start at the app start time ttidStartTime = appStartTime; } else { // The ttid/ttfd spans should start when the previous activity called its onPause method ttidStartTime = lastPausedTime; } transactionOptions.setStartTimestamp(ttidStartTime); // we can only bind to the scope if there's no running transaction ITransaction transaction = hub.startTransaction( new TransactionContext(activityName, TransactionNameSource.COMPONENT, UI_LOAD_OP), transactionOptions); setSpanOrigin(transaction); //... } } }
截取重点部分,创建一个TransactionOptions对象设置各种参数,再封装一个TransactionContext对象记录activityName,之后调用hub.startTransaction()进行上报:
//Hub.java @ApiStatus.Internal @Override public @NotNull ITransaction startTransaction( final @NotNull TransactionContext transactionContext, final @NotNull TransactionOptions transactionOptions) { return createTransaction(transactionContext, transactionOptions); }
//Hub.java private @NotNull ITransaction createTransaction( final @NotNull TransactionContext transactionContext, final @NotNull TransactionOptions transactionOptions) { Objects.requireNonNull(transactionContext, "transactionContext is required"); ITransaction transaction; if (!isEnabled()) { //... } else if (!options.getInstrumenter().equals(transactionContext.getInstrumenter())) { //... } else if (!options.isTracingEnabled()) { //... } else { final SamplingContext samplingContext = new SamplingContext(transactionContext, transactionOptions.getCustomSamplingContext()); @NotNull TracesSamplingDecision samplingDecision = tracesSampler.sample(samplingContext); transactionContext.setSamplingDecision(samplingDecision); transaction = new SentryTracer( transactionContext, this, transactionOptions, transactionPerformanceCollector); // The listener is called only if the transaction exists, as the transaction is needed to // stop it if (samplingDecision.getSampled() && samplingDecision.getProfileSampled()) { final ITransactionProfiler transactionProfiler = options.getTransactionProfiler(); transactionProfiler.onTransactionStart(transaction); } } if (transactionOptions.isBindToScope()) { configureScope(scope -> scope.setTransaction(transaction)); } return transaction; }
根据传入的transactionContext和transactionOptions创建一个SamplingContext对象,调用tracesSampler.sample(samplingContext)获取当前activity的采样率samplingDecision,再创建一个SentryTracer对象transaction。接着获取AndroidTransactionProfiler对象transactionProfiler,调用其onTransactionStart()方法开始跟踪(前面的章节已经讲过了AndroidTransactionProfiler)。onActivityCreated()创建Transaction的过程讲完了,我们再来看看在onActivityResumed()时调用finishAppStartSpan()进行上报的实现:
//ActivityLifecycleIntegration.java private void finishAppStartSpan() { final @Nullable SentryDate appStartEndTime = AppStartState.getInstance().getAppStartEndTime(); if (performanceEnabled && appStartEndTime != null) { finishSpan(appStartSpan, appStartEndTime); } }
//ActivityLifecycleIntegration.java private void finishSpan( final @Nullable ISpan span, final @NotNull SentryDate endTimestamp, final @Nullable SpanStatus spanStatus) { if (span != null && !span.isFinished()) { final @NotNull SpanStatus status = spanStatus != null ? spanStatus : span.getStatus() != null ? span.getStatus() : SpanStatus.OK; span.finish(status, endTimestamp); } }
跟踪到SentryTracer. finish()方法:
//SentryTracer.java @Override @ApiStatus.Internal public void finish(@Nullable SpanStatus status, @Nullable SentryDate finishDate) { finish(status, finishDate, true); }
//SentryTracer.java @Override public void finish( @Nullable SpanStatus status, @Nullable SentryDate finishDate, boolean dropIfNoChildren) { //... ProfilingTraceData profilingTraceData = null; if (Boolean.TRUE.equals(isSampled()) && Boolean.TRUE.equals(isProfileSampled())) { profilingTraceData = hub.getOptions() .getTransactionProfiler() .onTransactionFinish(this, performanceCollectionData); } //... final SentryTransaction transaction = new SentryTransaction(this); final TransactionFinishedCallback finishedCallback = transactionOptions.getTransactionFinishedCallback(); if (finishedCallback != null) { finishedCallback.execute(this); } //... transaction.getMeasurements().putAll(measurements); hub.captureTransaction(transaction, traceContext(), null, profilingTraceData); } }
先从options拿到AndroidTransactionProfiler对象再调用其onTransactionFinish()方法封装成ProfilingTraceData(在之前的章节已经介绍过ProfilingTraceData了)。然后创建一个SentryTransaction对象,最后调用 hub.captureTransaction(transaction, traceContext(), null, profilingTraceData)上报事件:
//Hub.java @ApiStatus.Internal @Override public @NotNull SentryId captureTransaction( final @NotNull SentryTransaction transaction, final @Nullable TraceContext traceContext, final @Nullable Hint hint, final @Nullable ProfilingTraceData profilingTraceData) { Objects.requireNonNull(transaction, "transaction is required"); SentryId sentryId = SentryId.EMPTY_ID; if (!isEnabled()) { //... } else { if (!transaction.isFinished()) { //... } else { if (!Boolean.TRUE.equals(transaction.isSampled())) { //... } else { StackItem item = null; try { item = stack.peek(); sentryId = item.getClient() .captureTransaction( transaction, traceContext, item.getScope(), hint, profilingTraceData); } catch (Throwable e) { //... } } } } return sentryId; }
调用了SentryClient的captureTransaction()方法。这个具体流程不讲了,跟captureEvent()类似,只是封装的数据结构不太一样。最终都是调用到AsyncHttpTransport.send()方法,流程一致。
到此为止,Sentry提供的针对SentryEvent和SentryTransaction两种事件的上报已经分析完毕了。
05
定制化APM系统的初步想法
分析完整个Sentry的实现后,我们意识到如果希望定制自己的APM,完全可以以Sentry为base进行一些拓展和改造。比如说根据需求创建自己的Integration为Sentry增加新的能力;创建EventProcessor随事件上报新的参数;重写AndroidTransactionProfiler添加新的性能数据;结合其他三方APM相关库的输出作为附件封装成事件上报等。
总而言之就是将我们希望监测的性能数据与Sentry的基本能力和参数进行绑定,复用Sentry的数据结构,上报到Sentry后台或者其他后台。
06
总结
这篇文章相信分析了Sentry(Android端)实现的具体流程,希望能给大家定制化APM系统一些参考和想法。
还没有评论,来说两句吧...