Android 效能優化——記憶體篇
http://www.cnblogs.com/hoolay/p/6278229.html
Author : Hoolay Android Team Chiclaim
一、android官方一些記憶體方面的記憶體tips
1、避免建立不必要的物件。
- 如儘量避免字串的加號拼接,可以使用StringBuilder來拼接。
- 如果需要TextView設定多個字串片段,可以使用textView.append方法,不要直接用加號拼起來。
2、儘量使用for-each迴圈,對於ArrayList,請使用普通的for,如:
int len = list.size();
for (int i = 0; i < len; ++i) {
//todo somgthing
}
3、使用系統庫函式。
- 使用系統庫函式,並且還有彙編級別的優化,他們通常比帶有JIT的Java編譯出來的程式碼更高效 如:
System.arraycopy()
;String.indexOf()
等。 - 如果系統函式能夠解決的,不要加入第三方庫,可能第三方庫使用起來比較簡單(
jsoup
,htmlparser
等)。如解析HTML可以使用系統的XmlPullParser
。
二、使用 ArrayMap、SparseArray代替HashMap
ArrayMap 和 HashMap的在記憶體的使用上,更加高效。
ArrayMap實現上有兩個陣列,一個數組是儲存key hash,另一個數組儲存value,ArrayMap通過二分法(binary search)來進行查詢的。
HashMap通過一個數組來實現的,key hash作為陣列的索引,這樣就需要更大的記憶體來減少key hash的衝突,key hash就是陣列的索引,所以查詢效率很高。
使用建議:
-
當資料量比較小的時候(小於1000),優先使用ArrayMap,否則使用HashMap。
-
map裡巢狀map。
使用方法:
-
ArrayMap和HashMap的使用方法都是一樣的,ArrayMap也實現了Map介面。
-
另外,ArrayMap可以通過keyAt(index)方法來獲取第index位置的key,keyValue(int index)同理。但是HashMap是不可以的。
arrayMap.keyAt(0);
arrayMap.valueAt(0);
SparseArray和ArrayMap非常像,它們都是通過兩種緊密包裝的陣列,而不是一個大的雜湊雜湊,從而減少了整個記憶體的覆蓋區。但是查詢的速度就慢了。
只不過SparseArray和ArrayMap最大的區別是SparseArray的key是一個基本型別。
SparseArray的key是int型別,而不是Integer。像以前使用HashMap的時候,如果key是整形,必須是Integer。
Integer佔16個位元組,int只佔4個位元組,如果元素比較多,從而可以很好的減少記憶體的佔用。
除了SparseArray類還有如下類可供使用:
SparseBooleanMap <boolean,Object>
SparseIntMap <int,Object>
SparseLongMap <long,Object>
SparseArray和ArrayMap的使用建議
和 使用方法
都是一樣的。
三、Thread與Thread Pool
在android開發中,一些耗時的操作都會放到後臺執行緒去執行,比如:網路、本地檔案、資料庫等。
new Thread(new Runnable() {
@Override
public void run() {
//do something...
}
}).start();
每個執行緒至少消耗64k的記憶體,如果你在某個時間點,迅速開啟了很多執行緒(比如載入列表圖片,然後使用者滑動列表),這個時候可能記憶體使用量就會飆升。
-
會出現記憶體抖動(memory churn),因為短時間開啟了很多執行緒,完成任務後,這些執行緒都會被回收。記憶體表現為:低-高-低。甚至可能出現OOM。
-
一個系統所能處理的執行緒數量是有限的,如果超多了最大承載量,效能會受到很大的影響。而且可能還會影響使用者的後續操作。
這時候Thread Pool執行緒池的作用就凸顯出來了。
Java為我們提供了操作執行緒池的api ThreadPoolExecutor ,ExecutorService是一個介面,相關的執行緒池的類都實現了該介面,如 ThreadPoolExecutor 。
建立一個執行緒池可以通過 ThreadPoolExecutor類來實現。
ThreadPoolExecutor pool = new ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);//新建一個執行緒池
pool.execute(Runnable);//執行任務
下面是官方對ThreadPoolExecutor的引數說明:
Parameters:
corePoolSize - the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
maximumPoolSize - the maximum number of threads to allow in the pool
keepAliveTime - when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
unit - the time unit for the keepAliveTime argument
workQueue - the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method.
-
corePoolSize
核心執行緒數,核心執行緒會一直存活,即使沒有任務需要處理。當執行緒數小於核心執行緒數時,即使現有的執行緒空閒,執行緒池也會優先建立新執行緒來處理任務,而不是直接交給現有的執行緒處理。核心執行緒在allowCoreThreadTimeout被設定為true時會超時退出,預設情況下不會退出。 -
maxPoolSize
執行緒池允許最大的執行緒數量。 -
keepAliveTime
當執行緒空閒時間達到keepAliveTime,該執行緒會退出,直到執行緒數量等於corePoolSize。如果allowCoreThreadTimeout設定為true,則所有執行緒均會退出直到執行緒數量為0。 -
allowCoreThreadTimeout
是否允許核心執行緒空閒keepAliveTime退出,預設值為false。 -
workQueue
任務佇列。pool.execute(runnable)提交的task都會放到workQueue。
下面來一個簡單的sample:
public class MyClass {
private ThreadPoolExecutor pool ;
private MyClass(){
//建立執行緒池
pool = new ThreadPoolExecutor(4, 7, 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static void main(String[] args) {
MyClass myClass = new MyClass();
for (int i = 0; i < 10; i++) {
//提交任務
myClass.pool.execute(new MyRunnable(myClass));
}
myClass.pool.shutdown();
}
private String getCount() {
return pool.getCorePoolSize()+"-"+pool.getActiveCount() + "-" + pool.getMaximumPoolSize();
}
private static class MyRunnable implements Runnable {
MyClass myClass;
MyRunnable(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
System.out.println("thread name:" + Thread.currentThread().getName() + " start " + myClass.getCount());
try {
//模擬耗時任務
Thread.sleep(3000L);
System.out.println("thread name:" + Thread.currentThread().getName() + " end " + myClass.getCount());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面的程式碼很簡單:建立了一個corePoolSize為4,maxPoolSize為7的執行緒池。
然後往執行緒池裡提交10個任務,每個任務列印pool.getCorePoolSize()+"-"+pool.getActiveCount() + "-" + pool.getMaximumPoolSize(),即corePoolSize(核心執行緒數),activeCount(正在活動的執行緒總數)和maximumPoolSize(執行緒池允許的最大執行緒數)值。
測試結果如下:
thread name:pool-1-thread-1 start 4-2-7
thread name:pool-1-thread-2 start 4-4-7
thread name:pool-1-thread-3 start 4-4-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-1 end 4-4-7
thread name:pool-1-thread-2 end 4-3-7
thread name:pool-1-thread-4 end 4-4-7
thread name:pool-1-thread-1 start 4-4-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-2 start 4-4-7
thread name:pool-1-thread-3 end 4-4-7
thread name:pool-1-thread-3 start 4-4-7
thread name:pool-1-thread-2 end 4-4-7
thread name:pool-1-thread-4 end 4-4-7
thread name:pool-1-thread-3 end 4-4-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-1 end 4-3-7
thread name:pool-1-thread-2 start 4-4-7
thread name:pool-1-thread-4 end 4-2-7
thread name:pool-1-thread-2 end 4-2-7
Process finished with exit code 0
從測試結果來看,我們列印pool.getCorePoolSize()+"-"+pool.getActiveCount() + "-" + pool.getMaximumPoolSize()的值是正常的。但是隻建立了4個執行緒:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
我們設定了執行緒池的最大數為7,我們提交了10個任務,但是為什麼只建立了corePoolSize=4個執行緒?
檢視官方文件可以找到答案:
-
當通過execute(Runnable)提交一個新任務,並且小於corePoolSize正在執行的執行緒數,將會建立一個新的執行緒來處理這個任務,不管執行緒池裡有沒有空閒的執行緒。
-
If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full.
大於corePoolSize小於maximumPoolSize,workQueue佇列滿了,才會建立新的執行緒。 -
如果corePoolSize和maximumPoolSize值設定成一樣的,相當於建立了一個固定數量的執行緒池。
-
多數情況下,都是通過構造方法來設定corePoolSize和maximumPoolSize,但是也可以通過setCorePoolSize和setMaximumPoolSize來動態設定。
所以上面的例子,只建立了4個執行緒,因為雖然我們提交了10個任務,但是構建workQueue
時候沒有傳入佇列大小,預設大小是Integer.MAX_VALUE,所以workQueue是不會滿的。所以最多就建立了4個執行緒。
據此,我把workQueue
佇列容量改成4:
pool = new ThreadPoolExecutor(4, 7, 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(4));
測試結果:
thread name:pool-1-thread-1 start 4-2-7
thread name:pool-1-thread-2 start 4-2-7
thread name:pool-1-thread-3 start 4-3-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-5 start 4-6-7
thread name:pool-1-thread-6 start 4-6-7
thread name:pool-1-thread-1 end 4-6-7
thread name:pool-1-thread-2 end 4-6-7
thread name:pool-1-thread-2 start 4-5-7
thread name:pool-1-thread-1 start 4-6-7
thread name:pool-1-thread-3 end 4-6-7
thread name:pool-1-thread-3 start 4-6-7
thread name:pool-1-thread-4 end 4-6-7
thread name:pool-1-thread-5 end 4-6-7
thread name:pool-1-thread-4 start 4-6-7
thread name:pool-1-thread-6 end 4-5-7
thread name:pool-1-thread-1 end 4-4-7
thread name:pool-1-thread-2 end 4-4-7
thread name:pool-1-thread-3 end 4-2-7
thread name:pool-1-thread-4 end 4-1-7
Process finished with exit code 0
發現建立了6個執行緒,大於上一次的測試結果(上一次是建立了4個執行緒),可是我們設定的maximumPoolSize為7,按道理應該是建立7個執行緒才對呀,這是為什麼呢?
這需要了解下workQueue
佇列的策略了。我們上面的列子使用的是 LinkedBlockingQueue。
下面來看看官方文件對 BlockingQueue的描述:
Any link BlockingQueue may be used to transfer and hold
submitted tasks. The use of this queue interacts with pool sizing:
If fewer than corePoolSize threads are running, the Executor
always prefers adding a new thread
rather than queuing.
If corePoolSize or more threads are running, the Executor
always prefers queuing a request rather than adding a new
thread.
If a request cannot be queued, a new thread is created unless
this would exceed maximumPoolSize, in which case, the task will be
rejected.
主要意思就是:
-
如果執行的執行緒少於 corePoolSize,Executor會建立新執行緒來執行任務,不會把任務放進queue。
-
如果執行的執行緒等於或多於 corePoolSize,Executor將請求加入佇列,而不是建立新的執行緒。
-
如果佇列已滿,無法將請求加入佇列,則建立新的執行緒,除非建立此執行緒超出 maximumPoolSize,在這種情況下,任務將被拒絕。
這樣就能解釋為什麼只建立6個執行緒了。
總共有10個task,核心執行緒corePoolSize=4,所以3個任務是不會放進queue的,直接建立3個新執行緒來處理task了,然後再執行execute(Runnable)的時候,就會大於等於corePoolSize,所以就會把接下來的4個任務放進queue(容量為4),然後就剩下3個task了,發現佇列已經滿了,建立3個執行緒來處理這剩下的3個task,所以總共只建立6個執行緒了。
maximumPoolSize=7,我就是想讓它建立7個執行緒,我們知道了上面的原理就很簡單了,可以把佇列的最大容量改成3或者新增11個任務就可以了:
new LinkedBlockingQueue<Runnable>(3)
或
for (int i = 0; i < 11; i++) {
myClass.pool.execute(new MyRunnable(myClass));
}
效果如下:
thread name:pool-1-thread-1 start 0-1-7
thread name:pool-1-thread-2 start 0-2-7
thread name:pool-1-thread-3 start 1-4-7
thread name:pool-1-thread-4 start 4-5-7
thread name:pool-1-thread-5 start 4-7-7
thread name:pool-1-thread-6 start 4-7-7
thread name:pool-1-thread-7 start 4-7-7
.....
Java提供了3種策略的Queue
-
SynchronousQueue 直接傳送task(Direct handoffs)
-
LinkedBlockingQueue 無邊界佇列(Unbounded queues)
-
ArrayBlockingQueue 有邊界佇列(Bounded queues)
更多詳細資訊可以檢視官方文件。
Java給我們提供了一些工廠方法來來建立執行緒池():
-
Executors.newFixedThreadPool(int threads);
-
Executors.newCachedThreadPool();
-
Executors.newSingleThreadExecutor();
-
Executors.newScheduledThreadPool(int threads);
這些方法都是通過構建ThreadPoolExecutor來實現的,具體的細節可以去看看文件,如果都不滿足你的需求,可以自己構造ThreadPoolExecutor。
四、IntentService與Service
一般我們在app裡的版本更新邏輯在Service裡起一個執行緒來檢測。
為了避免Service一直存在,減少記憶體消耗,檢測版本後,還需要手動stopSelf,略麻煩。
這時候用IntentService就比較合適了,預設就給你啟動了一個執行緒來執行耗時操作,完成自動關閉service。
Service和IntentService主要區別:
-
IntentService執行完會自動關閉(stopSelf),而Service不會。
-
IntentService會啟動一個執行緒來執行耗時操作,把耗時操作放到onHandleIntent(Intent intent)方法裡。而Service需要自己new Thread。
-
如果呼叫
startService(intent)
多次,IntentService會執行多次onHandleIntent(Intent intent)
,且必須等本次的onHandleIntent(Intent intent)執行完,才會執行下一次onHandleIntent(Intent intent),說白了就是如果正在執行任務,會把後面啟動的命令放到佇列裡。而多次呼叫startService(intent),Service僅僅會多次呼叫onStartCommand
方法。
五、避免常見的記憶體洩露
1、CountDownTimer、TimerTask、Handler導致的記憶體洩露
在專案中,我們常常可能要做活動倒計時的功能,我是用CountDownTimer來做的。如:
public static class TimeCounter extends CountDownTimer {
public TimeCounter(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
@Override
public void onFinish() {
//倒計時結束
}
@Override
public void onTick(long millisUntilFinished) {
//每間隔固定時間執行一次
//再次處理倒計時邏輯
}
}
如下圖所示:
因為要在TimeCounter內部要修改View的顯示,所以要把TextView傳遞進來,使用WeakReference來引用TextView避免記憶體洩露,如:
相關推薦
Android 效能優化——記憶體篇
歡迎轉載,轉載請標明出處【Hoolay Team】:
http://www.cnblogs.com/hoolay/p/6278229.html
Author : Hoolay Android Team
Chiclaim
一、android官方一
Android 效能優化—記憶體篇
一、android官方一些記憶體方面的記憶體tips
1、避免建立不必要的物件。
如儘量避免字串的加號拼接,可以使用StringBuilder來拼接。
如果需要TextView設定多個字串片段,可以使用textView.append方法,不要直接用加號拼起
Android效能優化--記憶體篇
看到很多關於記憶體優化的部落格好文,也收藏了好多地址,但每次看時都需要在眾多收藏的地址裡尋找一番,不如寫到自己的部落格裡,方便隨時翻看。
記憶體優化也是android進階的必學內容。APP記憶體的使用,是評價一款應用效能高低的一個重要指標。
1.記憶體與記憶
android效能優化——記憶體洩漏
在專案初期階段或者業務邏輯很簡單的時候對於app效能之一塊沒有太多感覺,但是隨著專案版本的迭代和專案業務邏輯越來越大,越來越複雜的時候,就會逐漸感覺到app效能的重要性,所以在專案初期階段時,就要有app效能這一意識,也便於專案後期的版本迭代和業務擴充套件;這裡所提到的效能優化問題是:記憶體洩漏
Android效能優化——工具篇
Android效能優化是Android開發中經常遇見的一個問題,接下來將對Android效能優化方面的知識點做一個簡單的梳理和總結,將從工具和程式碼兩方面進行梳理。所謂工欲善其事必先利其器,本文首先來看一下Android效能優化有哪些得力的工具。
1、TraceView
使用 adb 獲取 Android 效能資料--記憶體篇
記憶體說明
VSS -
Virtual Set Size 虛擬耗用記憶體(包含共享庫佔用的記憶體)
RSS -
Resident Set Size 實際使用實體記憶體(包含共享庫佔用的記憶體)
PSS -
Proportional Set Size 實際使用的實體記憶
Android 效能優化 ---- 記憶體優化
### 1、Android記憶體管理機制
#### 1.1 Java記憶體分配模型
先上一張JVM將記憶體劃分區域的圖
![](https://img2020.cnblogs.com/blog/967362/202007/967362-20200717091650518-110380858.png)
程式
Android效能優化篇之記憶體優化--記憶體洩漏
文章目錄
介紹
什麼是記憶體洩露
android中導致記憶體洩漏的主要幾種情況
1.單例模式
2.使用非靜態內部類
3.使用非同步事件處理機制Handler
4.使用靜態
[Android 效能優化系列]記憶體之基礎篇--Android如何管理記憶體
轉載請標明出處(http://blog.csdn.net/kifile),再次感謝
在接下來的一段時間裡,我會每天翻譯一部分關於效能提升的Android官方文件給大家
下面是本次的正文:
################
隨機訪問儲存器(Ram) 不管在哪種軟體開發
Android效能優化之記憶體篇
Google近期在Udacity上釋出了Android效能優化的線上課程,分別從渲染,運算與記憶體,電量幾個方面介紹瞭如何去優化效能,這些課程是Google之前在Youtube上釋出的Android效能優化典範專題課程的細化與補充。
下面是記憶體篇章的學習筆記,部分內容與前面的效能優化典範有重合,歡
【朝花夕拾】Android效能優化篇之(四)Apk打包
APK,即Android Package,是將android程式和資源整合在一起,形成的一個.apk檔案。相信所有的Android程式設計師是在IDE的幫助下,完成打包輕而易舉,但對打包流程真正清楚的可能並不多。本章的內容比較簡單,也是非常基礎的內容,但是對理解android應用的結構卻有很大
【朝花夕拾】Android效能優化篇之(一)序言及JVM篇
序言
筆者從事Anroid開發有些年頭了,深知掌握Anroid效能優化方面的知識的必要性,這是一個程式設計師必須修煉的內功。在面試中,它是面試官的摯愛,在工作中,它是程式碼質量的攔路虎,其重要性可見一斑。在團隊中,效能優化的工作又往往由經驗豐富的老師傅來完成,可見要做好效能優化,絕不是一件容易的事情。
【朝花夕拾】Android效能優化篇之(五)Android虛擬機器簡介
前言
Android虛擬機器的使用,使得android應用和Linux核心分離,這樣做使得android系統更穩定可靠,比如程式中即使包含惡意程式碼,也不會直接影響系統檔案;也提高了跨平臺相容性。在Android4.4以前的系統中,Android系統均採用Dalvik作為執行andorid程式的
【朝花夕拾】Android效能優化篇之(五)Android虛擬機器
前言
Android虛擬機器的使用,使得android應用和Linux核心分離,這樣做使得android系統更穩定可靠,比如程式中即使包含惡意程式碼,也不會直接影響系統檔案;也提高了跨平臺相容性。在Android4.4以前的系統中,Android系統均採用Da
android效能優化實戰前篇
本文地址:http://blog.csdn.net/iamws/article/details/51629160
前言:
最近因為某專案cpu,記憶體的使用率實在讓人不敢恭維;手機發燙,電量下降已經讓使用者無法忍受;頻繁快速迭代發版導致各種效能問題突出;由
[Android 效能優化系列]佈局篇之減少你的介面層級
轉載請標明出處(http://blog.csdn.net/kifile),再次感謝
在接下來的一段時間裡,我會每天翻譯一部分關於效能提升的Android官方文件給大家
效能優化之佈局篇:
題外話:
複雜的佈局,既會提高我們的設計難度,也會降低我們的程式碼效
Android 效能優化之String篇
Android 效能優化之 String篇
關於String相關知識都是老掉牙的東西了,但我們經常可能在不經意的String 字串拼接的情況下浪費記憶體,影響效能,也常常會成為觸發記憶體OOM的最後一步。
所以本文對String字串進行深度解析,有
Android 效能優化——管理應用的記憶體
請保持淡定,分析程式碼,記住:效能很重要。
隨機存取儲存器(RAM)在任何軟體開發環境中都是一個很寶貴的資源。這一點在實體記憶體通常很有限的移動作業系統上,顯得尤為突出。儘管 Android Runtime(ART)和 Dalvik 虛擬機器都扮演了常
Android 效能優化之記憶體洩漏檢測以及記憶體優化(中)
Android 記憶體洩漏檢測
通過上篇部落格我們瞭解了 Android JVM/ART 記憶體的相關知識和洩漏的原因,再來歸類一下記憶體洩漏的源頭,這裡我們簡單將其歸為一下三類:自身編碼引起由專案開發人員自身的編碼造成;第三方程式碼引起這裡的第三
Android 效能優化之記憶體檢測、卡頓優化、耗電優化、APK瘦身
導語 自2008年智慧時代開始,Android作業系統一路高歌,10年智慧機發展之路,如今 Android 9.0 代號P 都發布了,Android系統性能已經非常流暢了。但是,到了各大廠商手裡,改原始碼自定系統,使得Android原生系統變得魚龍混雜。另外,到了不同層次的