Sentry(Android)源码解析

5178777f20dd1100b7bae79b884f7f6b.jpeg

5d3cb3038172cedd7d04674f7dd053d6.gif

本文字数:16030

预计阅读时间:40分钟

049ed09a1152d5df58a2ffe7726054fa.png

01

前言

Sentry是一个日志记录、错误上报、性能监控的开源框架,支持众多平台:

2dcab304a8b2529bc8e6fb7d446b8c64.jpeg

其使用方式在本文不进行说明了,大家可参照官方文档: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

初始化

在详细梳理初始化流程之前,我们把关键步骤梳理成图方便大家理解:

96d973cd4d6d8b0aa1f395cf69e822d3.jpeg

接下来我们分析具体的实现。

3.1.SentryAndroid.init()

我们先从初始化开始分析。SentryAndroid.java位于sentry-android-core这个包内。934faab6a00d824e996b4f70577cd7b0.jpeg

从类的结构来看我们发现,SentryAndroid实际上只做了初始化这个操作:f1b7227d7491ef5603f928148c0eb863.jpeg

//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 handlerpublic static synchronized void init(@NotNull final Context context,@NotNull ILogger logger,@NotNull Sentry.OptionsConfiguration<SentryAndroidOptions> 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 {@Overridepublic SentryDate now() {return new SentryNanotimeDate();}
}
//SentryNanotimeDate.javaprivate final @NotNull Date date;private final long nanos;public SentryNanotimeDate() {this(DateUtils.getCurrentDateTime(), System.nanoTime());}
//DateUtils.javapublic 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这个包下:7ce94933755313d05bcaef1f760ac8f4.jpeg

我们先来看看Sentry.init()的实现:

//Sentry.javapublic static <T extends SentryOptions> void init(final @NotNull OptionsContainer<T> clazz,final @NotNull OptionsConfiguration<T> 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 OptionsContaine<T>clazz:**

//SentryAndroid.java
OptionsContainer.create(SentryAndroidOptions.class)
//OptionsContainer.java
public final class OptionsContainer<T> {public @NotNull static <T> OptionsContainer<T> create(final @NotNull Class<T> clazz) {return new OptionsContainer<>(clazz);}private final @NotNull Class<T> clazz;private OptionsContainer(final @NotNull Class<T> clazz) {super();this.clazz = clazz;}public @NotNull T createInstance()throws InstantiationException, IllegalAccessException, NoSuchMethodException,InvocationTargetException {return clazz.getDeclaredConstructor().newInstance();}
}

OptionsContainer.create()传的是SentryAndroidOptions这个class,返回的是OptionsContainer<SentryAndroidOptions>。在Sentry.java中调用clazz.createInstance()方法执行了SentryAndroidOptions的构造方法:

//SentryAndroidOptions.javapublic SentryAndroidOptions() {setSentryClientName(BuildConfig.SENTRY_ANDROID_SDK_NAME + "/" + BuildConfig.VERSION_NAME);setSdkVersion(createSdkVersion());setAttachServerName(false);// enable scope sync for Android by defaultsetEnableScopeSync(true);}

我们看做了一些Android相关的基础配置。SentryAndroidOptions的父类OptionsContainer的构造方法如下:

//SentryOptions.javaprivate SentryOptions(final boolean empty) {if (!empty) {// SentryExecutorService should be initialized before any// SendCachedEventFireAndForgetIntegrationexecutorService = new SentryExecutorService();// UncaughtExceptionHandlerIntegration should be inited before any other Integration.// if there's an error on the setup, we are able to capture itintegrations.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.javaSentryExecutorService() {this(Executors.newSingleThreadScheduledExecutor(new SentryExecutorServiceThreadFactory()));}
//SentryExecutorService.java@Overridepublic @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.javapublic 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() {}@Overridepublic Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() {return Thread.getDefaultUncaughtExceptionHandler();}@Overridepublic 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,看一下它的结构:bed8bc754031932b72606b933b03f015.jpeg

先看一下register()都做了什么:

//UncaughtExceptionHandlerIntegration.java@Overridepublic 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@Overridepublic 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.javaprivate SentryOptions(final boolean empty) {if (!empty) {// SentryExecutorService should be initialized before any// SendCachedEventFireAndForgetIntegrationexecutorService = new SentryExecutorService();// UncaughtExceptionHandlerIntegration should be inited before any other Integration.// if there's an error on the setup, we are able to capture itintegrations.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@Overridepublic @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;}@Overridepublic @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.javapublic static <T extends SentryOptions> void init(final @NotNull OptionsContainer<T> clazz,final @NotNull OptionsConfiguration<T> 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  @NotNullOptionsConfiguration<T>optionsConfiguration

OptionsConfiguration是个接口,applyOptionsConfiguration()调用其configure()方法做一些基础的配置,所以回到SentryAndroid.java:

//SentryAndroid.javaoptions -> {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()方法就是检查是否能找到那几个类,正常情况下返回trueBuildInfoProvider是为了读取android.os.Build下的信息,包括判断是否为模拟器:

6ccb64dd5d9a1cbd8d5ed42c18962845.jpeg

ActivityFramesTracker是利用FrameMetricsAggregator来收集app帧渲染的时间从而观察是否有掉帧的情况发生。我们继续看AndroidOptionsInitializer.loadDefaultAndMetadataOptions()的实现:

//AndroidOptionsInitializer.javastatic 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 nullabilityif (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.javastatic 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 belowLazyEvaluator<Boolean> 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 awayoptions.addIntegration(new SendCachedEnvelopeIntegration(new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()),startupCrashMarkerEvaluator));// AppLifecycleIntegration has to be installed before AnrIntegration, because AnrIntegration// relies on AppState set by itoptions.addIntegration(new AppLifecycleIntegration());options.addIntegration(AnrIntegrationFactory.create(context, buildInfoProvider));// registerActivityLifecycleCallbacks is only available if Context is an AppContextif (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.javapublic 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@Overridepublic 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@Overridepublic 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.javaprivate 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@Overridepublic 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);}@Overridepublic 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 {@NotNullpublic 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@Overridepublic 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@Overridepublic void run() {final ActivityManager activityManager =(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);final List<ApplicationExitInfo> 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 listfinal List<ApplicationExitInfo> 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 twiceexitInfos.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.javapublic 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.javaprivate 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@Overridepublic 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@Overridepublic 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.javapublic 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@Overridepublic void onActivityResumed(@NotNull Activity activity) {startTracking(activity);}@Overridepublic void onActivityPaused(@NotNull Activity activity) {stopTracking(activity);}
//UserInteractionIntegration.javaprivate 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<String> getDefaultActions() {final List<String> 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 sdcardactions.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<GestureTargetLocator> 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<ViewHierarchyExporter> 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@Overridepublic @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<ProfilingTransactionData> transactions和measurementsMap。traceFile是在onTransactionStart()时调用系统的VMDebug.startMethodTracing()生成的trace文件。transactions记录了每一个Transaction相关的基础信息,其数据结构如下:

//ProfilingTransactionData.javaprivate @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 nameprivate @NotNull Long relativeStartNs; // timestamp in nanoseconds this transaction startedprivate @Nullable Long relativeEndNs; // timestamp in nanoseconds this transaction endedprivate @NotNull Long relativeStartCpuMs; // cpu time in milliseconds this transaction startedprivate @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.javapublic static <T extends SentryOptions> void init(final @NotNull OptionsContainer<T> clazz,final @NotNull OptionsConfiguration<T> 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.javaprivate 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 oneif (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的配置信息,包括DNSHost信息等,然后创建了一个Hub对象,用来管理ScopeSentryClient

//Hub.javapublic 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.javaprivate 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的事件分为SentryEventSentryTransaction。它们俩其实继承自同一个父类: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@Overridepublic 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.javapublic 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.javapublic 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@NotNullSentryId captureEvent(@NotNull SentryEvent event, @Nullable Hint hint);
//Hub.java@Overridepublic @NotNull SentryId captureEvent(final @NotNull SentryEvent event, final @Nullable Hint hint) {return captureEventInternal(event, hint, null);}
//Hub.javaprivate @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.javaSentryClient(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@Overridepublic @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);
//...}
//...@NullableSession sessionBeforeUpdate =scope != null ? scope.withSession((@Nullable Session session) -> {}) : null;@Nullable Session session = null;if (event != null) {// https://develop.sentry.dev/sdk/sessions/#terminal-session-statesif (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 valuesif (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<Attachment> 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  @Nullableprivate SentryEvent processEvent(@NotNull SentryEvent event,final @NotNull Hint hint,final @NotNull List<EventProcessor> eventProcessors) {for (final EventProcessor processor : eventProcessors) {try {// only wire backfillable events through the backfilling processors, skip from others, and// the other way aroundfinal 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()方法中主要是就是执行了各个EventProcessorprocess()方法去添加额外信息。executeBeforeSend(event, hint)实际上是个callback,用来给用户提供一个发送事件之前的时机进行额外的处理。之后的代码实际上都是为了构建一个SentryEnvelope对象envelope交给transport去处理。envelope会把SentryEventAttachment(比如截图),sessiontraceContext进行封装,最终调用transport.send(envelope, hint)方法。

我们来看transport.send()方法的实现:

//AsyncHttpTransport.java@Overridepublic void send(final @NotNull SentryEnvelope envelope, final @NotNull Hint hint)throws IOException {// For now no caching on envelopesIEnvelopeCache 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,最终将envelopeThatMayIncludeClientReporthintcurrentEnvelopeCache封装成EnvelopeSender交给QueuedThreadPoolExecutor处理。其中EnvelopeSender是个Runnable,我们看看其run()方法的实现:

//AsyncHttpTransport.java@Overridepublic 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.javaprivate @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 retryHintUtils.runIfHasType(hint,Retryable.class,(retryable) -> {retryable.setRetry(true);},(hint, clazz) -> {
//...});}return result;}

我们可以看到,首先就是通过envelopeCache.store(envelope, hint)将这个事件保存在本地。然后通过connection.send(envelopeWithClientReport)将事件发送至服务端,如果事件发送成功的话,再调用envelopeCache.discard(envelope)将保存在本地的事件删除。到此为止一个SentryEvent的发送流程就分析完毕了。 

简单地梳理一下发送的流程:d7c67aec517890784f96a17fe959b3d4.jpeg

下面我们再来分析一下SentryTransaction的发送流程。 

4.2.IHub.captureTransaction()

之前我们在分析ActivityLifecycleIntegration的实现时提到过,在onActivityCreated()作为一个Transaction起始点,在onActivityResumed()时作为这个Transaction统计的终点,通过调用finishAppStartSpan()来进行Transaction的上报。我们先来看看在onActivityCreated()是如何创建一个SentryTransaction的:

//ActivityLifecycleIntegration.java@Overridepublic 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.javaprivate void startTracing(final @NotNull Activity activity) {WeakReference<Activity> 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 onesstopPreviousTransactions();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 spansfinal @NotNull SentryDate ttidStartTime;if (!(firstActivityCreated || appStartTime == null || coldStart == null)) {// The first activity ttid/ttfd spans should start at the app start timettidStartTime = appStartTime;} else {// The ttid/ttfd spans should start when the previous activity called its onPause methodttidStartTime = lastPausedTime;}transactionOptions.setStartTimestamp(ttidStartTime);// we can only bind to the scope if there's no running transactionITransaction transaction =hub.startTransaction(new TransactionContext(activityName, TransactionNameSource.COMPONENT, UI_LOAD_OP),transactionOptions);setSpanOrigin(transaction);
//...}}}

截取重点部分,创建一个TransactionOptions对象设置各种参数,再封装一个TransactionContext对象记录activityName,之后调用hub.startTransaction()进行上报:

//Hub.java@ApiStatus.Internal@Overridepublic @NotNull ITransaction startTransaction(final @NotNull TransactionContext transactionContext,final @NotNull TransactionOptions transactionOptions) {return createTransaction(transactionContext, transactionOptions);}
//Hub.javaprivate @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 itif (samplingDecision.getSampled() && samplingDecision.getProfileSampled()) {final ITransactionProfiler transactionProfiler = options.getTransactionProfiler();transactionProfiler.onTransactionStart(transaction);}}if (transactionOptions.isBindToScope()) {configureScope(scope -> scope.setTransaction(transaction));}return transaction;}

根据传入的transactionContexttransactionOptions创建一个SamplingContext对象,调用tracesSampler.sample(samplingContext)获取当前activity的采样率samplingDecision,再创建一个SentryTracer对象transaction。接着获取AndroidTransactionProfiler对象transactionProfiler,调用其onTransactionStart()方法开始跟踪(前面的章节已经讲过了AndroidTransactionProfiler)。onActivityCreated()创建Transaction的过程讲完了,我们再来看看在onActivityResumed()时调用finishAppStartSpan()进行上报的实现:

//ActivityLifecycleIntegration.javaprivate void finishAppStartSpan() {final @Nullable SentryDate appStartEndTime = AppStartState.getInstance().getAppStartEndTime();if (performanceEnabled && appStartEndTime != null) {finishSpan(appStartSpan, appStartEndTime);}}
//ActivityLifecycleIntegration.javaprivate 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.Internalpublic void finish(@Nullable SpanStatus status, @Nullable SentryDate finishDate) {finish(status, finishDate, true);}
//SentryTracer.java@Overridepublic 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@Overridepublic @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;}

调用了SentryClientcaptureTransaction()方法。这个具体流程不讲了,跟captureEvent()类似,只是封装的数据结构不太一样。最终都是调用到AsyncHttpTransport.send()方法,流程一致。 

到此为止,Sentry提供的针对SentryEventSentryTransaction两种事件的上报已经分析完毕了。

05

定制化APM系统的初步想法

分析完整个Sentry的实现后,我们意识到如果希望定制自己的APM,完全可以以Sentrybase进行一些拓展和改造。比如说根据需求创建自己的IntegrationSentry增加新的能力;创建EventProcessor随事件上报新的参数;重写AndroidTransactionProfiler添加新的性能数据;结合其他三方APM相关库的输出作为附件封装成事件上报等。

总而言之就是将我们希望监测的性能数据与Sentry的基本能力和参数进行绑定,复用Sentry的数据结构,上报到Sentry后台或者其他后台。 

06

总结

这篇文章相信分析了Sentry(Android端)实现的具体流程,希望能给大家定制化APM系统一些参考和想法。

da03efa0e1204a963d91c7ba66f3e805.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/288328.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Jenkins--在Linux上使用Docker安装

一、Jenkins 简介 Jenkins是一个流行的开源自动化服务器&#xff0c;用于持续集成和持续交付&#xff08;CI/CD&#xff09;。Jenkins的核心功能主要包括以下几点&#xff1a; 持续集成&#xff1a;Jenkins可以监控版本控制系统&#xff08;如Git、SVN&#xff09;中的代码变…

RAG进阶笔记:RAG进阶

1 查询/索引部分 1.1 层次索引 创建两个索引——一个由摘要组成&#xff0c;另一个由文档块组成分两步进行搜索&#xff1a;首先通过摘要过滤出相关文档&#xff0c;接着只在这个相关群体内进行搜索 1.2 假设性问题 让LLM为每个块生成一个假设性问题&#xff0c;并将这些问…

U盘未格式化,数据恢复攻略大揭秘

U盘遭遇未格式化困境&#xff0c;数据安全岌岌可危 在日常的工作和生活中&#xff0c;U盘以其便携、容量大的特性成为了我们不可或缺的存储工具。然而&#xff0c;有时我们会遇到这样一个棘手的问题&#xff1a;当我们将U盘插入电脑时&#xff0c;却收到了“未格式化”的提示。…

畅捷通T+ Ufida.T.DI.UIP.RRA.RRATableController 远程命令执行漏洞

一、漏洞信息 漏洞名称&#xff1a;畅捷通T Ufida.T.DI.UIP.RRA.RRATableController 远程命令执行漏洞 漏洞类别&#xff1a;远程命令执行漏洞 风险等级&#xff1a;高危 二、漏洞描述 畅捷通TPlus适用于异地多组织、多机构对企业财务汇总的管理需求&#xff1b;全面支持企…

为什么 Hashtable 不允许插入 null 键 和 null 值?

1、典型回答 浅层次的来回答这个问题的答案是&#xff0c;JDK 源码不支持 Hashtable 插入 value 值为 null&#xff0c;如以下JDK 源码所示&#xff1a; 也就是JDK 源码规定了&#xff0c;如果你给 Hashtable 插入 value 值为 null 就会抛出空指针异常 并目看上面的JDK 源码可…

js算法记录

> 更多请前往 https://www.passerma.com/article/86 滑动窗口 1 给定一个矩阵&#xff0c;包含N*M个整数&#xff0c;和一个包含K个整数的数组。现在要求在这个矩阵中找一个宽度最小的子矩阵&#xff0c;要求子矩阵包含数组中所有的整数 function minSubmatrixWidth(mat…

PCL拟合并绘制平面(二)

使用RANSAC拟合点云平面 1、C实现2、效果图 普通的点云平面拟合方式在一般情况下可以得到较好的平面拟合效果&#xff0c;但是容易出现平面拟合错误或是拟合的平面不是最优的情况。此时就需要根据自己的实际使用情况&#xff0c;调整平面拟合的迭代次数以及收敛条件。 使用RAN…

vue脚手架创建项目:账号登录(利用element-ui快速开发)(取消eslint强制格式)(修改端口号)

新手看不懂&#xff0c;老手不用看系列 文章目录 一、准备工作1.1 取消强制格式检查1.2 导入依赖&#xff0c;注册依赖 二、去element-ui官网找样式写Login组件2.1 引用局部组件2.2 运行项目 三、看一下发现没问题&#xff0c;开始修改前端的代码四、修改端口号4.1 修改后端端口…

一款比 K8S 更好用的编排工具——Nomod 单机部署

上下文 最近公司需要调研类似 EMCHub 这样支持算力共享的服务。第一直觉是使用 K8S 或 K3S&#xff0c;作为 CNCF 孵化的顶级项目&#xff0c;同时也是当前云原生生态使用最广的编排系统。但是在学习 EMC Hub 源码过程中&#xff0c;偶然发现它是基于 Nomad 做的集群管理。 相…

Python学习笔记------文件操作

编码 编码就是一种规则集合&#xff0c;记录了内容和二进制间进行相互转换的逻辑。 编码有许多中&#xff0c;我们最常用的是UTF-8编码 计算机只认识0和1&#xff0c;所以需要将内容翻译成0和1才能保存在计算机中。同时也需要编码&#xff0c;将计算机保存的0和1&#xff0c…

maya导入导出bvh 自动 脚本

目录 maya打开脚本编辑器 运行打开bvh脚本 maya导出bvh脚本 maya打开脚本编辑器 打开Maya软件,点击右下角 “脚本编辑器” 运行打开bvh脚本<

Spring:面试八股

文章目录 参考Spring模块CoreContainerAOP 参考 JavaGuide Spring模块 CoreContainer Spring框架的核心模块&#xff0c;主要提供IoC依赖注入功能的支持。内含四个子模块&#xff1a; Core&#xff1a;基本的核心工具类。Beans&#xff1a;提供对bean的创建、配置、管理功能…

国内ip地址推荐,畅享网络新体验!

在数字化时代&#xff0c;IP地址不仅是网络连接的基石&#xff0c;也是互联网产业发展的重要标志。国内作为全球互联网市场的重要参与者&#xff0c;拥有众多IP地址资源。虎观代理小二旨在探索并推荐一些国内IP地址&#xff0c;分析它们的价值所在&#xff0c;并探讨如何更好地…

华为数通 HCIP-Datacom H12-831 题库补充(3/27)

2024年 HCIP-Datacom&#xff08;H12-831&#xff09;最新题库&#xff0c;完整题库请扫描上方二维码&#xff0c;持续更新。 如图所示&#xff0c;关于R4路由器通过IS-IS计算出来的IPv6路由&#xff0c;哪一选项的描述是错误的&#xff1f; A&#xff1a;R4通过IS—IS只学习到…

stm32f103c8t6学习笔记(学习B站up江科大自化协)-DMA

DMA简介 DMA主要用于协助CPU完成数据转运的工作 DMA&#xff0c;英文全称Direct Memory Access&#xff0c;DMA这个外设是可以直接访问STM32内部存储器的&#xff0c;包括运行内存SRAM&#xff0c;程序存储器flash和寄存器等等&#xff0c;DMA都有权限访问&#xff0c;所以DMA能…

PHP页面如何实现设置独立访问密码

PHP网页如果需要查看信息必须输入密码&#xff0c;验证后才可显示出内容的代码如何实现&#xff1f; 对某些php页面设置单独的访问密码,如果密码不正确则无法查看内容,相当于对页面进行了一个加密。 如何实现这个效果&#xff0c;详细教程可以参考&#xff1a;PHP页面如何实现…

基于react native的自定义轮播图

基于react native的自定义轮播图 效果示例图示例代码 效果示例图 示例代码 import React, {useEffect, useRef, useState} from react; import {Animated,PanResponder,StyleSheet,Text,View,Dimensions, } from react-native; import {pxToPd} from ../../common/js/device;c…

【Linux】进程状态(R运行状态、S睡眠状态、D磁盘休眠状态、T停止状态、X死亡状态)

目录 01.运行状态 02.睡眠状态 03.磁盘睡眠状态 04.停止状态 05.死亡状态 进程的状态会随着操作系统的调度和外部事件的发生而不断地发生转换。例如&#xff0c;一个新创建的进程经过初始化后会进入就绪态&#xff0c;等待被调度执行&#xff1b;当调度器分配处理器资源给…

Windows直接运行python程序

Windows直接运行python程序 一、新建bat脚本二、新建vbs脚本 一、新建bat脚本 新建bat批处理脚本&#xff0c;写入以下内容 echo off call conda activate pytorch python app.pyecho off&#xff1a;在此语句后所有运行的命令都不显示命令行本身&#xff0c;但是本身的指令是…

Android: Gradle 命令

一、查看整个项目依赖传递关系 x.x.x (*) 该依赖已经有了&#xff0c;将不再重复依赖。x.x.x -> x.x.x 该依赖的版本被箭头所指的版本代替。x.x.x -> x.x.x(*) 该依赖的版本被箭头所指的版本代替&#xff0c;并且该依赖已经有了&#xff0c;不再重复依赖。 1. gradlew ap…