Android面試題及其答案(二)

內存相關的問題在面試中被問到的概率還是比較大的,而且內存優化對於一個程序的性能而言也是至關重要的,現在就讓我們一起來學習吧!

不廢話,直接上乾貨~

一、內存泄漏

內存泄漏就是我們對某一內存空間的使用完成後沒有釋放。
主要原因:導致內存泄漏最主要的原因就是某些長存對象持有了一些其它應該被回收的對象的引用,導致垃圾回收器無法去回收掉這些對象。
出現的場景:
1.數據庫的cursor沒有關閉;
2.構造adapter時,沒有使用緩存contentview;
3.Bitmap對象不使用時採用recycle()釋放內存;
4.activity中的對象的生命週期大於activity;

二、內存溢出
內存溢出通俗理解就是軟件(應用)運行需要的內存,超出了它可用的最大內存。
出現的場景:
1、使用大的Bitmap圖片;

三、內存優化方法:
1、應該儘量避免static成員變量引用資源耗費過多的實例,比如Context。Context儘量使用Application Context,因爲Application的Context的生命週期比較長,引用它不會出現內存泄露的問題。使用WeakReference代替強引用;
2、線程也是造成內存泄露的一個重要的源頭。線程產生內存泄露的主要原因在於線程生命週期的不可控。將線程內部類,改爲靜態內部類;
3、Bitmap問題:可以說出現OutOfMemory問題的絕大多原因都是因爲Bitmap的問題。因爲Bitmap佔用的內存實在是太多了,它是一個“超級大胖子”,特別是分辨率大的圖片,如果要顯示多張那問題就更顯著了;
如何解決Bitmap帶給我們的內存問題?
1)及時的銷燬,雖然系統能夠確認Bitmap分配的內存最終會被銷燬,但是由於它佔用的內存過多,所以很可能會超過java堆的限制。因此,在用完Bitmap時,要及時的recycle掉。recycle並不能確定立即就會將Bitmap釋放掉,但是會給虛擬機一個暗示:“該圖片可以釋放了”。
2)設置一定的採樣率,有時候,我們要顯示的區域很小,沒有必要將整個圖片都加載出來,而只需要記載一個縮小過的圖片,這時候可以設置一定的採樣率,那麼就可以大大減小佔用的內存。如下面的代碼: 
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2;//圖片寬高都爲原來的二分之一,即圖片爲原來的四分之一
3)使用LruCache緩存。
4、巧妙的運用軟引用(SoftRefrence),有些時候,我們使用Bitmap後沒有保留對它的引用,因此就無法調用Recycle函數。這時候巧妙的運用軟引用,可以使Bitmap在內存快不足時得到有效的釋放;
5、儘量使用9path圖片,Adapter要使用convertView複用等等;
6、比較耗資源的對象及時的關閉,例如Cursor,各種傳感器,Service 等等;
7、使用IntentService代替service,這種Service的最大特點就是當後臺任務執行結束後會自動停止,在極大程度上避免了Service內存泄漏的可能性;
8、使用ProGuard,它除了混淆代碼之外,還具有壓縮和優化代碼的功能。ProGuard會對我們的代碼進行檢索,刪除一些無用的代碼,並且會對類、字段、方法等進行重名,重命名之後的類、字段和方法名都會比原來簡短很多,這樣的話也就對內存的佔用變得更少了;

ListView的優化方案:
1>、複用contentView:就是自定義適配器在getView方法中要考慮方法傳進來的參數contentView是否爲null,如果爲null就創建contentView並返回,如果不爲null則直接使用;在這個方法中儘可能少創建view;
2>、異步加載圖片:給contentView設置tag(setTag()),傳入一個viewHolder對象,下次可以直接調用getTag()顯示緩存中的數據,可以達到圖像數據異步加載的效果;
3>.快速滑動列表時不顯示圖片:
當快速滑動列表時(SCROLL_STATE_FLING),item中的圖片獲取需要消耗資源的view,可以不顯示出來;而處於其他兩種狀態:空閒(SCROLL_STATE_IDLE)和低俗拖動(SCROLL_STATE_TOUCH_SCROLL),則將那些view顯示出來。
如果 listview 需要顯示的 item 很多,就要考慮分頁加載。比如一共要顯示100條或者更多的時候,我們可以考慮先加載20條,等用戶拉到列表底部的時候,再去加載接下來的20 條。
4>.如果自定義的item中有圖片,需要處理圖片(減少圖片所佔內存);
1.對圖片進行邊界壓縮 2.用option類來保存圖片大小 3.避免圖片的實時縮放,最好預先縮放到視圖大小

5>.儘量避免在listview適配器中使用線程,線程是產生內存泄露的主要原因,因爲線程的生命週期不可控;


我們用具體的代碼來看看,怎麼避免OOM
事例一:

Handler mHandler = new Handler() {  
    @Override  
    public void handleMessage(Message msg) {  
        mImageView.setImageBitmap(mBitmap);
    }
}
在一個Activity中有一個Handler的內部類,Handler對象會隱式地持有該Activity的引用,而Handler通常會伴隨着一個耗時的後臺線程(例如網絡下載圖片)一起出現,這個後臺線程在任務執行完畢之後,通過消息機制通知Handler,然後Handler把圖片更新到界面。然而,如果用戶在網絡請求過程中關閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由於這時線程尚未執行完,而該線程持有Handler的引用,這個Handler又持有Activity的引用,就導致該Activity無法被回收(即內存泄露),直到網絡請求結束。
要解決這個問題有兩種辦法:
方法一:通過程序邏輯來進行保護。 
1).在關閉Activity的時候停掉你的後臺線程。線程停掉了,就相當於切斷了Handler和外部連接的線,Activity自然會在合適的時候被回收。 
2).如果你的Handler是被delay的Message持有了引用(調用了postDelayed,那麼使用Handler的removeCallbacks()或removeCallbacksAndMessages(null)方法把消息對象從消息隊列移除就行了。 
方法二:將Handler聲明爲靜態類,並且加上Activity的弱引用。
靜態類不持有外部類的對象,所以你的Activity就可以被回收了。代碼如下:
static class MyHandler extends Handler {  
    WeakReference<Activity > mActivityReference;  
    MyHandler(Activity activity) {  
        mActivityReference= new WeakReference<Activity>(activity);
    }  
    @Override  
    public void handleMessage(Message msg) {  
        final Activity activity = mActivityReference.get();
        ImageView imageView= (ImageView)activity.findViewById(R.id.imageview);
        if (activity != null) {  
            mImageView.setImageBitmap(mBitmap);
        }
    }  
}
事例二、
new Thread(new Runnable() {  
    @Override  
    public void run() {  
        SystemClock.sleep(10000);  
    }  
}).start();
在Activity裏聲明瞭一個匿名內部類,如果Activity在銷燬之前,線程的任務還未完成, 那麼將導致Activity的內存資源無法回收,造成內存泄漏。
解決辦法就是使用靜態內部類,並且及時關閉線程,如下:
private static class MyThread extends Thread {  
    private boolean mRunning = false; 
    @Override  
    public void run() {  
        mRunning = true;  
        while (mRunning) {  
            SystemClock.sleep(1000);  
        }  
    }
    public void close() {  
        mRunning = false;  
    }  
}
我們在Activity退出時,可以在onDestroy()方法中顯示的調用mThread.close();以此來結束該線程,這樣在避免Activity OOM的同時也避免了線程的內存泄漏問題。

       每一個非靜態內部類實例都會持有一個外部類的引用,若該引用是Activity的引用,那麼該Activity在被銷燬時將無法被回收。如果你的靜態內部類需要一個相關Activity的引用以確保功能能夠正常運行,那麼你得確保你在對象中使用的是一個Activity的弱引用。
       這樣看來static好像是解決OOM問題的重要武器,那是不是每個內部類都使用static來修改就好了呢,其實不然,只有當此類在全局多處用到時才這樣做,因爲static聲明變量的生命週期其實是和APP的生命週期一樣的,有點類似於Application。如果大量的使用的話,就會佔據內存空間不釋放,積少成多也會造成內存的不斷開銷,直至掛掉。static一般用來修飾基本數據類型或者輕量級對象,儘量避免修飾集合或者大對象,常用作修飾全局配置項、工具類方法、內部類。 


最後再來個擴展閱讀...
GC回收的對象?
        如果GC發現一個或一組對象爲不可到達狀態,則將該對象從內存中回收。也就是說,一個對象不被任何引用所指向,則該對象會在被GC發現的時候被回收;另外,如果一組對象中只包含互相的引用,而沒有來自它們外部的引用(例如有兩個對象A和B互相持有引用,但沒有任何外部對象持有指向A或B的引用),這仍然屬於不可到達,同樣會被GC回收。 
什麼是WeakReference? OOM問題解決方法?
        WeakReference弱引用,與強引用(即我們常說的引用)相對,它的特點是,GC在回收時會忽略掉弱引用,即就算有弱引用指向某對象,但只要該對象沒有被強引用指向(實際上多數時候還要求沒有軟引用),該對象就會在被GC檢查到時回收掉。如果對內存的開銷比較關注的APP,可以考慮使用WeakReference,當GC回收掃過這塊內存區域時就會回收;如果不是那麼關注的話,可以使用SoftReference,它會在內存申請不足的情況下自動釋放,同樣也能解決OOM問題。同時Android自3.0以後也推出了LruCache類,使用LRU算法就釋放內存,一樣的能解決OOM,如果要兼容3.0以下的版本,需要導入v4包。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章