1. 程式人生 > 其它 >Android開源庫原始碼分析(Android面試)

Android開源庫原始碼分析(Android面試)

技術標籤: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);
    }
}