Android性能優(yōu)化之內(nèi)存優(yōu)化

導(dǎo)讀

讀完本篇能學(xué)到以下知識

  • 解決Activity的內(nèi)存泄漏
  • Bitmap加載優(yōu)化

前言

內(nèi)存優(yōu)化是Android中優(yōu)化的一個重點(diǎn),內(nèi)存優(yōu)化不到位會引起頻繁的GC,導(dǎo)致耗電嚴(yán)重.

要做內(nèi)存優(yōu)化首先要找到優(yōu)化的對象.在Android開發(fā)中有兩個內(nèi)存大戶,Activity和Bitmap.Activity主要是防止內(nèi)存泄漏,Bitmap需要防止oom

Activity的內(nèi)存泄漏

所謂內(nèi)存泄漏就是這個對象至少有一條到達(dá)根節(jié)點(diǎn)的路徑.
那有哪些是根節(jié)點(diǎn)呢?
Java虛擬機(jī)中的根節(jié)點(diǎn)如下:

  • 方法區(qū)中類靜態(tài)屬性引用的對象
  • 方法區(qū)中常量引用的對象
  • 本地方法棧中JNI(即一般說的Native方法)引用的對象
  • Thread

還有比較隱蔽的內(nèi)部類,內(nèi)部類會默認(rèn)持有外部類的引用要注意下.
內(nèi)存泄漏情景

  1. 靜態(tài)引用持有
public class TestActivity extends Activity{
    public static List list = new ArrayList();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        list.add(this);
    }
}

解決方法:及時remove會避免靜態(tài)引用持有.

  1. Handler發(fā)送消息
public class TestActivity extends Activity{
    Handler handler = new Handler(){
    
        @Override
        public void handleMessage(){
            ...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        handler.sendMessageDelay(msg, 100000);
    }
}

因?yàn)閔andler現(xiàn)在是個匿名內(nèi)部類,所以持有Activity的引用,一旦有未發(fā)送的Message,Activity就會內(nèi)存泄漏.

解決方法

1. 在onDestroy()里讓handler移除所有消息
Activity:
...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        handler.removeMessage(...);
    }
...
2. 定義一個繼承Handler的static類,用WeakReference持有Activity(如果需要調(diào)用Activity的話)
class Activity{
    MyHandler handler;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        handler = new MyHandler(this);
    }
    
    private static class MyHandler extends Handler{
    private WeakReference<Context> weakRef;
    
        public Handler(Context context){
            weakRef = new WeakReference<>(context);
        }
        
        @Override
        public void handleMessage(){
            ...
        }
    }
}
  1. 執(zhí)行Thread
class Activity{
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        new Thread(new Runnable(){
        
            @Override
            public void run(){
                //耗時操作
            }
        }).start();
    }
    
}

解決線程導(dǎo)致的內(nèi)存泄漏跟Handler類似

1. 在onDestroy()里調(diào)用Thread.interrupt()結(jié)束線程

2. 把線程定義成靜態(tài)內(nèi)部類用WeakReference持有Activity

檢測內(nèi)存泄漏方法

  1. adb命令(最簡單,我最喜歡用)
    adb shell dumpsys meminfo [包名]

用這個命令開始的時候打印一次,然后打開關(guān)閉泄漏Activity一定次數(shù),接著觸發(fā)GC(Profile里Memory的垃圾桶圖標(biāo)),最后再打印一次這個命令,看看兩次Activities的數(shù)量差是否和打開泄漏Activity次數(shù)一樣.如果不一樣說明部分(GC并不一定回收所有對象)Activity已經(jīng)被回收了,沒有內(nèi)存泄露.反之則內(nèi)存泄漏.

           Views:      189         ViewRootImpl:        1
     AppContexts:       11           Activities:        1
          Assets:        2        AssetManagers:        2
   Local Binders:       15        Proxy Binders:       25
   Parcel memory:        4         Parcel count:       16
Death Recipients:        0      OpenSSL Sockets:        0
  1. 第一種方法前提是有懷疑泄漏的對象.如果沒有懷疑對象,那可以用LeakCanary.LeakCanary接入到應(yīng)用中會定期觸發(fā)GC來判斷是否內(nèi)存泄露,一泄露就會給你提示.

  2. MAT
    用MAT檢測內(nèi)存泄露步驟跟1類似,就是去對比兩次dump出來的內(nèi)存信息,但MAT功能更強(qiáng)大.他可以找出泄露對象被誰持有.
    泄漏代碼:

Activity:
 
    public static List list = new ArrayList();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        list.add(this);
    }

具體操作如下:

  • dump出當(dāng)前heap快照
  • 用命令 hprof-conv 1.hprof 2.hprof轉(zhuǎn)換hprof文件(AS生成的hprof需要轉(zhuǎn)換才能被MAT識別)
    用Leak Suspects Report打開2.hprof文件,點(diǎn)擊Histogram,
    Show objects by class


    1.png

    Merge Shortest Paths to GC Roots-->exclude all phantom/weak/soft etc.reference


    2.png

    現(xiàn)在就可以看到持有該Activity的是一個list
    3.png

Bitmap優(yōu)化

  1. 防止OOM
    有時我們加載Bitmap,需要的只是一個很小的圖,但從本地或網(wǎng)絡(luò)加載的圖片非常的大,稍不注意就會報oom.
    解決方法如下:
Bitmap bm;
BitmapFactory.Options opt = new BitmapFactory.Options(); 
//設(shè)置只加載大小不加載像素數(shù)據(jù)
opt.inJustDecodeBounds = true;
bm = BitmapFactory.decodeFile(absolutePath, opt);

//計算出你需要的比例
opt.inSampleSize = bm.getHeight()/neededHeight;//偽代碼

//inJustDecodeBounds設(shè)為true去加載像素數(shù)據(jù)
opt.inJustDecodeBounds = false;
bm = BitmapFactory.decodeFile(absolutePath, opt);

就是先加載原始圖的大小,接著計算出需要的比例傳給inSampleSize,再加載一遍圖片.

  1. 設(shè)備分級
    對于一些低端機(jī)我們可以選擇降低圖片質(zhì)量(比如RGB_565)
  2. 緩存
    圖片緩存一般采取Lru的策略,其核心思想是“如果數(shù)據(jù)最近被訪問過原杂,那么將來被訪問的幾率也更高”.實(shí)現(xiàn)Lru策略可以用LinkedHashMap這個類構(gòu)造的時候傳入一個accessOrder為true
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

這時LinkedHashMap就會生成一個雙端隊(duì)列,get的時候把值插到最前面實(shí)現(xiàn)Lru的策略效果,實(shí)現(xiàn)原理可以參考這篇文章.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌端壳,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衍腥,死亡現(xiàn)場離奇詭異绪穆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蓖扑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門唉铜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人律杠,你說我怎么就攤上這事打毛。” “怎么了俩功?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碰声。 經(jīng)常有香客問我诡蜓,道長,這世上最難降的妖魔是什么胰挑? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任蔓罚,我火速辦了婚禮,結(jié)果婚禮上瞻颂,老公的妹妹穿的比我還像新娘豺谈。我一直安慰自己,他們只是感情好贡这,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布茬末。 她就那樣靜靜地躺著,像睡著了一般盖矫。 火紅的嫁衣襯著肌膚如雪丽惭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天辈双,我揣著相機(jī)與錄音责掏,去河邊找鬼。 笑死湃望,一個胖子當(dāng)著我的面吹牛换衬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播证芭,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瞳浦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了檩帐?” 一聲冷哼從身側(cè)響起术幔,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎湃密,沒想到半個月后诅挑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體四敞,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年拔妥,在試婚紗的時候發(fā)現(xiàn)自己被綠了忿危。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡没龙,死狀恐怖铺厨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硬纤,我是刑警寧澤解滓,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站筝家,受9級特大地震影響洼裤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜溪王,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一腮鞍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莹菱,春花似錦移国、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜜徽,卻和暖如春裹芝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背娜汁。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工嫂易, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掐禁。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓怜械,卻偏偏與公主長得像,于是被迫代替她去往敵國和親傅事。 傳聞我的和親對象是個殘疾皇子缕允,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354