WeakReference 弱引用與內(nèi)存泄漏

compile 'com.anthonycr.grant:permissions:1.1.2'

版本更新后,作者為了避免內(nèi)存泄漏,在源碼中添加了一個(gè)弱引用來(lái)存儲(chǔ)一個(gè)抽象類。

但在用戶有多個(gè)權(quán)限需要選擇的時(shí)候,抽象類有時(shí)候會(huì)被回收。當(dāng)用戶確認(rèn)權(quán)限之后,沒(méi)有調(diào)用到回調(diào)函數(shù)。

android內(nèi)存空間分配

首先, 讓我們快速看下Android啟動(dòng)流程. 與眾多基于Linux內(nèi)核的系統(tǒng)類似, 啟動(dòng)系統(tǒng)時(shí), bootloader啟動(dòng)內(nèi)核和init進(jìn)程. init進(jìn)程分裂出更多名為"daemons(守護(hù)進(jìn)程)"的底層的Linux進(jìn)程, 諸如android debug deamon, USB deamon等. 這些守護(hù)進(jìn)程處理底層硬件相關(guān)的接口.

隨后, init進(jìn)程會(huì)啟動(dòng)一個(gè)非常有意思的進(jìn)程---"Zygote". 顧名思義, 這是一個(gè)Android平臺(tái)的非常基礎(chǔ)的進(jìn)程. 這個(gè)進(jìn)程初始化了第一個(gè)VM, 并且預(yù)加載了framework和眾多App所需要的通用資源. 然后它開(kāi)啟一個(gè)Socket接口來(lái)監(jiān)聽(tīng)請(qǐng)求, 根據(jù)請(qǐng)求孵化出新的VM來(lái)管理新的App進(jìn)程. 一旦收到新的請(qǐng)求, Zygote會(huì)基于自身預(yù)先加載的VM來(lái)孵化出一個(gè)新的VM創(chuàng)建一個(gè)新的進(jìn)程.

啟動(dòng)Zygote之后, init進(jìn)程會(huì)啟動(dòng)runtime進(jìn)程. Zygote會(huì)孵化出一個(gè)超級(jí)管理進(jìn)程---System Server. SystemServer會(huì)啟動(dòng)所有系統(tǒng)核心服務(wù), 例如Activity Manager Service, 硬件相關(guān)的Service等. 到此, 系統(tǒng)準(zhǔn)備好啟動(dòng)它的第一個(gè)App進(jìn)程---Home進(jìn)程了.

app launch

當(dāng)啟動(dòng)一個(gè)Android程序時(shí),會(huì)啟動(dòng)一個(gè)Dalvik VM進(jìn)程,系統(tǒng)會(huì)給它分配固定的內(nèi)存空間(16M,32M不定),這塊內(nèi)存空間會(huì)映射到RAM上某個(gè)區(qū)域。然后這個(gè)Android程序就會(huì)運(yùn)行在這塊空間上。Java里會(huì)將這塊空間分成Stack棧內(nèi)存和Heap堆內(nèi)存。stack里存放對(duì)象的引用,heap里存放實(shí)際對(duì)象數(shù)據(jù)。
在程序運(yùn)行中會(huì)創(chuàng)建對(duì)象,如果未合理管理內(nèi)存,比如不及時(shí)回收無(wú)效空間就會(huì)造成內(nèi)存泄露,嚴(yán)重的話可能導(dǎo)致使用內(nèi)存超過(guò)系統(tǒng)分配內(nèi)存,即內(nèi)存溢出OOM,導(dǎo)致程序卡頓甚至直接退出。

dalvik的Heap和Stack

也就是帶有回調(diào)函數(shù)的對(duì)象會(huì)放到內(nèi)存堆中。當(dāng)然,一般處理內(nèi)存泄漏都是處理內(nèi)存堆,這里只是提一下。

弱引用

在Java里, 當(dāng)一個(gè)對(duì)象o被創(chuàng)建時(shí), 它被放在Heap里. 當(dāng)GC運(yùn)行的時(shí)候, 如果發(fā)現(xiàn)沒(méi)有任何引用指向o, o就會(huì)被回收以騰出內(nèi)存空間. 或者換句話說(shuō), 一個(gè)對(duì)象被回收, 必須滿足兩個(gè)條件: 1)沒(méi)有任何引用指向它 2)GC被運(yùn)行

   private synchronized void addPendingAction(@NonNull String[] permissions,
                                               @Nullable PermissionsResultAction action) {
        if (action == null) {
            return;
        }
        action.registerPermissions(permissions);
        mPendingActions.add(new WeakReference<>(action));
    }
    public synchronized void notifyPermissionsChange(@NonNull String[] permissions, @NonNull int[] results) {
        int size = permissions.length;
        if (results.length < size) {
            size = results.length;
        }
        Iterator<WeakReference<PermissionsResultAction>> iterator = mPendingActions.iterator();
        while (iterator.hasNext()) {
            PermissionsResultAction action = iterator.next().get();
            for (int n = 0; n < size; n++) {
                if (action == null || action.onResult(permissions[n], results[n])) {
                    iterator.remove();
                    break;
                }
            }
        }
        for (int n = 0; n < size; n++) {
            mPendingRequests.remove(permissions[n]);
        }
    }

在源碼中執(zhí)行到這兒的時(shí)候,action有時(shí)候變成了null 。

在addPendingAction操作中有PermissionsResultAction(強(qiáng)引用)引用指向,但到notifyPermissionsChange()的時(shí)候PermissionsResultAction依然被系統(tǒng)回收了,回調(diào)函數(shù)不被執(zhí)行。

這是因?yàn)榫幾g器在發(fā)現(xiàn)進(jìn)入while循環(huán)之后, PermissionsResultAction已經(jīng)沒(méi)有被使用, 所以進(jìn)行了優(yōu)化(將其置空).

寫了一段測(cè)試代碼,對(duì)象最后的確被回收了。

  public static void main(String[] args) {

        List<WeakReference<PermissionAction>> mPendingActions = new ArrayList<>(1);


        mPendingActions.add(new WeakReference<>(new PermissionAction()));


        int i = 0;
        WeakReference<PermissionAction> actionPermission = null;

        Iterator<WeakReference<PermissionAction>> iterator = mPendingActions.iterator();

        if(iterator.hasNext()){
            actionPermission = iterator.next();
        }

        while (true) {
            PermissionAction action = actionPermission.get();

            if (action != null) {
                i++;
                System.out.println("Object is alive for " + i + " loops - " + action);
            } else {
                System.out.println("Object has been collected.");
                break;
            }

        }

    }
  • WeakReference的一個(gè)特點(diǎn)是它何時(shí)被回收是不可確定的, 因?yàn)檫@是由GC運(yùn)行的不確定性所確定的. 所以, 一般用weak reference引用的對(duì)象是有價(jià)值被cache, 而且很容易被重新被構(gòu)建, 且很消耗內(nèi)存的對(duì)象.

雖然弱引用能讓app避免了內(nèi)存溢出的問(wèn)題,但也帶來(lái)了不確定性。

弱引用可以用于Handler,一般的Handler寫法可能會(huì)導(dǎo)致內(nèi)存泄漏。因?yàn)榉庆o態(tài)的內(nèi)部類持有外部類的對(duì)象,而handler又會(huì)由于msg的處理而可能常駐在進(jìn)程中,在activity或者service destroy后,不能及時(shí)被系統(tǒng)回收,導(dǎo)致內(nèi)存泄漏。
建議寫法:

private static class OuterHandler extends Handler {
    private final WeakReference<MainActivity> mActivity;
         
    public OuterHandler(MainActivity activity) {
      mActivity = new WeakReference<MainActivity>(activity);
    }
         
    @Override
    public void handleMessage(Message msg) {
      MainActivity activity = mActivity.get();
      if (activity != null) {
        // do something...
      }
    }
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容