編輯:關於Android編程
一、Android內存基礎
物理內存與進程內存
物理內存即移動設備上的RAM,當啟動一個Android程序時,會啟動一個Dalvik VM進程,系統會給它分配固定的內存空間(16M,32M不定),這塊內存空間會映射到RAM上某個區域。然後這個Android程序就會運行在這塊空間上。Java裡會將這塊空間分成Stack棧內存和Heap堆內存。stack裡存放對象的引用,heap裡存放實際對象數據。
在程序運行中會創建對象,如果未合理管理內存,比如不及時回收無效空間就會造成內存洩露,嚴重的話可能導致使用內存超過系統分配內存,即內存溢出OOM,導致程序卡頓甚至直接退出。
內存洩露(Memory Leak)
Java內存洩漏指的是進程中某些對象(垃圾對象)已經沒有使用價值了,但是它們卻可以直接或間接地引用到gc roots導致無法被GC回收。Dalvik VM具備的GC機制(垃圾回收機制)會在內存占用過多時自動回收,嚴重時會造成內存溢出OOM。
內存溢出OOM
當應用程序申請的java heap空間超過Dalvik VM HeapGrowthLimit時,溢出。
注意:OOM並不代表內存不足,只要申請的heap超過Dalvik VM HeapGrowthLimit時,即使內存充足也會溢出。效果是能讓較多進程常駐內存。
如果RAM不足時系統會做什麼?
Android的Memory Killer會殺死優先級較低的進程,讓高優先級進程獲取更多內存。
Android系統默認內存回收機制
進程優先級:Foreground進程、Visible進程、Service進程、Background進程、Empty進程;
如果用戶按Home鍵返回桌面,那麼該app成為Background進程;如果按Back返回,則成為Empty進程
ActivityManagerService直接管理所有進程的內存資源分配。所有進程要申請或釋放內存都需要通過ActivityManagerService對象。
垃圾回收不定期執行。當內存不夠時就會遍歷heap空間,把垃圾對象刪除。
堆內存越大,則GC的時間更長
二、優化
Bitmap優化
Bitmap非常消耗內存,而且在Android中,讀取bitmap時, 一般分配給虛擬機的圖片堆棧只有8M,所以經常造成OOM問題。所以有必要針對Bitmap的使用作出優化:
圖片顯示:加載合適尺寸的圖片,比如顯示縮略圖的地方不要加載大圖。
圖片回收:使用完bitmap,及時使用Bitmap.recycle()回收。
問題:Android不是自身具備垃圾回收機制嗎?此處為何要手動回收。
Bitmap對象不是new生成的,而是通過BitmapFactory生產的。而且通過源碼可發現是通過調用JNI生成Bitmap對象(nativeDecodeStream()等方法)。所以,加載bitmap到內存裡包括兩部分,Dalvik內存和Linux kernel內存。前者會被虛擬機自動回收。而後者必須通過recycle()方法,內部調用nativeRecycle()讓linux kernel回收。
捕獲OOM異常:程序中設定如果發生OOM的應急處理方式。
圖片緩存:內存緩存、硬盤緩存等
圖片壓縮:直接使用ImageView顯示Bitmap時會占很多資源,尤其當圖片較大時容易發生OOM。可以使用BitMapFactory.Options對圖片進行壓縮。
圖片像素:android默認顏色模式為ARGB_8888,顯示質量最高,占用內存最大。若要求不高時可采用RGB_565等模式。圖片大小:圖片長度*寬度*單位像素所占據字節數
ARGB_4444:每個像素占用2byte內存
ARGB_8888:每個像素占用4byte內存 (默認)
RGB_565:每個像素占用2byte內存
對象引用類型
強引用 strong:Object object=new Object()。當內存不足時,Java虛擬機寧願拋出OOM內存溢出異常,也不會輕易回收強引用對象來解決內存不足問題;
軟引用 soft:只有當內存達到某個阈值時才會去回收,常用於緩存;
弱引用 weak :只要被GC線程掃描到了就進行回收;
虛引用
如果想要避免OOM發生,則使用軟引用對象,即當內存快不足時進行回收;如果想盡快回收某些占用內存較大的對象,例如bitmap,可以使用弱引用,能被快速回收。不過如果要對bitmap作緩存就不要使用弱引用,因為很快就會被GC回收,導致緩存失敗。
關於java對象引用類型,具體可參加本人另一篇文章
池 pool
對象池:如果某個對象在創建時,需要較大的資源開銷,那麼可以將其放入對象池,即將對象保存起來,下次需要時直接取出使用,而不用再次創建對象。當然,維護對象池也需要一定開銷,故要衡量。
線程池:與對象池差不多,將線程對象放在池中供反復使用,減少反復創建線程的開銷。
三、Handler內存洩漏分析及解決
1、介紹
首先,請浏覽下面這段handler代碼:
public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } }
在使用handler時,這是一段很常見的代碼。但是,它卻會造成嚴重的內存洩漏問題。在實際編寫中,我們往往會得到如下警告:
In Android, Handler classes should be static or leaks might occur.
那麼,handler是如何造成內存洩漏的呢?
2、分析
(1)、Android角度
當Android應用程序啟動時,framework會為該應用程序的主線程創建一個Looper對象。這個Looper對象包含一個簡單的消息隊列Message Queue,並且能夠循環的處理隊列中的消息。這些消息包括大多數應用程序framework事件,例如Activity生命周期方法調用、button點擊等,這些消息都會被添加到消息隊列中並被逐個處理。
另外,主線程的Looper對象會伴隨該應用程序的整個生命周期。
然後,當主線程裡,實例化一個Handler對象後,它就會自動與主線程Looper的消息隊列關聯起來。所有發送到消息隊列的消息Message都會擁有一個對Handler的引用,所以當Looper來處理消息時,會據此回調[Handler#handleMessage(Message)](http://developer.android.com/reference/android/os/Handler.html#handleMessage(android.os.Message)方法來處理消息。
(2)、Java角度
在java裡,非靜態內部類 和 匿名類 都會潛在的引用它們所屬的外部類。但是,靜態內部類卻不會。
(3)、洩漏來源
請浏覽下面一段代碼:
public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
當activity結束(finish)時,裡面的延時消息在得到處理前,會一直保存在主線程的消息隊列裡持續10分鐘。而且,由上文可知,這條消息持有對handler的引用,而handler又持有對其外部類(在這裡,即SampleActivity)的潛在引用。這條引用關系會一直保持直到消息得到處理,從而,這阻止了SampleActivity被垃圾回收器回收,同時造成應用程序的洩漏。
注意,上面代碼中的Runnable類--非靜態匿名類--同樣持有對其外部類的引用。從而也導致洩漏。
3、洩漏解決方案
首先,上面已經明確了內存洩漏來源:
只要有未處理的消息,那麼消息會引用handler,非靜態的handler又會引用外部類,即Activity,導致Activity無法被回收,造成洩漏;
Runnable類屬於非靜態匿名類,同樣會引用外部類。
為了解決遇到的問題,我們要明確一點:靜態內部類不會持有對外部類的引用。所以,我們可以把handler類放在單獨的類文件中,或者使用靜態內部類便可以避免洩漏。
另外,如果想要在handler內部去調用所在的外部類Activity,那麼可以在handler內部使用弱引用的方式指向所在Activity,這樣統一不會導致內存洩漏。
對於匿名類Runnable,同樣可以將其設置為靜態類。因為靜態的匿名類不會持有對外部類的引用。
public class SampleActivity extends Activity { /** * Instances of static inner classes do not hold an implicit * reference to their outer class. */ private static class MyHandler extends Handler { private final WeakReference<SampleActivity> mActivity; public MyHandler(SampleActivity activity) { mActivity = new WeakReference<SampleActivity>(activity); } @Override public void handleMessage(Message msg) { SampleActivity activity = mActivity.get(); if (activity != null) { // ... } } } private final MyHandler mHandler = new MyHandler(this); /** * Instances of anonymous classes do not hold an implicit * reference to their outer class when they are "static". */ private static final Runnable sRunnable = new Runnable() { @Override public void run() { /* ... */ } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mHandler.postDelayed(sRunnable, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
4、小結
雖然靜態類與非靜態類之間的區別並不大,但是對於Android開發者而言卻是必須理解的。至少我們要清楚,如果一個內部類實例的生命周期比Activity更長,那麼我們千萬不要使用非靜態的內部類。最好的做法是,使用靜態內部類,然後在該類裡使用弱引用來指向所在的Activity。
很久沒有更新第三方SDK這個系列了,所以更新一下這幾天工作中使用到的推送,寫這個系列真的很要命,你要去把他們的API文檔大致的翻閱一遍,而且各種功能都實現一遍,解決各種b
Android4.3按鍵消息處理與之前的版本有稍微的區別,基本原理還是一樣的,這裡主要從兩個階段來分析: 1.前期的准備工作,即開機時啟動相應的的線程,靜候按鍵事件的來臨
最近經常看到各種介紹MVP模式的博客的,之前寫過不少的Android應用,在做那些應用的時候,都是要求快速完成,所以從開始設計到寫代碼就一直考慮著重用。以前寫的項目基本都
Volley的核心結構如下所示:今天主要看Network相關:Network整體功能介紹:通過實現了Network接口的類(這裡是BasicNetwork),執行從Req