Java/Android中的引用類型及WeakReference應用實踐

1、背景

通常意義上而言,Java/Android中的引用類型包括強引用、軟引用、弱引用、虛引用。不一樣的引用類型具備各自適用的應用場景,並與JVM的GC直接相關。java

做爲Java/Android中的引用類型之一,WeakReference被大量的使用到系統源碼、基礎工具甚至具體的業務邏輯中。在解決須要異步使用目標對象實體、且又不影響目標對象實體的生命週期的場景中,具備自然優點。同時,還能進一步判斷目標對象實體當前所處的GC階段,如當前是否GC roots可達,亦或者已經被GC回收。android


2、四種引用類型

2.1 強引用與GC可達

默認狀況下,咱們直接指向對象的引用類型爲強引用,也是咱們每天寫代碼一定會用到的。數組

在Java/Android應用層面上,強引用更多的只是單純的概念層次上的,引用變量定義時對應的類型即爲實際指向對象的類型或其父類型。如:緩存

Person person = new Person();
複製代碼

其中,person就是一個強引用變量,指向的是Person類型的對象實體。安全

從GC的視角來看,new Person()對應的對象實體,是儲存在堆中的(逃逸先沒必要考慮)。person這個引用變量,依據實際的變量定義的位置,有可能分配在棧中存儲(如在方法中定義),也有可能分配在堆中存儲(如做爲類的字段)。bash

引用關係畫一個簡單的圖,大概以下所示:markdown

現實中,對同一個對象實體,每每會具備複雜的多個引用指向,如最多見的將對象的引用變量做爲實參傳遞,形參接收後會指向同一對象實體等等。所以,現實中的對象引用與實體關係比較複雜,可能以下:數據結構

GC時,經過可達性去分析,若是沒有強引用指向對象實體,或者即便有強引用指向,但強引用的所處的對象自身,已經不能從GC Roots可達了,這時GC,此對象實體會被垃圾回收。多線程

從對象實體的生命週期視角來看,new Person()時開始給對象分配內存空間,並調用構造器等進行初始化,此時,對象生成。一旦在GC Roots中沒有強引用直達,對象實體變成「孤魂野鬼」,對象生命週期走向完結,對應內存空間能夠被回收。app

只要對象實體存在強應用可達,就不會被垃圾回收,直至發生OOM,進程終止。


2.2 Reference 與 ReferenceQueue

Java源碼中的java.lang.ref包,對應的是應用類型和引用隊列的類定義。在Android中,對應部分具體源碼上有稍許更改,但總體上類職責與實現邏輯是相似的,不妨礙總體上的對引用類型的分析。

爲了陳述方便,同時不引發歧義,先界定幾個基本概念,以及對應的具體解釋。

1,目標對象實體。表示一般意義上建立出來的對象,例如上述強引用示例中的new Person()即表示一個Person類型的對象實體。此對象能夠被引用對象中的referent屬性去指向。

2,引用對象。由具體的引用類型類(如WeakReference、SoftReference、PhantomReference)所建立出來的對象。引用對象在建立時,外部會將目標對象實體傳入進來,從而使得引用對象中的referent屬性去指向目標對象實體

3,referent屬性引用對象中的referent屬性指向的是實際的目標對象實體

4,引用隊列引用對象建立時,由外部傳入ReferenceQueue類型的對象,引用隊列中存儲的是引用對象,而且,是會在特定狀況下由虛擬機將引用對象入隊。存在於引用隊列中的引用對象,代表此引用對象referent屬性所指向的目標對象實體已經被垃圾回收。

Reference類自己,是一個抽象類,做爲具體引用類型的基類,定義了基本的類屬性與行爲。主體類結構以下所示:

從類的註釋上能夠看出,Reference類對全部子類提供了一致的操做行爲,並在運行時是會與虛擬機中的垃圾收集器緊密協做的,實際使用中,咱們只能使用現有的Reference類的子類,或者自定義類去繼承現有的Reference類的子類。

/**
* Abstract base class for reference objects.  This class defines the
* operations common to all reference objects.  Because reference objects are
* implemented in close cooperation with the garbage collector, this class may
* not be subclassed directly.
*
* @author   Mark Reinhold
* @since    1.2
*/

public abstract class Reference<T> {

    ....
    
}
複製代碼

Reference類比較關鍵的部分摘錄以下:

public abstract class Reference<T> {
    ....
    
    private T referent;         /* Treated specially by GC */
    
    volatile ReferenceQueue<? super T> queue;
    
    /**
     * Returns this reference object's referent. If this reference object has * been cleared, either by the program or by the garbage collector, then * this method returns <code>null</code>. * * @return The object to which this reference refers, or * <code>null</code> if this reference object has been cleared */ public T get() { return this.referent; } /** * Clears this reference object. Invoking this method will not cause this * object to be enqueued. * * <p> This method is invoked only by Java code; when the garbage collector * clears references it does so directly, without invoking this method. */ public void clear() { this.referent = null; } /* -- Constructors -- */ Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; } .... } 複製代碼

能夠看出,Reference類有兩個構造器,其中T referent是一個泛型形式表示的形參,指向的是目標對象實體ReferenceQueue<? super T> queue表示的是一個引用隊列,隊列內存儲的元素是引用對象,外部調用方經過get()方法獲取目標對象實體。若是引用對象中的referent屬性爲nullget()方法將返回null

referent屬性爲null存在以下兩個觸發場景: 1,虛擬機進行垃圾回收時; 2,人爲的調用引用對象clear()方法。

其中區別在於,人爲的調用clear()方法,並不會使得此引用對象進入引用隊列

ReferenceQueue,表示引用隊列,類的職責能夠從類註釋中看出來。

/**
 * Reference queues, to which registered reference objects are appended by the
 *  * garbage collector after the appropriate reachability changes are detected.
 *  *
 * @author   Mark Reinhold
 * @since    1.2
 */
public class ReferenceQueue<T> {

    ....
    
}
複製代碼

引用隊列中存儲的元素,是引用對象,垃圾回收器會在引用對象中的目標對象實體再也不可達時,對目標對象實體進行垃圾回收,並將對應的引用對象放入引用隊列中。所以,咱們能夠經過引用對象中是否存在引用對象,去判斷對應的目標對象實體是否已經被垃圾回收。

Reference是一個抽象類,實際使用時,外部用的是其具體的子類,依據實際的需求場景,對應選擇使用WeakReferenceSoftReferencePhantomReference


2.3 軟引用

首先要說明一下,通常意義上的軟引用弱引用虛引用,實際上指的都是引用對象中的指向目標對象實體referent屬性。而非指此引用對象自己。由於此referent屬性纔是真正指向的目標對象實體,且存在於具體的引用對象中,具備具體的引用類型的特性。固然,這個特性更可能是虛擬機賦予的。

例如:衆所周知的,當目標對象實體沒有強引用可達,但有軟引用指向時,在內存不夠用時,纔會回收目標對象實體

所以,咱們發現,只要內存夠用(是否夠用由虛擬機判斷),即便目標對象實體只是軟引用可達的,目標對象實體也不會被GC,會一直存活。

能夠經過實際的例子看一下軟引用的效果。

public class SoftReferenceTest {
    public static void main(String[] args) {

        A a = new A();

        ReferenceQueue<A> rq = new ReferenceQueue<A>();
        SoftReference<A> srA = new SoftReference<A>(a, rq);

        a = null;

        if (srA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象還沒有進入垃圾回收流程" + srA.get());
        }

        // 通知系統進行垃圾回收
        System.gc();

        try {
            Thread.currentThread().sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (srA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象還沒有進入垃圾回收流程" + srA.get());
        }

        System.out.println("引用對象:" + rq.poll());
    }
}

class A {

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("in A finalize");
    }

}
複製代碼

運行結果爲:

a對象還沒有進入垃圾回收流程com.corn.javalib.A@60e53b93
a對象還沒有進入垃圾回收流程com.corn.javalib.A@60e53b93
引用對象:null
複製代碼

當a對象沒有強引用可達時,只有軟引用可達,此時,不管系統是否發生GC,a對象的生命週期依然是存活的,不會被垃圾回收。也正由於以下,引用隊列中是不存在對應的srA這個引用對象的。

上述過程對A這個類型的目標對象實體的引用關係,起始是這樣的:

當執行a = null時,此時引用關係以下:

強引用斷裂,但不影響引用對象中的對A對象這個目標對象實體的引用關係。

所以,只要內存足夠,經過引用對象get()方法,均可以獲取到A對象實體。

若是恰巧此時,內存不夠了呢,虛擬機在GC流程中,會將引用對象referent強制置爲null,此時A對象實體完全變成「孤魂野鬼」,能夠被垃圾回收。

固然,這裏須要說明一點的是,示例中只是一個demo。當方法執行完畢後,方法中所佔用的棧內存空間的引用(A a、SoftReference srA)會自動出棧,A對象實體也會自動變成「孤魂野鬼」,直至等待被垃圾回收。

實際使用中,SoftReference不必定被常常用到,雖然SoftReference能夠適當應用到如緩存等場景,但通常更通用的建議是使用如LruCache等緩存方案。


2.4 弱引用

與弱引用直接關聯的引用對象類型爲WeakReference。弱引用的特性以下:

目標對象實體沒有強引用可達,但有弱引用可達,此時,在發生GC以前,此目標對象實體都是存活的,一旦發生GC,GC過程當中會將弱引用對象中的referent屬性置爲null,並直接將此目標對象實體進行回收,並將此引用對象入隊到引用隊列中。

繼續看一個具體的示例:

public class WeakReferenceTest {
    public static void main(String[] args) {

        A a = new A();

        ReferenceQueue<A> rq = new ReferenceQueue<A>();
        WeakReference<A> wrA = new WeakReference<A>(a, rq);

        System.out.println("引用對象:" + wrA);

        a = null;

        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象還沒有進入垃圾回收流程" + wrA.get());
        }

        // 通知系統進行垃圾回收
        System.gc();

        try {
            Thread.currentThread().sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象還沒有進入垃圾回收流程" + wrA.get());
        }

        System.out.println("引用對象:" + rq.poll());
    }

    static class A {

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("in A finalize");
        }

    }
}
複製代碼

輸出結果爲:

引用對象:java.lang.ref.WeakReference@60e53b93
a對象還沒有進入垃圾回收流程com.corn.javalib.WeakReferenceTest$A@5e2de80c
in A finalize
a對象進入垃圾回收流程
引用對象:java.lang.ref.WeakReference@60e53b93
複製代碼

示例代碼中,System.gc();執行後,之因此讓當前線程sleep(1),是基於進一步確保GC線程能被調度執行考慮的。最終的輸運行結果,對應的弱引用對象,被入隊到引用隊列中,代表A對象實體已經被垃圾回收。

引用關係起初是這樣的:

執行a = null時,此時引用關係以下:

當虛擬機GC時,首先會將referent置爲null,引用關係變爲以下:

此時,A對象實體已經變成「孤魂野鬼」,能夠被垃圾回收。GC過程當中,弱引用對象入隊引用隊列

由此,咱們發現,弱引用一個強大的地方在於,弱引用本質上,是不改變目標對象實體的生命週期的,也不影響目標對象實體被GC的時機,而且,還提供了一種機制,即基於引用隊列下的,能夠直接去監測目標對象實體是否已經被GC。

這無疑是至關強大的,至關於提供了一種能夠監測到對象是否被GC的方法,且不影響到對象生命週期自己。


2.5 虛引用

不管是SoftReferenceWeakReference仍是PhantomReference,做爲Reference類的子類,自身更多隻是做爲引用類型的對象,去標記用的,類中沒有過多的自身的邏輯。與引用類型的邏輯處理過程,絕大部分都是在虛擬機中實現的。

固然,有一大不一樣的是,PhantomReference類中,重寫了T get()方法,直接返回了null

public class PhantomReference<T> extends Reference<T> {

    /**
     * Returns this reference object's referent. Because the referent of a * phantom reference is always inaccessible, this method always returns * <code>null</code>. * * @return <code>null</code> */ public T get() { return null; } /** * Creates a new phantom reference that refers to the given object and * is registered with the given queue. * * <p> It is possible to create a phantom reference with a <tt>null</tt> * queue, but such a reference is completely useless: Its <tt>get</tt> * method will always return null and, since it does not have a queue, it * will never be enqueued. * * @param referent the object the new phantom reference will refer to * @param q the queue with which the reference is to be registered, * or <tt>null</tt> if registration is not required */ public PhantomReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } } 複製代碼

也就是說,經過虛引用對象get()方法,是沒法獲取到目標對象實體的。但實際上,虛引用對象中的referent仍是指向目標對象實體的。也正由於如此,使用到虛引用對象時,每每都須要傳一個引用隊列,不然,構建的虛引用就沒有任何意義了。

虛擬機在GC時,接下來的處理流程與弱引用相似。目標對象實體被GC後,會被入隊到引用隊列中。


3、WeakReference應用實踐

3.1 WeakReference特性總結

相比SoftReferencePhantomReferenceWeakReference應用更加廣泛。主要得益於WeakReference提供的特性:
1,提供了一種監測目標對象實體是否已經被垃圾回收的方法;
2,同時不改變目標對象實體自己的生命週期;
3,對外提供了T get()方法去嘗試獲取到目標對象。

下面具體看一下WeakReference在Java/Android中的使用場景。


3.2 經過WeakReference處理Handler內存泄漏

很多人第一次接觸到WeakReference這個概念,是在Activity中的Handler可能引發的內存泄露中。

Activity中的Handler內存泄露,都比較熟。Activity中的Handler,若是以非靜態內部類的方式存在,默認會持有外部類,即Activity的引用,在Activity對象中經過Handler發出去的消息,是會被加入到消息隊列中的,待Looper不斷輪循,在MQ中取到此消息時,纔會進行消息的處理,如handleMessage。也就是說,Handler默認持有Activity的引用,同時消息處理過程總體上是異步的。此時,在消息被處理前,若是按下了如back鍵等,Activity是會出棧的,一旦GC發生,理論上此Activity對象也應該被GC,但因爲被Handler持有,致使強引用可達,內存沒法回收,且handleMessage依然能夠執行。

所以,每每都建議將Handler定義成靜態的內存類,或者外部類形式,此時,再也不默認持有Activity引用,但若是handleMessage中又須要使用到Activity中的屬性時,這種狀況下,經過WeakReference實現,就是一個極佳的使用場景。

從新梳理下上述的流程:本質上就是Activity對象中須要作一件事情,這個事情是一個將來發生的,異步的事情。最佳的指望應該是,當Acitivity對象生命週期走向完結,這件事情與Acitivity直接相關的部分應當天然終止。由於指望上,此時Activity對象已經被銷燬,甚至被垃圾回收。那與Acitivity直接相關的這部分天然也就沒有意義了。

咱們發現,這其實徹底符合WeakReference的特性,經過WeakReference對象中的T referent屬性,弱引用到Activity對象實體,當T get()null時,直接將與Activity對象有關的事情終止便可。這也是經典的Handler內存泄露的處理方式。


3.3 WeakHashMap

WeakHashMapHashMap基本實現過程是同樣的,根本的區別在於,其內部的Entry繼承的是WeakReferenceEntry中的key具備弱引用特性。具體定義以下:

/**
 * The entries in this hash table extend WeakReference, using its main ref
 * field as the key.
 */
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    final int hash;
    Entry<K,V> next;

    /**
     * Creates new entry.
     */
    Entry(Object key, V value,
          ReferenceQueue<Object> queue,
          int hash, Entry<K,V> next) {
        super(key, queue);
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }

    @SuppressWarnings("unchecked")
    public K getKey() {
        return (K) WeakHashMap.unmaskNull(get());
    }

    public V getValue() {
        return value;
    }

    public V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        K k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            V v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public int hashCode() {
        K k = getKey();
        V v = getValue();
        return Objects.hashCode(k) ^ Objects.hashCode(v);
    }

    public String toString() {
        return getKey() + "=" + getValue();
    }
}
複製代碼

所以,當Entrykey指向的目標對象實體自己沒有其餘強引用或軟引用可達時,GC發生時,此目標對象實體會被收回,Entrykey會被置爲null,而且,此Entry對象,將被入隊到引用隊列中。但直到此時,對於WeakHashMap而言,這些keynullEntry仍是做爲一個個item項存在的,依然處於以前的位置。

實際上,這些Entry已經沒有必要存在了,由於key已經從起初的指向目標對象實體變成了null,做爲key-value這種映射關係,已經發生了破壞,且key本來指向的目標對象實體生命週期也已經走向了完結。

因而,WeakHashMap提供了一種機制,去清除對應的這種狀況下的Entry。並在主要方法調用路徑中,會執行expungeStaleEntries方法。

/**
 * Expunges stale entries from the table.
 */
private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
                Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, table.length);

            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    // Must not null out e.next;
                    // stale entries may be in use by a HashIterator
                    e.value = null; // Help GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}
複製代碼

expungeStaleEntries首先從引用隊列中去一個取出對應的引用對象,實際類型即爲Entry。而後找map中找到對應的Entry,並從map中移除。

爲了將上述狀況中的keynull,與直接向map中put一個key自己就爲null區分開,WeakHashMapput時,會將keynull轉成成一個new Object()對象。並以此爲keyput到map中。

/**
 * Associates the specified value with the specified key in this map.
 * If the map previously contained a mapping for this key, the old
 * value is replaced.
 *
 * @param key key with which the specified value is to be associated.
 * @param value value to be associated with the specified key.
 * @return the previous value associated with <tt>key</tt>, or
 *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
 *         (A <tt>null</tt> return can also indicate that the map
 *         previously associated <tt>null</tt> with <tt>key</tt>.)
 */
public V put(K key, V value) {
    Object k = maskNull(key);
    int h = hash(k);
    Entry<K,V>[] tab = getTable();
    int i = indexFor(h, tab.length);

    for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
        if (h == e.hash && eq(k, e.get())) {
            V oldValue = e.value;
            if (value != oldValue)
                e.value = value;
            return oldValue;
        }
    }

    modCount++;
    Entry<K,V> e = tab[i];
    tab[i] = new Entry<>(k, value, queue, h, e);
    if (++size >= threshold)
        resize(tab.length * 2);
    return null;
}
複製代碼

關鍵語句maskNull(key);實現以下:

/**
 * Value representing null keys inside tables.
 */
private static final Object NULL_KEY = new Object();
    
/**
 * Use NULL_KEY for key if it is null.
 */
private static Object maskNull(Object key) {
    return (key == null) ? NULL_KEY : key;
}
複製代碼

固然了,取一個指定的keynullEntry也會相應轉化。

總結一下,WeakHashMap中的Entry,其實是一個弱引用對象,使得key成爲了事實上的referent,具有了弱引用特性。實際使用中,WeakHashMap中元素項的key,每每是指向具備必定生命週期的目標對象實體。如Activity做爲key,等等,這須要實際考慮具體的業務場景。


3.4 ThreadLocal

ThreadLocal爲多線程場景下的共享變量的線程安全,提供了一種方案。具體思路是將共享變量,分別放到各自線程內部的ThreadLocalMap屬性中。ThreadLocalMap,以ThreadLocal對象爲key,對應須要存入的對象爲value,對外,統一封裝在ThreadLocal類內部,並提供接口。也就是說,外界對ThreadLocalMap是無感知的。

ThreadLocal對外主要提供了T get()set(T value)remove()方法。

/**
 * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

/**
 * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

/**
 * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * {@code initialValue} method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } 複製代碼

上述方法最終都轉到了ThreadLocalMap中。ThreadLocalMap內部是數組存儲的數據結構,在必要時候進行擴容。重點看一下元素項Entry的定義:

/**
 * The entries in this hash map extend WeakReference, using
 * its main ref field as the key (which is always a
 * ThreadLocal oject).  Note that null keys (i.e. entry.get()
 * == null) mean that the key is no longer referenced, so the
 * entry can be expunged from table.  Such entries are referred to
 * as "stale entries" in the code that follows.
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
複製代碼

咱們發現,與WeakHashMap相似,ThreadLocalMap中的Entry繼承的也是WeakReferenceEntry中的key即爲referent,指向的目標對象實體ThreadLocal對象。所以,Entry中的key具有了弱引用特性。

當所指向的ThreadLocal對象生命週期完結時,Entry中的key會自動被置爲null,同時,與WeakHashMap相似,ThreadLocalMap中也提供了expungeStaleEntry去清除對應的Entry

其中具體的引用關係以下圖所示。

如此,對於線程池等線程複用的場景,即便線程對象依然存活,ThreadLocal對象也不會發生內存泄露,會隨着其自己生命週期的終結而終結。


3.5 LifeCycle

最新的Android jetpack套件中,LifeCycle是其中重要的一個組成部分。LifeCycle提供了一種對象能夠觀察組件的聲明週期的機制,並在源碼層面開始支持。總體設計上,採用的是觀察者模式,具備生命週期的被觀察的組件,是被觀察者,觀察組件生命週期的對象,是觀察者。組件針對不一樣的生命週期的變化,會發出對應的事件,並對應回調觀察者對象的中相應的方法。

ComponentActivity爲例,源碼中直接實現了LifecycleOwner接口,並初始化了mLifecycleRegistry對象。LifecycleRegistry,做爲觀察者與被觀察者的橋樑,主要完成對觀察者的註冊,並接收到被觀察者發出的事件後,分發給觀察者。

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    LifecycleOwner,
    ViewModelStoreOwner,
    SavedStateRegistryOwner,
    OnBackPressedDispatcherOwner {

....

private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
....

複製代碼

LifecycleRegistry中,存在mLifecycleOwner屬性,此對象是一個弱引用對象,其referent指向的目標對象實體LifecycleOwner,即被觀察者。對應註釋部分以下:

/**
 * The provider that owns this Lifecycle.
 * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
 * because it keeps strong references on all other listeners, so you'll leak all of them as * well. */ private final WeakReference<LifecycleOwner> mLifecycleOwner; .... 複製代碼

也就是說,LifecycleRegistry對象中對被觀察者,即擁有聲明週期的組件,如Activity、Fragment,不是直接強引用的,而是經過,mLifecycleOwner,去弱引用,防止LifecycleRegistry在被泄露的狀況下致使組件被進一步泄露。


3.6 LeakCanary實現原理

LeakCanary,做爲Android中知名的內存泄露檢測工具,可以檢測使用過程當中泄露的對象,並提供詳細的路徑等信息。

LeakCanary主要實現原理是經過WeakReference去弱引用到目標對象,並結合ReferenceQueue以實現檢測到目標對象生命週期的目的。下面以檢測Activity爲例,分析LeakCanary監測過程。

執行LeakCanary.install(context);後,會執行RefWatcher的構建。

public final class LeakCanary {

  /**
   * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
   * references (on ICS+).
   */
  public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

....
}
複製代碼

其中,buildAndInstall()方法中,會自動加上對Activity以及Fragment的監測。Activity對應的監測類是ActivityRefWatcher

/**
* Creates a {@link RefWatcher} instance and makes it available through {@link
* LeakCanary#installedRefWatcher()}.
*
* Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.
*
* @throws UnsupportedOperationException if called more than once per Android process.
*/
public RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
  throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
  if (watchActivities) {
    ActivityRefWatcher.install(context, refWatcher);
  }
  if (watchFragments) {
    FragmentRefWatcher.Helper.install(context, refWatcher);
  }
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
複製代碼

ActivityRefWatcher中,經過向Application中註冊Activity的生命週期回調接口,並在Activity onActivityDestroyed方法回調中,開始觀察。

public final class ActivityRefWatcher {
    private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
        public void onActivityDestroyed(Activity activity) {
            ActivityRefWatcher.this.refWatcher.watch(activity);
        }
    };

....

}
複製代碼

watch方法中,開始對Activity對象增長上弱引用。

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
}
複製代碼

KeyedWeakReference,繼承WeakReference。構造器中,造成referent對Activity對象的弱引用特性,並傳入了引用隊列

final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;
  public final String name;

  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}
複製代碼

ensureGoneAsync方法中,會調用ensureGone觸發GC。

public interface GcTrigger {
  GcTrigger DEFAULT = new GcTrigger() {
    @Override public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perfom a gc.
      Runtime.getRuntime().gc();
      enqueueReferences();
      System.runFinalization();
    }

    private void enqueueReferences() {
      // Hack. We don't have a programmatic way to wait for the reference queue daemon to move // references to the appropriate queues. try { Thread.sleep(100); } catch (InterruptedException e) { throw new AssertionError(); } } }; void runGc(); } 複製代碼

隨後經過判斷引用隊列中是否有此引用對象,去判斷Activity對象是否被回收。並針對未回收狀況,經過HeapDump去分析內存及堆棧詳情。

對其餘對象,如Fragment等的內存泄露監測,基本過程也是類似的。


4、結語

WeakReference自身的特性,決定了能夠被普遍的應用到實際的需求場景中,釐清WeakReference中對應的各概念,尤爲是其內部的T referent,能夠進一步加深對WeakReference的理解。並在對應的場景中,選擇對應現有的,或自實現相應的類結構,以完成目標功能的同時,減小沒必要要的內存泄露等問題。

end~

相關文章
相關標籤/搜索