Android內(nèi)存優(yōu)化--常見的內(nèi)存泄漏以及優(yōu)化方案

無用的內(nèi)存(沒有使用的對象)仍然被其他對象持有引用导饲,造成該對象無法被系統(tǒng)回收讯嫂,以致該對象在堆中所占用的內(nèi)存單元無法被釋放而造成內(nèi)存空間浪費(fèi)浇垦,這中情況就是內(nèi)存泄露炕置。

在開發(fā)的過程中,我們的一些編程習(xí)慣有可能會導(dǎo)致app內(nèi)存溢出情況溜族,下面舉簡單的幾個(gè)例子說明:

1讹俊、單利模式:

單利模式在開發(fā)中我們經(jīng)常使用垦沉,如果使用不當(dāng)就會造成內(nèi)存泄漏煌抒,單利模式都是靜態(tài)的,它的生命周期一般都會很長厕倍,如果一個(gè)對象已經(jīng)沒有用處了寡壮,但是單例還持有它的引用,那么在整個(gè)應(yīng)用程序的生命周期它都不能正常被回收讹弯,從而導(dǎo)致內(nèi)存泄露况既。說了這么多,上代碼:

    public class Books {

        public static Books instance;

        private Context context;

        private Books(Context context){
              this.context = context;`
          }

        public static Books getInstance(Context context){
             if (instance==null){
                  instance =new Books(context);
             } 
          return instance;
      }
    }

在上面的代碼片段中,在調(diào)用getInstance(context)方法時(shí)组民,傳入context棒仍,activity,service上下文都會造成內(nèi)存泄漏臭胜。

activity為例莫其,當(dāng)我們啟動(dòng)一個(gè)activity 在其中調(diào)用getInstance(context)傳入this獲取Books單例,這樣Books類的單例instance就持有了Activity的引用耸三,當(dāng)退出activity時(shí)乱陡,activity便銷毀,然而Books單利為靜態(tài)單利仪壮,在應(yīng)用程序的整個(gè)生命周期中存在 會繼續(xù)持有這個(gè)activity的引用憨颠,導(dǎo)致這個(gè)activity對象無法被回收釋放,這就造成了內(nèi)存泄露积锅。

為了避免這樣單例導(dǎo)致內(nèi)存泄露爽彤,我們可以將context參數(shù)改為全局的上下文:

    private Books(Context context){
        this.context = context.getApplication();
    }

全局的上下文Application Context就是應(yīng)用程序的上下文养盗,和單例的生命周期一樣長,這樣就避免了內(nèi)存泄漏适篙。

單例模式對應(yīng)應(yīng)用程序的生命周期爪瓜,所以我們在構(gòu)造單例的時(shí)候盡量避免使用activity的上下文,而是使用Application的上下文匙瘪。

2铆铆、靜態(tài)變量:

靜態(tài)變量存儲在方法區(qū),它的生命周期從類加載開始丹喻,到整個(gè)進(jìn)程結(jié)束薄货。一旦靜態(tài)變量初始化后,它所持有的引用只有等到進(jìn)程結(jié)束才會釋放碍论。

在Android開發(fā)中谅猾,靜態(tài)持有很多時(shí)候都有可能因?yàn)槠涫褂玫纳芷诓灰恢露鴮?dǎo)致內(nèi)存泄露,所以我們在新建靜態(tài)持有的變量的時(shí)候需要多考慮一下各個(gè)成員之間的引用關(guān)系鳍悠,并且盡量少地使用靜態(tài)持有的變量税娜,以避免發(fā)生內(nèi)存泄露。當(dāng)然藏研,我們也可以在適當(dāng)?shù)臅r(shí)候講靜態(tài)量重置為null敬矩,使其不再持有引用,這樣也可以避免內(nèi)存泄露蠢挡。

3弧岳、動(dòng)畫及吐司:

動(dòng)畫在開發(fā)中是一個(gè)不能少的環(huán)節(jié),也是一個(gè)很耗時(shí)的操作业踏,然而在開發(fā)中很多時(shí)候都忘記對動(dòng)畫進(jìn)行關(guān)閉禽炬,即使當(dāng)前界面已經(jīng)銷毀,我們看不見動(dòng)畫的存在勤家,只要沒有調(diào)用cancel() 便依然執(zhí)行腹尖,從而導(dǎo)致activity或者fragment中的內(nèi)存無法釋放,資源無法得到及時(shí)的回收伐脖。

     @Override
        protected void onDestroy() {
            super.onDestroy();
            if (objectAnimator!=null){
                objectAnimator.cancel();
                objectAnimator = null;
            }
            if(toast!=null){
                toast.cancel();
              }
         }

4热幔、非靜態(tài)內(nèi)部類:
非靜態(tài)內(nèi)部類(包括匿名內(nèi)部類)默認(rèn)就會持有外部類的引用,當(dāng)非靜態(tài)內(nèi)部類對象的生命周期比外部類對象的生命周期長時(shí)晓殊,就會導(dǎo)致內(nèi)存泄露断凶。

非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄露在Android開發(fā)中有一種典型的場景就是使用Handler,很多開發(fā)者在使用Handler是這樣寫的:

    public class MainActivity extends AppCompatActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            start();
        }
    
        private void start() {
            Message msg = Message.obtain();
            msg.what = 1;
            mHandler.sendMessage(msg);
        }
    
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == 1) {
                     // todo
                }
            }
          };
      }

在上面的代碼片段中雖然沒有內(nèi)部類的寫法巫俺,也不會造成內(nèi)存认烁。

熟悉Handler消息機(jī)制的都知道,Handler會作為成員變量保存在發(fā)送的消息msg中,即msg持有Handler的引用却嗡,而Handler是Activity的非靜態(tài)內(nèi)部類實(shí)例舶沛,即Handler持有Activity的引用,那么我們就可以理解為msg間接持有Activity的引用窗价。msg被發(fā)送后先放到消息隊(duì)列MessageQueue中如庭,然后等待Looper的輪詢處理(MessageQueue和Looper都是與線程相關(guān)聯(lián)的,MessageQueue是Looper引用的成員變量撼港,而Looper是保存在ThreadLocal中的)坪它。那么當(dāng)Activity退出后,msg可能仍然存在于消息對列MessageQueue中未處理或者正在處理帝牡,那么這樣就會導(dǎo)致Activity無法被回收往毡,以致發(fā)生Activity的內(nèi)存泄露。
通常在Android開發(fā)中如果要使用內(nèi)部類靶溜,但又要規(guī)避內(nèi)存泄露开瞭,一般都會采用靜態(tài)內(nèi)部類+弱引用的方式。

    public class MainActivity extends AppCompatActivity {
    
        private Handler mHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler = new MyHandler(this);
            start();
        }
    
        private void start() {
            Message msg = Message.obtain();
            msg.what = 1;
            mHandler.sendMessage(msg);
        }
    
        private static class MyHandler extends Handler {
    
            private WeakReference<MainActivity> activityWeakReference;
    
            public MyHandler(MainActivity activity) {
                activityWeakReference = new WeakReference<>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = activityWeakReference.get();
                if (activity != null) {
                    if (msg.what == 1) {
                        // todo
                    }
                }
            }
        }
    }

mHandler通過弱引用的方式持有Activity罩息,當(dāng)GC執(zhí)行垃圾回收時(shí)嗤详,遇到Activity就會回收并釋放所占據(jù)的內(nèi)存單元。這樣就不會發(fā)生內(nèi)存泄露了瓷炮。
上面的做法確實(shí)避免了Activity導(dǎo)致的內(nèi)存泄露葱色,發(fā)送的msg不再已經(jīng)沒有持有Activity的引用了,但是msg還是有可能存在消息隊(duì)列MessageQueue中崭别,所以更好的是在Activity銷毀時(shí)就將mHandler的回調(diào)和發(fā)送的消息給移除掉冬筒。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

5恐锣、未取消注冊

比如我們在activity中注冊了廣播茅主,在銷毀activity時(shí)沒有對廣播進(jìn)行取消,那么這個(gè)廣播會一直存在系統(tǒng)中土榴,同上面所說的非靜態(tài)內(nèi)部類一樣持有Activity引用诀姚,導(dǎo)致內(nèi)存泄露。因此注冊廣播后在Activity銷毀后一定要取消注冊玷禽。

6赫段、集合和資源未關(guān)閉和置空

這個(gè)比較好理解,如果一個(gè)對象放入到ArrayList矢赁、HashMap等集合中糯笙,這個(gè)集合就會持有該對象的引用。當(dāng)我們不再需要這個(gè)對象時(shí)撩银,也并沒有將它從集合中移除给涕,這樣只要集合還在使用(而此對象已經(jīng)無用了),這個(gè)對象就造成了內(nèi)存泄露。并且如果集合被靜態(tài)引用的話够庙,集合里面那些沒有用的對象更會造成內(nèi)存泄露了恭应。所以在使用集合時(shí)要及時(shí)將不用的對象從集合remove,或者clear集合耘眨,以避免內(nèi)存泄漏昼榛。

在使用IO、File流或者Sqlite剔难、Cursor等資源時(shí)要及時(shí)關(guān)閉胆屿。這些資源在進(jìn)行讀寫操作時(shí)通常都使用了緩沖,如果及時(shí)不關(guān)閉偶宫,這些緩沖對象就會一直被占用而得不到釋放莺掠,以致發(fā)生內(nèi)存泄露。因此我們在不需要使用它們的時(shí)候就及時(shí)關(guān)閉读宙,以便緩沖能及時(shí)得到釋放彻秆,從而避免內(nèi)存泄露。

總結(jié)

開發(fā)過程中內(nèi)存管理是一個(gè)重點(diǎn)结闸,也培養(yǎng)程序員對系統(tǒng)資源的管理唇兑,內(nèi)存泄漏最終會導(dǎo)致內(nèi)存溢出 OOM異常。

構(gòu)建單例模式的時(shí)候盡量不要用activity做引入桦锄。

使用到靜態(tài)變量的時(shí)候扎附,在內(nèi)存回收的時(shí)候?qū)⑵潇o態(tài)變量置空。

使用動(dòng)畫和吐司的時(shí)候结耀,雖然給我們的感覺是已經(jīng)結(jié)束了留夜,只要沒有調(diào)用cancel() 內(nèi)存都會一直被占用,得不到釋放图甜。

使用靜態(tài)內(nèi)部類+軟引用代替非靜態(tài)內(nèi)部類碍粥。

及時(shí)取消廣播或者觀察者注冊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黑毅,一起剝皮案震驚了整個(gè)濱河市嚼摩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矿瘦,老刑警劉巖枕面,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缚去,居然都是意外死亡潮秘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門易结,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枕荞,“玉大人稠通,你說我怎么就攤上這事÷虿” “怎么了改橘?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長玉控。 經(jīng)常有香客問我飞主,道長,這世上最難降的妖魔是什么高诺? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任碌识,我火速辦了婚禮,結(jié)果婚禮上虱而,老公的妹妹穿的比我還像新娘筏餐。我一直安慰自己,他們只是感情好牡拇,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布魁瞪。 她就那樣靜靜地躺著,像睡著了一般惠呼。 火紅的嫁衣襯著肌膚如雪导俘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天剔蹋,我揣著相機(jī)與錄音旅薄,去河邊找鬼。 笑死泣崩,一個(gè)胖子當(dāng)著我的面吹牛少梁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矫付,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼凯沪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了技即?” 一聲冷哼從身側(cè)響起著洼,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎而叼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豹悬,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡葵陵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞻佛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脱篙。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娇钱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绊困,到底是詐尸還是另有隱情文搂,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布秤朗,位于F島的核電站煤蹭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏取视。R本人自食惡果不足惜硝皂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望作谭。 院中可真熱鬧稽物,春花似錦、人聲如沸折欠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锐秦。三九已至傀缩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間农猬,已是汗流浹背赡艰。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斤葱,地道東北人慷垮。 一個(gè)月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像揍堕,于是被迫代替她去往敵國和親料身。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361