android app性能優化大彙總(內存性能優化)

轉載請註明本文出自大苞米的博客(http://blog.csdn.net/a396901990),謝謝支持!
html

 

寫在最前:java

本文的思路主要借鑑了2014年AnDevCon開發者大會的一個演講PPT,加上把網上搜集的各類內存零散知識點進行彙總、挑選、簡化後整理而成。android

因此我將本文定義爲一個工具類的文章,若是你在Android開發中遇到關於內存問題,或者立刻要參加面試,或者就是單純的學習或複習一下內存相關知識,都歡迎閱讀。(本文最後我會盡可能列出所參考的文章)。git

 

 

內存簡介:
 
RAM(random access memory)隨機存取存儲器。說白了就是內存。

通常Java在內存分配時會涉及到如下區域:程序員

 

寄存器(Registers):速度最快的存儲場所,由於寄存器位於處理器內部咱們在程序中沒法控制github

棧(Stack):存放基本類型的數據和對象的引用,但對象自己不存放在棧中,而是存放在堆中面試

堆(Heap):堆內存用來存放由new建立的對象和數組。在堆中分配的內存,由Java虛擬機的自動垃圾回收器(GC)來管理。正則表達式

靜態域(static field):  靜態存儲區域就是指在固定的位置存放應用程序運行時一直存在的數據,Java在內存中專門劃分了一個靜態存儲區域來管理一些特殊的數據變量如靜態的數據變量算法

常量池(constant pool):虛擬機必須爲每一個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和floating point常量)和對其餘類型,字段和方法的符號引用。shell

非RAM存儲:硬盤等永久存儲空間

 

 

堆棧特色對比:

因爲篇幅緣由,下面只簡單的介紹一下堆棧的一些特性。

:當定義一個變量時,Java就在棧中爲這個變量分配內存空間,當該變量退出該做用域後,Java會自動釋放掉爲該變量所分配的內存空間,該內存空間能夠當即被另做他用。

:當堆中的new產生數組和對象超出其做用域後,它們不會被釋放,只有在沒有引用變量指向它們的時候才變成垃圾,不能再被使用。即便這樣,所佔內存也不會當即釋放,而是等待被垃圾回收器收走。這也是Java比較佔內存的緣由。

 

:存取速度比堆要快,僅次於寄存器。但缺點是,存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。

:堆是一個運行時數據區,能夠動態地分配內存大小,所以存取速度較慢。也正由於這個特色,堆的生存期沒必要事先告訴編譯器,並且Java的垃圾收集器會自動收走這些再也不使用的數據。

 

:棧中的數據能夠共享, 它是由編譯器完成的,有利於節省空間。

例如:須要定義兩個變量int a = 3;int b = 3;

編譯器先處理int a = 3;首先它會在棧中建立一個變量爲a的引用,而後查找棧中是否有3這個值,若是沒找到,就將3存放進來,而後將a指向3。接着處理int b = 3;在建立完b的引用變量後,由於在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的狀況。這時,若是再讓a=4;那麼編譯器會從新搜索棧中是否有4值,若是沒有,則將4存放進來,並讓a指向4;若是已經有了,則直接將a指向這個地址。所以a值的改變不會影響到b的值。

:例如上面棧中a的修改並不會影響到b, 而在堆中一個對象引用變量修改了這個對象的內部狀態,會影響到另外一個對象引用變量。

 

內存耗用名詞解析:

VSS - Virtual Set Size 虛擬耗用內存(包含共享庫佔用的內存)

RSS - Resident Set Size 實際使用物理內存(包含共享庫佔用的內存)

PSS - Proportional Set Size 實際使用的物理內存(比例分配共享庫佔用的內存)

USS - Unique Set Size 進程獨自佔用的物理內存(不包含共享庫佔用的內存)

通常來講內存佔用大小有以下規律:VSS >= RSS >= PSS >= USS

 

 

OOM:

 

內存泄露能夠引起不少的問題:

1.程序卡頓,響應速度慢(內存佔用高時JVM虛擬機會頻繁觸發GC)

2.莫名消失(當你的程序所佔內存越大,它在後臺的時候就越可能被幹掉。反以內存佔用越小,在後臺存在的時間就越長)

3.直接崩潰(OutOfMemoryError)

 

ANDROID內存面臨的問題:

 

1.有限的堆內存,原始只有16M

2.內存大小消耗等根據設備,操做系統等級,屏幕尺寸的不一樣而不一樣

3.程序不能直接控制

4.支持後臺多任務處理(multitasking)

5.運行在虛擬機之上

 

 

 

5R:

本文主要經過以下的5R方法來對ANDROID內存進行優化:

1.Reckon(計算)

首先須要知道你的app所消耗內存的狀況,知己知彼才能百戰不殆

2.Reduce(減小)

消耗更少的資源

 

3.Reuse(重用)

當第一次使用完之後,儘可能給其餘的使用

 

5.Recycle(回收)

返回資源給生產流

4.Review(檢查)

回顧檢查你的程序,看看設計或代碼有什麼不合理的地方。

 

 

Reckon (計算):

瞭解本身應用的內存使用狀況是頗有必要的。若是當內存使用太高的話就須要對其進行優化,由於更少的使用內存能夠減小ANDROID系統終止咱們的進程的概率,也能夠提升多任務執行效率和體驗效果。

下面從系統內存(system ram)和堆內存(heap)兩個方面介紹一些查看和計算內存使用狀況的方法:

 

System Ram(系統內存):

觀察和計算系統內存使用狀況,可使用Android提供給咱們的兩個工具procstatsmeminfo。他們一個側重於後臺的內存使用,另外一個是運行時的內存使用。

 

Process Stats: 

Android 4.4 KitKat 提出了一個新系統服務,叫作procstats。它將幫助你更好的理解你app在後臺(background)時的內存使用狀況。
Procstats能夠去監視你app在一段時間的行爲,包括在後臺運行了多久,並在此段時間使用了多少內存。從而幫助你快速的找到應用中不效率和不規範的地方去避免影響其performs,尤爲是在低內存的設備上運行時。
你能夠經過adb shell命令去使用procstats(adb shell dumpsys procstats --hours 3),或者更方便的方式是運行Process Stats開發者工具(在4.4版本的手機中點擊Settings > Developer options > Process Stats)
點擊單個條目還能夠查看詳細信息
 
meminfo:
Android還提供了一個工具叫作meminfo。它是根據PSS標準 (Proportional Set Size——實際物理內存)計算每一個進程的內存使用而且按照重要程度排序。
你能夠經過命令行去執行它:(adb shell dumpsys meminfo)或者使用在設備上點擊Settings > Apps > Running(與Procstats不用,它也能夠在老版本上運行)
更多關於 Procstatsmeninfo的介紹能夠參考我翻譯的一篇文章: Process Stats:瞭解你的APP如何使用內存

 

Heap(堆內存):

在程序中可使用以下的方法去查詢內存使用狀況

 

ActivityManager#getMemoryClass()

查詢可用堆內存的限制

3.0(HoneyComb)以上的版本能夠經過largeHeap=「true」來申請更多的堆內存(不過這算做「做弊」)

 

 

ActivityManager#getMemoryInfo(ActivityManager.MemoryInfo)
獲得的MemoryInfo中能夠查看以下Field的屬性:
availMem:表示系統剩餘內存
lowMemory:它是boolean值,表示系統是否處於低內存運行
hreshold:它表示當系統剩餘內存低於好多時就當作低內存運行

 

 

android.os.Debug#getMemoryInfo(Debug.MemoryInfo memoryInfo)

獲得的MemoryInfo中能夠查看以下Field的屬性:

 

dalvikPrivateDirty: The private dirty pages used by dalvik。
dalvikPss :The proportional set size for dalvik.
dalvikSharedDirty :The shared dirty pages used by dalvik.
nativePrivateDirty :The private dirty pages used by the native heap.
nativePss :The proportional set size for the native heap.
nativeSharedDirty :The shared dirty pages used by the native heap.
otherPrivateDirty :The private dirty pages used by everything else.
otherPss :The proportional set size for everything else.
otherSharedDirty :The shared dirty pages used by everything else.

 

 

 
dalvik:是指dalvik所使用的內存。
native:是被native堆使用的內存。應該指使用C\C++在堆上分配的內存。
other:是指除dalvik和native使用的內存。可是具體是指什麼呢?至少包括在C\C++分配的非堆內存,好比分配在棧上的內存。
private:是指私有的。非共享的。
share:是指共享的內存。
PSS:實際使用的物理內存(比例分配共享庫佔用的內存)
 PrivateDirty:它是指非共享的,又不能換頁出去(can not be paged to disk )的內存的大小。好比Linux爲了提升分配內存速度而緩衝的小對象,即便你的進程結束,該內存也不會釋放掉,它只是又從新回到緩衝中而已。
SharedDirty:參照PrivateDirty我認爲它應該是指共享的,又不能換頁出去(can not be paged to disk )的內存的大小。好比Linux爲了提升分配內存速度而緩衝的小對象,即便全部共享它的進程結束,該內存也不會釋放掉,它只是又從新回到緩衝中而已。
 

 

android.os.Debug#getNativeHeapSize()

返回的是當前進程navtive堆自己總的內存大小

android.os.Debug#getNativeHeapAllocatedSize()

返回的是當前進程navtive堆中已使用的內存大小

android.os.Debug#getNativeHeapFreeSize()

返回的是當前進程navtive堆中已經剩餘的內存大小

 

 

Memory Analysis Tool(MAT):

一般內存泄露分析被認爲是一件頗有難度的工做,通常由團隊中的資深人士進行。不過,今天咱們要介紹的 MAT(Eclipse Memory Analyzer)被認爲是一個「傻瓜式「的堆轉儲文件分析工具,你只須要輕輕點擊一下鼠標就能夠生成一個專業的分析報告。

以下圖:

關於詳細的MAT使用我推薦下面這篇文章:使用 Eclipse Memory Analyzer 進行堆轉儲文件分析

 

OOM:

 

內存泄露能夠引起不少的問題:

1.程序卡頓,響應速度慢(內存佔用高時JVM虛擬機會頻繁觸發GC)

2.莫名消失(當你的程序所佔內存越大,它在後臺的時候就越可能被幹掉。反以內存佔用越小,在後臺存在的時間就越長)

3.直接崩潰(OutOfMemoryError)

 

ANDROID內存面臨的問題:

 

1.有限的堆內存,原始只有16M

2.內存大小消耗等根據設備,操做系統等級,屏幕尺寸的不一樣而不一樣

3.程序不能直接控制

4.支持後臺多任務處理(multitasking)

5.運行在虛擬機之上

 

5R:

本文主要經過以下的5R方法來對ANDROID內存進行優化:

 

1.Reckon(計算)

首先須要知道你的app所消耗內存的狀況,知己知彼才能百戰不殆

2.Reduce(減小)

消耗更少的資源

 

3.Reuse(重用)

當第一次使用完之後,儘可能給其餘的使用

5.Recycle(回收)

返回資源給生產流

4.Review(檢查)

回顧檢查你的程序,看看設計或代碼有什麼不合理的地方。

 

Reckon:

 

關於內存簡介,和Reckon(內存計算)的內容請看上一篇文章:ANDROID內存優化(大彙總——上)

 

 

Reduce :

 

Reduce的意思就是減小,直接減小內存的使用是最有效的優化方式。

下面來看看有哪些方法能夠減小內存使用:

 

 

Bitmap
Bitmap是內存消耗大戶,絕大多數的OOM崩潰都是在操做Bitmap時產生的,下面來看看如何幾個處理圖片的方法:

 

圖片顯示:

咱們須要根據需求去加載圖片的大小。

例如在列表中僅用於預覽時加載縮略圖(thumbnails )。

只有當用戶點擊具體條目想看詳細信息的時候,這時另啓動一個fragment/activity/對話框等等,去顯示整個圖片

 

 

圖片大小:

直接使用ImageView顯示bitmap會佔用較多資源,特別是圖片較大的時候,可能致使崩潰。 
使用BitmapFactory.Options設置inSampleSize, 這樣作能夠減小對系統資源的要求。 
屬性值inSampleSize表示縮略圖大小爲原始圖片大小的幾分之一,即若是這個值爲2,則取出的縮略圖的寬和高都是原始圖片的1/2,圖片大小就爲原始大小的1/4。 

 

[java]  view plain copy
 
 
  1. BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();  
  2. bitmapFactoryOptions.inJustDecodeBounds = true;  
  3. bitmapFactoryOptions.inSampleSize = 2;  
  4. // 這裏必定要將其設置回false,由於以前咱們將其設置成了true    
  5. // 設置inJustDecodeBounds爲true後,decodeFile並不分配空間,即,BitmapFactory解碼出來的Bitmap爲Null,但可計算出原始圖片的長度和寬度    
  6. options.inJustDecodeBounds = false;  
  7. Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);  

 

 

圖片像素:

Android中圖片有四種屬性,分別是:
ALPHA_8:每一個像素佔用1byte內存 
ARGB_4444:每一個像素佔用2byte內存 
ARGB_8888:每一個像素佔用4byte內存 (默認)
RGB_565:每一個像素佔用2byte內存 
 
Android默認的顏色模式爲ARGB_8888,這個顏色模式色彩最細膩,顯示質量最高。但一樣的,佔用的內存也最大。 因此在對圖片效果不是特別高的狀況下使用RGB_565(565沒有透明度屬性),以下:
[java]  view plain copy
 
 
  1. publicstaticBitmapreadBitMap(Contextcontext, intresId) {  
  2.     BitmapFactory.Optionsopt = newBitmapFactory.Options();  
  3.     opt.inPreferredConfig = Bitmap.Config.RGB_565;  
  4.     opt.inPurgeable = true;  
  5.     opt.inInputShareable = true;  
  6.     //獲取資源圖片   
  7.     InputStreamis = context.getResources().openRawResource(resId);  
  8.     returnBitmapFactory.decodeStream(is, null, opt);  
  9. }  

圖片回收:

使用Bitmap事後,就須要及時的調用Bitmap.recycle()方法來釋放Bitmap佔用的內存空間,而不要等Android系統來進行釋放。

下面是釋放Bitmap的示例代碼片斷。

 

[java]  view plain copy
 
 
  1. // 先判斷是否已經回收  
  2. if(bitmap != null && !bitmap.isRecycled()){  
  3.     // 回收而且置爲null  
  4.     bitmap.recycle();  
  5.     bitmap = null;  
  6. }  
  7. System.gc();  

捕獲異常:

通過上面這些優化後還會存在報OOM的風險,因此下面須要一道最後的關卡——捕獲OOM異常:

 

[java]  view plain copy
 
 
  1. Bitmap bitmap = null;  
  2. try {  
  3.     // 實例化Bitmap  
  4.     bitmap = BitmapFactory.decodeFile(path);  
  5. catch (OutOfMemoryError e) {  
  6.     // 捕獲OutOfMemoryError,避免直接崩潰  
  7. }  
  8. if (bitmap == null) {  
  9.     // 若是實例化失敗 返回默認的Bitmap對象  
  10.     return defaultBitmapMap;  
  11. }  
 

 

修改對象引用類型:

 

引用類型:

引用分爲四種級別,這四種級別由高到低依次爲:強引用>軟引用>弱引用>虛引用。

強引用(strong reference)
如:Object object=new Object(),object就是一個強引用了。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足問題。

軟引用(SoftReference)
只有內存不夠時纔回收,經常使用於緩存;當內存達到一個閥值,GC就會去回收它;

 

弱引用(WeakReference)   

弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它 所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。 

 

虛引用(PhantomReference)   

"虛引用"顧名思義,就是形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收。  

 

軟引用和弱引用的應用實例:

注意:對於SoftReference(軟引用)或者WeakReference(弱引用)的Bitmap緩存方案,如今已經不推薦使用了。自Android2.3版本(API Level 9)開始,垃圾回收器更着重於對軟/弱引用的回收,因此下面的內容能夠選擇忽略。

在Android應用的開發中,爲了防止內存溢出,在處理一些佔用內存大並且聲明週期較長的對象時候,能夠儘可能應用軟引用和弱引用技術。

下面以使用軟引用爲例來詳細說明(弱引用的使用方式與軟引用是相似的):

假設咱們的應用會用到大量的默認圖片,並且這些圖片不少地方會用到。若是每次都去讀取圖片,因爲讀取文件須要硬件操做,速度較慢,會致使性能較低。因此咱們考慮將圖片緩存起來,須要的時候直接從內存中讀取。可是,因爲圖片佔用內存空間比較大,緩存不少圖片須要不少的內存,就可能比較容易發生OutOfMemory異常。這時,咱們能夠考慮使用軟引用技術來避免這個問題發生。

首先定義一個HashMap,保存軟引用對象。

[java]  view plain copy
 
 
  1. private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();  
再來定義一個方法,保存Bitmap的軟引用到HashMap。

 

[java]  view plain copy
 
 
  1. public void addBitmapToCache(String path) {  
  2.        // 強引用的Bitmap對象  
  3.        Bitmap bitmap = BitmapFactory.decodeFile(path);  
  4.        // 軟引用的Bitmap對象  
  5.        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);  
  6.        // 添加該對象到Map中使其緩存  
  7.        imageCache.put(path, softBitmap);  
  8.    }  
獲取的時候,能夠經過SoftReference的get()方法獲得Bitmap對象。
[java]  view plain copy
 
 
  1. public Bitmap getBitmapByPath(String path) {  
  2.         // 從緩存中取軟引用的Bitmap對象  
  3.         SoftReference<Bitmap> softBitmap = imageCache.get(path);  
  4.         // 判斷是否存在軟引用  
  5.         if (softBitmap == null) {  
  6.             return null;  
  7.         }  
  8.         // 取出Bitmap對象,若是因爲內存不足Bitmap被回收,將取得空  
  9.         Bitmap bitmap = softBitmap.get();  
  10.         return bitmap;  
  11.     }  
使用軟引用之後,在OutOfMemory異常發生以前,這些緩存的圖片資源的內存空間能夠被釋放掉的,從而避免內存達到上限,避免Crash發生。

須要注意的是,在垃圾回收器對這個Java對象回收前,SoftReference類所提供的get方法會返回Java對象的強引用,一旦垃圾線程回收該Java對象以後,get方法將返回null。因此在獲取軟引用對象的代碼中,必定要判斷是否爲null,以避免出現NullPointerException異常致使應用崩潰。

 

到底何時使用軟引用,何時使用弱引用呢?

我的認爲,若是隻是想避免OutOfMemory異常的發生,則可使用軟引用。若是對於應用的性能更在乎,想盡快回收一些佔用內存比較大的對象,則可使用弱引用。

還有就是能夠根據對象是否常用來判斷。若是該對象可能會常用的,就儘可能用軟引用。若是該對象不被使用的可能性更大些,就能夠用弱引用。

另外,和弱引用功能相似的是WeakHashMap。WeakHashMap對於一個給定的鍵,其映射的存在並不阻止垃圾回收器對該鍵的回收,回收之後,其條目從映射中有效地移除。WeakHashMap使用ReferenceQueue實現的這種機制。

 

其餘小tips:

 

對常量使用static final修飾符

讓咱們來看看這兩段在類前面的聲明:

static int intVal = 42;
static String strVal = "Hello, world!";
編譯器會生成一個叫作clinit的初始化類的方法,當類第一次被使用的時候這個方法會被執行。方法會將42賦給intVal,而後把一個指向類中常量表 的引用賦給strVal。當之後要用到這些值的時候,會在成員變量表中查找到他們。 下面咱們作些改進,使用「final」關鍵字:

static final int intVal = 42;
static final String strVal = "Hello, world!";

如今,類再也不須要clinit方法,由於在成員變量初始化的時候,會將常量直接保存到類文件中。用到intVal的代碼被直接替換成42,而使用strVal的會指向一個字符串常量,而不是使用成員變量。

將一個方法或類聲明爲final不會帶來性能的提高,可是會幫助編譯器優化代碼。舉例說,若是編譯器知道一個getter方法不會被重載,那麼編譯器會對其採用內聯調用。

你也能夠將本地變量聲明爲final,一樣,這也不會帶來性能的提高。使用「final」只能使本地變量看起來更清晰些(可是也有些時候這是必須的,好比在使用匿名內部類的時候)。

 

靜態方法代替虛擬方法

若是不須要訪問某對象的字段,將方法設置爲靜態,調用會加速15%到20%。這也是一種好的作法,由於你能夠從方法聲明中看出調用該方法不須要更新此對象的狀態。

 

減小沒必要要的全局變量

儘可能避免static成員變量引用資源耗費過多的實例,好比Context

 

由於Context的引用超過它自己的生命週期,會致使Context泄漏。因此儘可能使用Application這種Context類型。 你能夠經過調用Context.getApplicationContext()或 Activity.getApplication()輕鬆獲得Application對象。 
 

避免建立沒必要要的對象

最多見的例子就是當你要頻繁操做一個字符串時,使用StringBuffer代替String。

對於全部全部基本類型的組合:int數組比Integer數組好,這也歸納了一個基本事實,兩個平行的int數組比 (int,int)對象數組性能要好不少。

整體來講,就是避免建立短命的臨時對象。減小對象的建立就能減小垃圾收集,進而減小對用戶體驗的影響。

 
避免內部Getters/Setters
在Android中,虛方法調用的代價比直接字段訪問高昂許多。一般根據面嚮對象語言的實踐,在公共接口中使用Getters和Setters是有道理的,但在一個字段常常被訪問的類中宜採用直接訪問。

 

避免使用浮點數

一般的經驗是,在Android設備中,浮點數會比整型慢兩倍。

 

使用實體類比接口好

 

假設你有一個HashMap對象,你能夠將它聲明爲HashMap或者Map:

Map map1 = new HashMap();
HashMap map2 = new HashMap();

哪一個更好呢?

按照傳統的觀點Map會更好些,由於這樣你能夠改變他的具體實現類,只要這個類繼承自Map接口。傳統的觀點對於傳統的程序是正確的,可是它並不適合嵌入式系統。調用一個接口的引用會比調用實體類的引用多花費一倍的時間。若是HashMap徹底適合你的程序,那麼使用Map就沒有什麼價值。若是有些地方你不能肯定,先避免使用Map,剩下的交給IDE提供的重構功能好了。(固然公共API是一個例外:一個好的API經常會犧牲一些性能)

 

避免使用枚舉

枚舉變量很是方便,但不幸的是它會犧牲執行的速度和並大幅增長文件體積。

使用枚舉變量可讓你的API更出色,並能提供編譯時的檢查。因此在一般的時候你毫無疑問應該爲公共API選擇枚舉變量。可是當性能方面有所限制的時候,你就應該避免這種作法了。

 

for循環

訪問成員變量比訪問本地變量慢得多,以下面一段代碼:

 

[java]  view plain copy
 
 
  1. for(int i =0; i < this.mCount; i++)  {}  

永遠不要在for的第二個條件中調用任何方法,以下面一段代碼:

 

[java]  view plain copy
 
 
  1. for(int i =0; i < this.getCount(); i++) {}  

對上面兩個例子最好改成:

 

[java]  view plain copy
 
 
  1. int count = this.mCount; / int count = this.getCount();  
  2. for(int i =0; i < count; i++)  {}  
在java1.5中引入的for-each語法。編譯器會將對數組的引用和數組的長度保存到本地變量中,這對訪問數組元素很是好。 可是編譯器還會在每次循環中產生一個額外的對本地變量的存儲操做(以下面例子中的變量a),這樣會比普通循環多出4個字節,速度要稍微慢一些:

 

[java]  view plain copy
 
 
  1. for (Foo a : mArray) {  
  2.     sum += a.mSplat;  
  3. }  

瞭解並使用類庫

選擇Library中的代碼而非本身重寫,除了一般的那些緣由外,考慮到系統空閒時會用匯編代碼調用來替代library方法,這可能比JIT中生成的等價的最好的Java代碼還要好。

當你在處理字串的時候,不要吝惜使用String.indexOf()String.lastIndexOf()等特殊實現的方法。這些方法都是使用C/C++實現的,比起Java循環快10到100倍。

System.arraycopy方法在有JIT的Nexus One上,自行編碼的循環快9倍。

android.text.format包下的Formatter類,提供了IP地址轉換、文件大小轉換等方法;DateFormat類,提供了各類時間轉換,都是很是高效的方法。

TextUtils類,對於字符串處理Android爲咱們提供了一個簡單實用的TextUtils類,若是處理比較簡單的內容不用去思考正則表達式不妨試試這個在android.text.TextUtils的類

高性能MemoryFile類,不少人抱怨Android處理底層I/O性能不是很理想,若是不想使用NDK則能夠經過MemoryFile類實現高性能的文件讀寫操做。MemoryFile適用於哪些地方呢?對於I/O須要頻繁操做的,主要是和外部存儲相關的I/O操做,MemoryFile經過將 NAND或SD卡上的文件,分段映射到內存中進行修改處理,這樣就用高速的RAM代替了ROM或SD卡,性能天然提升很多,對於Android手機而言同時還減小了電量消耗。該類實現的功能不是不少,直接從Object上繼承,經過JNI的方式直接在C底層執行。

 

 

Reuse:

 
Reuse重用,減小內存消耗的重要手段之一。
核心思路就是將已經存在的內存資源從新使用而避免去建立新的,最典型的使用就是 緩存(Cache 池(Pool)
 
Bitmap緩存:
 

Bitmap緩存分爲兩種:

一種是內存緩存,一種是硬盤緩存。

 

內存緩存(LruCache):

以犧牲寶貴的應用內存爲代價,內存緩存提供了快速的Bitmap訪問方式。系統提供的LruCache類是很是適合用做緩存Bitmap任務的,它將最近被引用到的對象存儲在一個強引用的LinkedHashMap中,而且在緩存超過了指定大小以後將最近不常使用的對象釋放掉。

注意:之前有一個很是流行的內存緩存實現是SoftReference(軟引用)或者WeakReference(弱引用)的Bitmap緩存方案,然而如今已經不推薦使用了。自Android2.3版本(API Level 9)開始,垃圾回收器更着重於對軟/弱引用的回收,這使得上述的方案至關無效。

 

硬盤緩存(DiskLruCache):

 

一個內存緩存對加速訪問最近瀏覽過的Bitmap很是有幫助,可是你不能侷限於內存中的可用圖片。GridView這樣有着更大的數據集的組件能夠很輕易消耗掉內存緩存。你的應用有可能在執行其餘任務(如打電話)的時候被打斷,而且在後臺的任務有可能被殺死或者緩存被釋放。一旦用戶從新聚焦(resume)到你的應用,你得再次處理每一張圖片。

在這種狀況下,硬盤緩存能夠用來存儲Bitmap並在圖片被內存緩存釋放後減少圖片加載的時間(次數)。固然,從硬盤加載圖片比內存要慢,而且應該在後臺線程進行,由於硬盤讀取的時間是不可預知的。

注意:若是訪問圖片的次數很是頻繁,那麼ContentProvider可能更適合用來存儲緩存圖片,例如Image Gallery這樣的應用程序。

更多關於內存緩存和硬盤緩存的內容請看Google官方教程 https://developer.android.com/develop/index.html
 
圖片緩存的開源項目:
對於圖片的緩存如今都傾向於使用開源項目,這裏我列出幾個我搜到的:
 

1. Android-Universal-Image-Loader 圖片緩存

目前使用最普遍的圖片緩存,支持主流圖片緩存的絕大多數特性。
項目地址:https://github.com/nostra13/Android-Universal-Image-Loader

 

2. picasso square開源的圖片緩存
項目地址:https://github.com/square/picasso
特色:(1)能夠自動檢測adapter的重用並取消以前的下載
(2)圖片變換
(3)能夠加載本地資源
(4)能夠設置佔位資源
(5)支持debug模式

 

3. ImageCache 圖片緩存,包含內存和Sdcard緩存
項目地址:https://github.com/Trinea/AndroidCommon
特色:

(1)支持預取新圖片,支持等待隊列
(2)包含二級緩存,可自定義文件名保存規則
(3)可選擇多種緩存算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13種)或自定義緩存算法
(4)可方便的保存及初始化恢復數據
(5)支持不一樣類型網絡處理
(6)可根據系統配置初始化緩存等

4. Android 網絡通訊框架Volley
項目地址:https://android.googlesource.com/platform/frameworks/volley
咱們在程序中須要和網絡通訊的時候,大致使用的東西莫過於AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,在2013年的Google I/O發佈了Volley。Volley是Android平臺上的網絡通訊庫,能使網絡通訊更快,更簡單,更健壯。
特色:
(1)JSON,圖像等的異步下載;
(2)網絡請求的排序(scheduling)
(3)網絡請求的優先級處理
(4)緩存
(5)多級別取消請求
(6)和Activity和生命週期的聯動(Activity結束時同時取消全部網絡請求)
 
Adapter適配器
 
在Android中Adapter使用十分普遍,特別是在list中。因此adapter是數據的  「集散地」 ,因此對其進行內存優化是頗有必要的。
下面算是一個標準的使用模版:
主要使用convertView和ViewHolder來進行緩存處理

 

[java]  view plain copy
 
 
  1. @Override  
  2. public View getView(int position, View convertView, ViewGroup parent) {  
  3.     ViewHolder vHolder = null;  
  4.     //若是convertView對象爲空則建立新對象,不爲空則複用    
  5.     if (convertView == null) {  
  6.         convertView = inflater.inflate(..., null);  
  7.         // 建立 ViewHodler 對象    
  8.         vHolder = new ViewHolder();  
  9.         vHolder.img= (ImageView) convertView.findViewById(...);  
  10.         vHolder.tv= (TextView) convertView.findViewById(...);  
  11.         // 將ViewHodler保存到Tag中(Tag能夠接收Object類型對象,因此任何東西均可以保存在其中)  
  12.         convertView.setTag(vHolder);  
  13.     } else {  
  14.         //當convertView不爲空時,經過getTag()獲得View    
  15.         vHolder = (ViewHolder) convertView.getTag();  
  16.     }  
  17.     // 給對象賦值,修改顯示的值    
  18.     vHolder.img.setImageBitmap(...);  
  19.     vHolder.tv.setText(...);  
  20.     return convertView;  
  21. }  
  22. //將顯示的View 包裝成類    
  23. static class ViewHolder {  
  24.     TextView tv;  
  25.     ImageView img;  
  26. }  
 
池(PooL)
 

對象池:

對象池使用的基本思路是:將用過的對象保存起來,等下一次須要這種對象的時候,再拿出來重複使用,從而在必定程度上減小頻繁建立對象所形成的開銷。 並不是全部對象都適合拿來池化――由於維護對象池也要形成必定開銷。對生成時開銷不大的對象進行池化,反而可能會出現「維護對象池的開銷」大於「生成新對象的開銷」,從而使性能下降的狀況。可是對於生成時開銷可觀的對象,池化技術就是提升性能的有效策略了。

 

 

線程池:

線程池的基本思想仍是一種對象池的思想,開闢一塊內存空間,裏面存放了衆多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣能夠避免反覆建立線程對象所帶來的性能開銷,節省了系統的資源。

好比:一個應用要和網絡打交道,有不少步驟須要訪問網絡,爲了避免阻塞主線程,每一個步驟都建立個線程,在線程中和網絡交互,用線程池就變的簡單,線程池是對線程的一種封裝,讓線程用起來更加簡便,只須要創一個線程池,把這些步驟像任務同樣放進線程池,在程序銷燬時只要調用線程池的銷燬函數便可。

 

java提供了ExecutorServiceExecutors類,咱們能夠應用它去創建線程池。

一般能夠創建以下4種:

 

[java]  view plain copy
 
 
  1. /** 每次只執行一個任務的線程池 */  
  2. ExecutorService singleTaskExecutor =  Executors.newSingleThreadExecutor();  
  3.   
  4. /** 每次執行限定個數個任務的線程池 */  
  5. ExecutorService limitedTaskExecutor = Executors.newFixedThreadPool(3);  
  6.   
  7. /** 全部任務都一次性開始的線程池 */  
  8. ExecutorService allTaskExecutor = Executors.newCachedThreadPool();  
  9.   
  10. /** 建立一個可在指定時間裏執行任務的線程池,亦可重複執行 */  
  11. ExecutorService scheduledTaskExecutor = Executors.newScheduledThreadPool(3);  

 

 

更多關於線程池的內容我推薦這篇文章: http://www.xuanyusong.com/archives/2439
注意:

 

要根據狀況適度使用緩存,由於內存有限。

能保存路徑地址的就不要存放圖片數據,不常用的儘可能不要緩存,不用時就清空。

 

OOM:

 

內存泄露能夠引起不少的問題:

1.程序卡頓,響應速度慢(內存佔用高時JVM虛擬機會頻繁觸發GC)

2.莫名消失(當你的程序所佔內存越大,它在後臺的時候就越可能被幹掉。反以內存佔用越小,在後臺存在的時間就越長)

3.直接崩潰(OutOfMemoryError)

 

ANDROID內存面臨的問題:

 

1.有限的堆內存,原始只有16M

2.內存大小消耗等根據設備,操做系統等級,屏幕尺寸的不一樣而不一樣

3.程序不能直接控制

4.支持後臺多任務處理(multitasking)

5.運行在虛擬機之上

 

5R:

本文主要經過以下的5R方法來對ANDROID內存進行優化:

 

1.Reckon(計算)

首先須要知道你的app所消耗內存的狀況,知己知彼才能百戰不殆

2.Reduce(減小)

消耗更少的資源

 

3.Reuse(重用)

當第一次使用完之後,儘可能給其餘的使用

5.Recycle(回收)

回收資源

4.Review(檢查)

回顧檢查你的程序,看看設計或代碼有什麼不合理的地方。

 

 

 

內存簡介Reckon(計算):

 

關於內存簡介,和Reckon的內容請看:ANDROID內存優化(大彙總——上)

 

 

Reduce(減小) ,Reuse(重用):

 

關於Reduce,和Reuse的內容請看:ANDROID內存優化(大彙總——中)

 

 

Recycle(回收):

 

Recycle(回收),回收能夠說是在內存使用中最重要的部分。由於內存空間有限,不管你如何優化,如何節省內存總有用完的時候。而回收的意義就在於去清理和釋放那些已經閒置,廢棄再也不使用的內存資源和內存空間。

由於在Java中有垃圾回收(GC)機制,因此咱們平時都不會太關注它,下面就來簡單的介紹一下回收機制:

 

 

垃圾回收(GC):

 

Java垃圾回收器:

在C,C++或其餘程序設計語言中,資源或內存都必須由程序員自行聲明產生和回收,不然其中的資源將消耗,形成資源的浪費甚至崩潰。但手工回收內存每每是一項複雜而艱鉅的工做。

因而,Java技術提供了一個系統級的線程,即垃圾收集器線程(Garbage Collection Thread),來跟蹤每一塊分配出去的內存空間,當Java 虛擬機(Java Virtual Machine)處於空閒循環時,垃圾收集器線程會自動檢查每一快分配出去的內存空間,而後自動回收每一快能夠回收的無用的內存塊。 

 

做用:

1.清除不用的對象來釋放內存:

採用一種動態存儲管理技術,它自動地釋放再也不被程序引用的對象,按照特定的垃圾收集算法來實現資源自動回收的功能。當一個對象再也不被引用的時候,內存回收它佔領的空間,以便空間被後來的新對象使用。 

2.消除堆內存空間的碎片:

因爲建立對象和垃圾收集器釋放丟棄對象所佔的內存空間,內存會出現碎片。碎片是分配給對象的內存塊之間的空閒內存洞。碎片整理將所佔用的堆內存移到堆的一端,JVM將整理出的內存分配給新的對象。 

 

垃圾回收器優勢:

1.減輕編程的負擔,提升效率:

使程序員從手工回收內存空間的繁重工做中解脫了出來,由於在沒有垃圾收集機制的時候,可能要花許多時間來解決一個難懂的存儲器問題。在用Java語言編程的時候,靠垃圾收集機制可大大縮短期。

2.它保護程序的完整性:

所以垃圾收集是Java語言安全性策略的一個重要部份。 

 

垃圾回收器缺點:

1.佔用資源時間:

Java虛擬機必須追蹤運行程序中有用的對象, 並且最終釋放沒用的對象。這一個過程須要花費處理器的時間。

2.不可預知:

垃圾收集器線程雖然是做爲低優先級的線程運行,但在系統可用內存量太低的時候,它可能會突發地執行來挽救內存資源。固然其執行與否也是不可預知的。 

3.不肯定性:

 

不能保證一個無用的對象必定會被垃圾收集器收集,也不能保證垃圾收集器在一段Java語言代碼中必定會執行。

一樣也沒有辦法預知在一組均符合垃圾收集器收集標準的對象中,哪個會被首先收集。 

4.不可操做

 

垃圾收集器不能夠被強制執行,但程序員能夠經過調用System. gc方法來建議執行垃圾收集器。
 
垃圾回收算法:
 
1.引用計數(Reference Counting) 
比較古老的回收算法。原理是此對象有一個引用,即增長一個計數,刪除一個引用則減小一個計數。垃圾回收時,只用收集計數爲0的對象。此算法最致命的是沒法處理循環引用的問題。 
2.標記-清除(Mark-Sweep) 
此算法執行分兩階段。第一階段從引用根節點開始標記全部被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法須要暫停整個應用,同時,會產生內存碎片。
3.複製(Copying) 
此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另一個區域中。次算法每次只處理正在使用中的對象,所以複製成本比較小,同時複製過去之後還能進行相應的內存整理,不過出現「碎片」問題。固然,此算法的缺點也是很明顯的,就是須要兩倍內存空間。
4.標記-整理(Mark-Compact) 
此算法結合了 「標記-清除」和「複製」兩個算法的優勢。也是分兩階段,第一階段從根節點開始標記全部被引用對象,第二階段遍歷整個堆,把清除未標記對象而且把存活對象 「壓縮」到堆的其中一塊,按順序排放。此算法避免了「標記-清除」的碎片問題,同時也避免了「複製」算法的空間問題。 
5.增量收集(Incremental Collecting) 
實施垃圾回收算法,即:在應用進行的同時進行垃圾回收。不知道什麼緣由JDK5.0中的收集器沒有使用這種算法的。 
6.分代(Generational Collecting) 
基於對對象生命週期分析後得出的垃圾回收算法。把對象分爲年青代、年老代、持久代,對不一樣生命週期的對象使用不一樣的算法(上述方式中的一個)進行回收。如今的垃圾回收器(從J2SE1.2開始)都是使用此算法的。 
 
 

finalize():

每個對象都有一個finalize方法,這個方法是從Object類繼承來的。 

當垃圾回收肯定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。

 

Java 技術容許使用finalize方法在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。一旦垃圾回收器準備好釋放對象佔用的空間,將首先調用其finalize()方法,而且在下一次垃圾回收動做發生時,纔會真正回收對象佔用的內存。
簡單的說finalize方法是在垃圾收集器刪除對象以前對這個對象調用的


System.gc():

咱們能夠調用System.gc方法,建議虛擬機進行垃圾回收工做(注意,是建議,但虛擬機會不會這樣幹,咱們也沒法預知!)

 

下面來看一個例子來了解finalize()System.gc()的使用:

 

[java]  view plain  copy
 
 print?
  1. public class TestGC {  
  2.     public TestGC() {}  
  3.       
  4.     //當垃圾回收器肯定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。  
  5.     protected void finalize() {  
  6.         System.out.println("我已經被垃圾回收器回收了...");  
  7.     }  
  8.       
  9.     public static void main(String [] args) {  
  10.         TestGC gc = new TestGC();  
  11.         gc = null;    
  12.         // 建議虛擬機進行垃圾回收工做  
  13.         System.gc();  
  14.     }  
  15. }  
如上面的例子所示,你們能夠猜猜重寫的finalize方法會不會執行?

 

答案是:不必定

 

由於不管是設置gc的引用爲null仍是調用System.gc()方法都只是"建議"垃圾回收器進行垃圾回收,可是最終全部權還在垃圾回收器手中,它會不會進行回收咱們沒法預知!

垃圾回收面試題:
最後經過網上找到的3道面試題來結束垃圾回收的內容。

 

面試題一: 

 

[java]  view plain  copy
 
 print?
  1. 1.fobj = new Object ( ) ;   
  2. 2.fobj. Method ( ) ;   
  3. 3.fobj = new Object ( ) ;   
  4. 4.fobj. Method ( ) ;   

 

問:這段代碼中,第幾行的fobj 符合垃圾收集器的收集標準? 
答:第3行。由於第3行的fobj被賦了新值,產生了一個新的對象,即換了一塊新的內存空間,也至關於爲第1行中的fobj賦了null值。這種類型的題是最簡單的。 

面試題二: 
[java]  view plain  copy
 
 print?
  1. 1.Object sobj = new Object ( ) ;   
  2. 2.Object sobj = null ;   
  3. 3.Object sobj = new Object ( ) ;   
  4. 4.sobj = new Object ( ) ;   
問:這段代碼中,第幾行的內存空間符合垃圾收集器的收集標準? 
答:第2行和第4行。由於第2行爲sobj賦值爲null,因此在此第1行的sobj符合垃圾收集器的收集標準。而第4行至關於爲sobj賦值爲null,因此在此第3行的sobj也符合垃圾收集器的收集標準。 

若是有一個對象的句柄a,且你把a做爲某個構造器的參數,即 new Constructor ( a )的時候,即便你給a賦值爲null,a也不符合垃圾收集器的收集標準。直到由上面構造器構造的新對象被賦空值時,a才能夠被垃圾收集器收集。 

面試題三: 
[java]  view plain  copy
 
 print?
  1. 1.Object aobj = new Object ( ) ;   
  2. 2.Object bobj = new Object ( ) ;   
  3. 3.Object cobj = new Object ( ) ;   
  4. 4.aobj = bobj;   
  5. 5.aobj = cobj;   
  6. 6.cobj = null;   
  7. 7.aobj = null;   
問:這段代碼中,第幾行的內存空間符合垃圾收集器的收集標準? 
答:第4,7行。注意這類題型是認證考試中可能遇到的最難題型了。 
行1-3:分別建立了Object類的三個對象:aobj,bobj,cobj
行4:此時對象aobj的句柄指向bobj,原來aojb指向的對象已經沒有任何引用或變量指向,這時,就符合回收標準。
行5:此時對象aobj的句柄指向cobj,因此該行的執行不能使aobj符合垃圾收集器的收集標準。 
行6:此時仍沒有任何一個對象符合垃圾收集器的收集標準。 
行7:對象cobj符合了垃圾收集器的收集標準,由於cobj的句柄指向單一的地址空間。在第6行的時候,cobj已經被賦值爲null,但由cobj同時還指向了aobj(第5行),因此此時cobj並不符合垃圾收集器的收集標準。而在第7行,aobj所指向的地址空間也被賦予了空值null,這就說明了,由cobj所指向的地址空間已經被徹底地賦予了空值。因此此時cobj最終符合了垃圾收集器的收集標準。 但對於aobj和bobj,仍然沒法判斷其是否符合收集標準。 

總之,在Java語言中,判斷一塊內存空間是否符合垃圾收集器收集的標準只有兩個: 
1.給對象賦予了空值null,如下再沒有調用過。 
2.給對象賦予了新值,既從新分配了內存空間。 

最後再次提醒一下,一塊內存空間符合了垃圾收集器的收集標準,並不意味着這塊內存空間就必定會被垃圾收集器收集。

 

 

 

資源的回收:

剛纔講了一堆理論的東西,下面來點實際能用上的,資源的回收:

 

Thread(線程)回收:

線程中涉及的任何東西GC都不能回收(Anything reachable by a thread cannot be GC'd ),因此線程很容易形成內存泄露。

以下面代碼所示:

 

[java]  view plain  copy
 
 print?
  1. Thread t = new Thread() {  
  2.     public void run() {  
  3.         while (true) {  
  4.             try {  
  5.                 Thread.sleep(1000);  
  6.                 System.out.println("thread is running...");  
  7.             } catch (InterruptedException e) {  
  8.               
  9.             }  
  10.         }  
  11.     }  
  12. };  
  13. t.start();  
  14. t = null;  
  15. System.gc();  
如上在線程t中每間隔一秒輸出一段話,而後將線程設置爲null而且調用System.gc方法。

 

最後的結果是線程並不會被回收,它會一直的運行下去。

 

 

由於運行中的線程是稱之爲垃圾回收根(GC Roots)對象的一種,不會被垃圾回收。當垃圾回收器判斷一個對象是否可達,老是使用垃圾回收根對象做爲參考點。

 

Cursor(遊標)回收:

 

Cursor是Android查詢數據後獲得的一個管理數據集合的類,在使用結束之後。應該保證Cursor佔用的內存被及時的釋放掉,而不是等待GC來處理。而且Android明顯是傾向於編程者手動的將Cursor close掉,由於在源代碼中咱們發現,若是等到垃圾回收器來回收時,會給用戶以錯誤提示。

因此咱們使用Cursor的方式通常以下:

 

[java]  view plain  copy
 
 print?
  1. Cursor cursor = null;  
  2. try {  
  3.     cursor = mContext.getContentResolver().query(uri,null, null,null,null);  
  4.     if(cursor != null) {  
  5.         cursor.moveToFirst();  
  6.         //do something  
  7.     }  
  8. catch (Exception e) {  
  9.     e.printStackTrace();  
  10. finally {  
  11.     if (cursor != null) {  
  12.         cursor.close();  
  13.     }  
  14. }  
有一種狀況下,咱們不能直接將Cursor關閉掉,這就是在CursorAdapter中應用的狀況,可是注意,CursorAdapter在Acivity結束時並無自動的將Cursor關閉掉,所以,你須要在onDestroy函數中,手動關閉。
[java]  view plain  copy
 
 print?
  1. @Override    
  2. protected void onDestroy() {          
  3.     if (mAdapter != null && mAdapter.getCurosr() != null) {    
  4.         mAdapter.getCursor().close();    
  5.     }    
  6.     super.onDestroy();     
  7. }    

 

Receiver(接收器)回收

 

調用registerReceiver()後未調用unregisterReceiver(). 
當咱們Activity中使用了registerReceiver()方法註冊了BroadcastReceiver,必定要在Activity的生命週期內調用unregisterReceiver()方法取消註冊 
也就是說registerReceiver()和unregisterReceiver()方法必定要成對出現,一般咱們能夠重寫Activity的onDestory()方法: 
[java]  view plain  copy
 
 print?
  1. @Override    
  2. protected void onDestroy() {    
  3.       this.unregisterReceiver(receiver);    
  4.       super.onDestroy();    
  5. }    
 

Stream/File(流/文件)回收:

主要針對各類流,文件資源等等如:

InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap圖片等操做等都應該記得顯示關閉。
和以前介紹的Cursor道理相似,就很少說了。

 

 

 

 

Review:

 

Review(回顧,檢查),你們都知道Code Review的重要性。而這裏我說的Review和Code Review差很少,主要目的就是檢查代碼中存在的不合理和能夠改進的地方,固然這個Review須要你們本身來作啦。

 

 

Code Review(代碼檢查):

Code Review主要檢查代碼中存在的一些不合理或能夠改進優化的地方,你們能夠參考以前寫的Reduce,Reuse和Recycle都是側重講解這方面的。

 

 

 

UI Review(視圖檢查):

Android對於視圖中控件的佈局渲染等會消耗不少的資源和內存,因此這部分也是咱們須要注意的。

 

減小視圖層級:
減小視圖層級能夠有效的減小內存消耗,由於視圖是一個樹形結構,每次刷新和渲染都會遍歷一次。
 
hierarchyviewer:
想要減小視圖層級首先就須要知道視圖層級,因此下面介紹一個SDK中自帶的一個很是好用的工具hierarchyviewer。
你能夠在下面的地址找到它: your sdk path\sdk\tools

 

如上圖你們能夠看到,hierarchyviewer能夠很是清楚的看到當前視圖的層級結構,而且能夠查看視圖的執行效率(視圖上的小圓點,綠色表示流暢,黃色和紅色次之),因此咱們能夠很方便的查看哪些view可能會影響咱們的性能從而去進一步優化它。

 

hierarchyviewer還提供另一種列表式的查看方式,能夠查看詳細的屏幕畫面,具體到像素級別的問題均可以經過它發現。

 

 

ViewStub標籤

此標籤可使UI在特殊狀況下,直觀效果相似於設置View的不可見性,可是其更大的意義在於被這個標籤所包裹的Views在默認狀態下不會佔用任何內存空間。

 

 

include標籤

能夠經過這個標籤直接加載外部的xml到當前結構中,是複用UI資源的經常使用標籤。

 

merge標籤

它在優化UI結構時起到很重要的做用。目的是經過刪減多餘或者額外的層級,從而優化整個Android Layout的結構。

 

(注意:靈活運用以上3個標籤能夠有效減小視圖層級,具體使用你們能夠上網搜搜)

 

 

佈局用Java代碼比寫在XML中快

通常狀況下對於Android程序佈局每每使用XML文件來編寫,這樣能夠提升開發效率,可是考慮到代碼的安全性以及執行效率,能夠經過Java代碼執行建立,雖然Android編譯過的XML是二進制的,可是加載XML解析器的效率對於資源佔用仍是比較大的,Java處理效率比XML快得多,可是對於一個複雜界面的編寫,可能須要一些套嵌考慮,若是你思惟靈活的話,使用Java代碼來佈局你的Android應用程序是一個更好的方法。

 
 

重用系統資源:

 

1. 利用系統定義的id

好比咱們有一個定義ListView的xml文件,通常的,咱們會寫相似下面的代碼片斷。

 

[html]  view plain  copy
 
 print?
  1. <ListView  
  2.     android:id="@+id/mylist"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"/>  

 

這裏咱們定義了一個ListView,定義它的id是"@+id/mylist"。實際上,若是沒有特別的需求,就能夠利用系統定義的id,相似下面的樣子。

 

[html]  view plain  copy
 
 print?
  1. <ListView  
  2.     android:id="@android:id/list"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"/>  
在xml文件中引用系統的id,只須要加上「@android:」前綴便可。若是是在Java代碼中使用系統資源,和使用本身的資源基本上是同樣的。不一樣的是,須要使用android.R類來使用系統的資源,而不是使用應用程序指定的R類。這裏若是要獲取ListView可使用android.R.id.list來獲取。

 

2. 利用系統的圖片資源

這樣作的好處,一個是美工不須要重複的作一份已有的圖片了,能夠節約很多工時;另外一個是能保證咱們的應用程序的風格與系統一致。

3. 利用系統的字符串資源

若是使用系統的字符串,默認就已經支持多語言環境了。如上述代碼,直接使用了@android:string/yes和@android:string/no,在簡體中文環境下會顯示「肯定」和「取消」,在英文環境下會顯示「OK」和「Cancel」。

4. 利用系統的Style

 假設佈局文件中有一個TextView,用來顯示窗口的標題,使用中等大小字體。可使用下面的代碼片斷來定義TextView的Style。

 

[html]  view plain  copy
 
 print?
  1. <TextView  
  2.         android:id="@+id/title"  
  3.         android:layout_width="wrap_content"  
  4.         android:layout_height="wrap_content"  
  5.         android:textAppearance="?android:attr/textAppearanceMedium" />  
其中android:textAppearance="?android:attr/textAppearanceMedium"就是使用系統的style。須要注意的是,使用系統的style,須要在想要使用的資源前面加「?android:」做爲前綴,而不是「@android:」。

 

5. 利用系統的顏色定義

除了上述的各類系統資源之外,還可使用系統定義好的顏色。在項目中最經常使用的,就是透明色的使用。

 

[html]  view plain  copy
 
 print?
  1. android:background ="@android:color/transparent"  

 

除了上面介紹的之外還有不少其餘Android系統自己自帶的資源,它們在應用中均可以直接使用。具體的,能夠進入android-sdk的相應文件夾中去查看。例如:能夠進入$android-sdk$\platforms\android-8\data\res,裏面的系統資源就盡收眼底了。

開發者須要花一些時間去熟悉這些資源,特別是圖片資源和各類Style資源,這樣在開發過程當中,能重用的儘可能重用,並且有時候使用系統提供的效果可能會更好。

 

 

其餘小tips:

1. 分辨率適配-ldpi,-mdpi, -hdpi配置不一樣精度資源,系統會根據設備自適應,包括drawable, layout,style等不一樣資源。

2.儘可能使用dp(density independent pixel)開發,不用px(pixel)。

3.多用wrap_content, match_parent

4.永遠不要使用AbsoluteLayout

5.使用9patch(經過~/tools/draw9patch.bat啓動應用程序),png格式

6.將Acitivity中的Window的背景圖設置爲空。getWindow().setBackgroundDrawable(null);android的默認背景是否是爲空。

7.View中設置緩存屬性.setDrawingCache爲true。


 

 

Desgin Review(設計檢查):

Desgin Review主要側重檢查一下程序的設計是否合理,包括框架的設計,界面的設計,邏輯的設計(其實這些東西開發以前就應該想好了)。

 

框架設計:

是否認義了本身的Activity和fragment等經常使用控件的基類去避免進行重複的工做

是否有完善的異常處理機制,即便真的出現OOM也不會直接崩潰致使直接退出程序

 

界面設計:

 

1.在視圖中加載你所須要的,而不是你所擁有。由於用戶不可能同時看到全部東西。最典型的例子就是ListView中的滑動加載。

2.若是數據特別大,此時應該暗示用戶去點擊加載,而不是直接加載。

3.合理運用分屏,轉屏等,它是個雙刃劍,由於它便可以使程序更加美觀功能更加完善,但也相應增長了資源開銷。

 

邏輯設計:

避免子類直接去控制父類中內容,可使用監聽等方式去解決

相關文章
相關標籤/搜索