Android內(nèi)存泄漏個(gè)人理解與分析

今天整理一下關(guān)于內(nèi)存泄漏和優(yōu)化相關(guān),這是個(gè)人最近心得,希望能夠幫助讀者。

下面我們便開(kāi)始吧。

最近組內(nèi)在討論關(guān)于內(nèi)存泄漏與優(yōu)化的問(wèn)題,每個(gè)人多多少少可能都會(huì)遇到這樣的問(wèn)題,總是覺(jué)得哪里會(huì)出現(xiàn)內(nèi)存泄漏,而網(wǎng)上對(duì)內(nèi)存泄漏和優(yōu)化的文章有一大堆,每次看的總是覺(jué)得一時(shí)能夠理解,但是自己卻總是用不到或者想不透,最近頗有心得,下面來(lái)講下關(guān)于這個(gè)問(wèn)題,結(jié)合一些例子,優(yōu)化和泄漏放在一起。

個(gè)人理解有以下幾點(diǎn):
1.首先,關(guān)于后臺(tái)服務(wù),別總是想著要?;?,現(xiàn)在Google已經(jīng)把a(bǔ)pp后臺(tái)活動(dòng)限制的死死的,別想著要反著來(lái),人家這樣做肯定是經(jīng)過(guò)仔細(xì)考慮才發(fā)布的,而且每個(gè)版本都還要加強(qiáng)限制,不懂得小伙伴可以大概了解一下Doze模式下面程序活動(dòng)情況,基本后臺(tái)活動(dòng)會(huì)不斷延時(shí)進(jìn)行,就連用的微信最近放久了也不及時(shí)不靈光了。
結(jié)論:服務(wù)Google推薦使用JobService,一般服務(wù)做前臺(tái)就行,也可以用IntentService(如果與UI無(wú)交互)

2.應(yīng)用返回為殺死,我們經(jīng)常會(huì)在主頁(yè)面重寫返回,讓用戶點(diǎn)了back之后進(jìn)入后臺(tái)頁(yè)面,這是不友好的。
建議:使用正常退出流程,讓應(yīng)用關(guān)閉當(dāng)前頁(yè)面,否則這部分頁(yè)面任然占用內(nèi)存,導(dǎo)致資源浪費(fèi)

3.關(guān)于Context引用,有Activity的Context,而一般建議使用ApplicationContext,這樣使對(duì)象引用的是Application,而不是頁(yè)面的Context,否則存在內(nèi)存泄漏,這個(gè)通俗易懂,但是什么情況是Context引用而容易導(dǎo)致內(nèi)存泄漏呢,我整理了一下,用一句話來(lái)講:線程或者靜態(tài)對(duì)象直接或者間接引用了ActivityContext。

什么是直接引用?

1.靜態(tài)對(duì)象引用

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
     //public static Object ref1;
        RefManager.ref1 = this;
    }
}

這是一個(gè)靜態(tài)對(duì)象引用case,這里存在頁(yè)面關(guān)閉activity任然被靜態(tài)指向?qū)е聝?nèi)存泄漏,我想這樣的代碼應(yīng)該每人寫。

2.線程引用

public class MyThread extends Thread{
    private Context mContext;

    public MyThread(Context mContext) {
        this.mContext = mContext;
    }
}

Thread mThread = new MyThread(this);
mThread.start();

這種case是線程直接引用Context,這種情況導(dǎo)致頁(yè)面關(guān)閉但是Thread可能還沒(méi)有執(zhí)行完成導(dǎo)致Context被引用出現(xiàn)內(nèi)存泄漏。

以上是2種直接引用this對(duì)象(Map引用也差不多),這2種情況都需要使用慎重,直接引用很容易排查,建議使用WeakReference或者在頁(yè)面推出執(zhí)行引用置空操作并且釋放資源

3.間接引用:

關(guān)于間接引用,據(jù)我理解,其實(shí)就是 非靜態(tài)內(nèi)部類/匿名內(nèi)部類 被靜態(tài)或者線程引用,為什么這樣理解呢?

3.1鏈?zhǔn)絺鬟f引用:顧名思義,就是一個(gè)A -> B -> T/S,那么T/S也是引用A的,舉例如下:

MyObject obj = new MyObject(MainActivity.this);
Thread mThread = new MyThread(obj);
mThread.start();

這里MyObject對(duì)象對(duì)Activity引用,而該對(duì)象又被MyThread引用,這么按照傳遞的效果,那么Activity也是間接被MyThread引用了

3.2非靜態(tài)內(nèi)部類:

非靜態(tài)內(nèi)部類可以訪問(wèn)外部方法(靜態(tài)內(nèi)部類則不能訪問(wèn)),也就是說(shuō),非靜態(tài)內(nèi)部類默認(rèn)可以完全訪問(wèn)外部方法,這樣外部類就是默認(rèn)被非靜態(tài)內(nèi)部類完全引用了,如果將這個(gè)非靜態(tài)內(nèi)部類傳給靜態(tài)變量或者線程,那么就好比 A -> B -> T/S , 其中A是Context/Activity,B在A里面屬于非靜態(tài)內(nèi)部類, T/S是線程或靜態(tài)變量,這樣T/S就是間接引用A,在A關(guān)閉可以導(dǎo)致內(nèi)存泄漏,當(dāng)然了,這里需要注意的是可能B經(jīng)過(guò)很多C或者D或者E,最后到T/S,這就存在一個(gè)鏈?zhǔn)介g接引用,這條鏈上面的每個(gè)元素都不會(huì)被釋放,導(dǎo)致泄漏。下面將結(jié)合3.3舉例:

3.3匿名內(nèi)部類:

匿名內(nèi)部類同3.2非靜態(tài)內(nèi)部類,匿名內(nèi)部類是由new創(chuàng)建并重寫或者實(shí)現(xiàn)某個(gè)方法,假設(shè)在A中有個(gè)匿名內(nèi)部類,那么匿名內(nèi)部類重寫或者實(shí)現(xiàn)的方法中可以調(diào)用A中所有方法,這就存在對(duì)A的引用,匿名內(nèi)部類引用了A,這時(shí)候又和上面一樣的case了,如果這個(gè)匿名內(nèi)部類經(jīng)過(guò)多次鏈?zhǔn)絺鬟f給了靜態(tài)變量或者線程,那么頁(yè)面關(guān)閉的時(shí)候,匿名內(nèi)部類不會(huì)被釋放,那么引用的A也不會(huì)被釋放,導(dǎo)致內(nèi)存泄漏,常見(jiàn)的有CallBack,Handler,new構(gòu)造的所有對(duì)象重寫或?qū)崿F(xiàn)的方法會(huì)產(chǎn)生引用A內(nèi)部方法,舉個(gè)栗子:

public class MainActivity extends Activity{

    private void init(){
        //匿名內(nèi)部類引用
        MyThread thread = new MyThread(new callBack1() {
            @Override
            public void onResult() {
                processResult();
            }
        });
        //非靜態(tài)內(nèi)部類引用
        thread.setCallBack2(new callBack2());
    }

    public interface callBack1{
        void onResult();
    }

    public class callBack2{
        public void invoke(){
            MainActivity.this.processResult();
        }
    }

    public void processResult(){
        //do something
    }
}

此例子是一個(gè)Activity里面有一個(gè)內(nèi)部類CallBack2和一個(gè)接口,首先是非靜態(tài)內(nèi)部類,使用invoke方法調(diào)用Activity的onResult,其二是匿名內(nèi)部類接口callBack1,這里內(nèi)部類接口callBack1引用了this里面方法導(dǎo)致后臺(tái)操作不會(huì)及時(shí)釋放Context。

4.資源的打開(kāi)與關(guān)閉

使用IO、File流或者Sqlite、Cursor等資源時(shí)要及時(shí)關(guān)閉,一般都是對(duì)應(yīng)寫注冊(cè)與反注冊(cè), Open與Close。

5.其他

耗時(shí)任務(wù),屬性動(dòng)畫間接引用View里面Context,Thread(Runnable),Timer引用This方法導(dǎo)致泄漏。

總結(jié):
關(guān)于內(nèi)存泄漏,目前個(gè)人理解就是對(duì)頁(yè)面Context(this)的引用,直接或者間接引用,直接引用容易發(fā)現(xiàn),間接引用需要追蹤鏈接,不管是線程還是靜態(tài)內(nèi)部類,引用方法或者view都會(huì)造成內(nèi)存泄漏。如果內(nèi)部類并沒(méi)有引用Context(this)里面的方法或者指向則不會(huì)造成泄漏。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 內(nèi)存管理的目的 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。簡(jiǎn)單粗...
    晨光光閱讀 1,373評(píng)論 1 4
  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,...
    宇宙只有巴掌大閱讀 2,492評(píng)論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏...
    _痞子閱讀 1,702評(píng)論 0 8
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏...
    apkcore閱讀 1,306評(píng)論 2 7
  • 新家搬到了高樓, 發(fā)現(xiàn)習(xí)慣住在平地上。 黃昏時(shí)打開(kāi)了窗, 晚風(fēng)竟來(lái)得猝不及防。 我俯瞰著窗外的廣場(chǎng), 花園里有人遛...
    遇上音階閱讀 334評(píng)論 0 0

友情鏈接更多精彩內(nèi)容