Android開源庫原始碼分析(Android面試)
阿新 • • 發佈:2020-12-09
技術標籤:android
LeakCanary
初始化註冊
在清單檔案中註冊了一個 ContentProvider 用於在應用啟動時初始化程式碼:
leakcanary-leaksentry/*/AndroidManifest.xml
···
<application>
<provider
android:name="leakcanary.internal.LeakSentryInstaller"
android:authorities="${applicationId}.leak-sentry-installer"
android:exported="false"/>
</application>
···
在 LeakSentryInstaller 生命週期 onCreate()
方法中完成初始化步驟:
LeakSentryInstaller.kt
internal class LeakSentryInstaller : ContentProvider () {
override fun onCreate(): Boolean {
CanaryLog.logger = DefaultCanaryLog()
val application = context!!.applicationContext as Application
InternalLeakSentry.install(application)
return true
}
···
然後分別註冊 Activity/Fragment 的監聽:
InternalLeakSentry.kt
···
fun install(application: Application) {
CanaryLog.d("Installing LeakSentry")
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalLeakSentry.application = application
val configProvider = { LeakSentry.config }
ActivityDestroyWatcher.install(
application, refWatcher, configProvider
)
FragmentDestroyWatcher.install(
application, refWatcher, configProvider
)
listener.onLeakSentryInstalled(application)
}
···
ActivityDestroyWatcher.kt
···
fun install(application: Application,refWatcher: RefWatcher,configProvider: () -> Config
) {
val activityDestroyWatcher = ActivityDestroyWatcher(refWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
···
AndroidOFragmentDestroyWatcher.kt
···
override fun watchFragments(activity: Activity) {
val fragmentManager = activity.fragmentManager
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
···
AndroidXFragmentDestroyWatcher.kt
···
override fun watchFragments(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
}
···
引用洩漏觀察
RefWatcher.kt
···
@Synchronized fun watch(watchedInstance: Any, name: String) {
if (!isEnabled()) {
return
}
removeWeaklyReachableInstances()
val key = UUID.randomUUID().toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference = KeyedWeakReference(watchedInstance, key, name, watchUptimeMillis, queue)
CanaryLog.d(
"Watching %s with key %s",
((if (watchedInstance is Class<*>) watchedInstance.toString() else "instance of ${watchedInstance.javaClass.name}") + if (name.isNotEmpty()) " named $name" else ""), key
)
watchedInstances[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableInstances()
val retainedRef = watchedInstances[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onInstanceRetained()
}
}
···
InternalLeakCanary.kt
···
override fun onReferenceRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onReferenceRetained()
}
}
···
Dump Heap
發現洩漏之後,獲取 Heamp Dump 相關檔案:
AndroidHeapDumper.kt
···
override fun dumpHeap(): File? {
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null
···
return try {
Debug.dumpHprofData(heapDumpFile.absolutePath)
if (heapDumpFile.length() == 0L) {
CanaryLog.d("Dumped heap file is 0 byte length")
null
} else {
heapDumpFile
}
} catch (e: Exception) {
CanaryLog.d(e, "Could not dump heap")
// Abort heap dump
null
} finally {
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}
···
HeapDumpTrigger.kt
···
private fun checkRetainedInstances(reason: String) {
···
val heapDumpFile = heapDumper.dumpHeap()
···
lastDisplayedRetainedInstanceCount = 0
refWatcher.removeInstancesWatchedBeforeHeapDump(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
···
啟動一個 HeapAnalyzerService 來分析 heapDumpFile:
HeapAnalyzerService.kt
···
override fun onHandleIntentInForeground(intent: Intent?) {
···
val heapAnalyzer = HeapAnalyzer(this)
val config = LeakCanary.config
val heapAnalysis =
heapAnalyzer.checkForLeaks(
heapDumpFile, config.referenceMatchers, config.computeRetainedHeapSize, config.objectInspectors,
if (config.useExperimentalLeakFinders) config.objectInspectors else listOf(
AndroidObjectInspectors.KEYED_WEAK_REFERENCE
)
)
config.analysisResultListener(application, heapAnalysis)
}
···
Heap Dump 之後,可以檢視以下內容:
- 應用分配了哪些型別的物件,以及每種物件的數量。
- 每個物件使用多少記憶體。
- 程式碼中儲存對每個物件的引用。
- 分配物件的呼叫堆疊。(呼叫堆疊當前僅在使用Android 7.1及以下時有效。)
EventBus
自定義註解
- 申明註解類
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
// 執行緒模式
ThreadMode threadMode() default ThreadMode.POSTING;
// 是否為粘性事件
boolean sticky() default false;
// 事件的優先順序
int priority() default 0;
}
- 註冊訂閱事件
@Subscribe(threadMode = ThreadMode.MAIN, priority = 1, sticky = true)
public void onEventMainThreadP1(IntTestEvent event) {
handleEvent(1, event);
}
註冊訂閱者
EventBus.getDefault().register(object);
Eventbus.java
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
- 通過反射查詢訂閱者類裡的訂閱事件,新增到
METHOD_CACHE
中
SubscriberMethodFinder.java
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
···
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
···
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
傳送事件
EventBus.getDefault().post(object);
- 根據事件型別獲取到對應的訂閱者資訊
Subscription.java
final class Subscription {
final Object subscriber;
final SubscriberMethod subscriberMethod;
···
}
EventBus.java
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
···
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
···
- 根據註冊已獲得的
Method
物件呼叫相關注冊方法
EventBus.java
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}