App性能優化之內存優化
阿新 • • 發佈:2017-09-24
處理器 app https dma subst arraymap lru cache man onmeasure
本文為慕課網《App性能優化之內存優化》課程的學習筆記,視頻地址 (http://www.imooc.com/video/13670)
## 如何查看一個app在安卓系統中的內存分配情況?
方法一:
1.啟動android studio和虛擬機,建立連接。
2.打開cmd窗口,輸入adb shell。
3.輸入ps。
4.可以看到有一個name為應用包名的進程,這就是我們的app所在的進程
5.為了具體查看app所在進程的內存使用情況,需輸入dumpsys meminfo +包名。
方法二:
float total_memory=
Runtime.getRuntime().totalMemory()*1.0f/1024/1024;
float free_memory=
Runtime.getRuntime().freeMemory()*1.0f/1024/1024;
float max_memory=
Runtime.getRuntime().maxMemory()*1.0f/1024/1024;
- 1
- 2
- 3
- 4
- 5
- 6
方法三:
打開android studio的android monitor。
方法四:
打開android studio的Tools→Android→Android Device Monitor。
android內存分配與回收方式
- 一個App通常就是一個進程,對應一個虛擬機。
- GC(垃圾回收器)只在Heap剩余空間不足時才觸發垃圾回收。(當GC回收垃圾後Heap剩余空間仍不足,GC會發起系統請求,若GC有很多變量,且GC回收會占用處理器時間,如果處理時間很長,影響app響應)。
- GC觸發時,所有線程都會暫停,極端情況下發生線程抖動(後面會說)。
App內存限制機制
- 每個app分配的最大內存限制,隨不同設備而不同。查看方式:`
ActivityManager manager= (ActivityManager)getSystemService(ACTIVITY_SERVICE);
int memory=manager.getMemoryClass();
int large=manager.getLargeMemoryClass();//大部分情況下二者相同
- 1
- 2
- 3
- 吃內存大戶:圖片
切換應用時後臺App清理機制
- app切換時的LRU cache (LRU算法,最近使用的排在最前面,最少可能的被清理掉)
- 系統清理(或內存變化)時會回調應用裏activity的onTrimMemory(int level)方法。這時我們可以判斷系統內存是否不足了,如果是就清理掉應用的一些不用的內存來使應用的占用內存變小,減少被系統清理掉的可能性。level對應信息
App內存優化方法
- 數據結構優化
1.頻繁的字符串拼接采用StringBuilder而不是通過+的方式(會產生無用的中間字符串內存塊,視頻中二者拼接同樣字符串的總耗時為3ms和8000ms!!!)。
2.ArrayMap,SparseMap替換HashMap(HashMap效率不高,占用內存大)。
3.內存抖動(變量使用不當引起,比如突然產生很多變量或申請很多內存空間,但很快就做完事情棄之不用了,過了一會又進行上述操作,如果此時Heap不夠,GC觸發垃圾回收,此時所有線程暫停,內存使用情況會像抖動一樣忽高忽低)。
4.再小的Class也要消耗0.5KB。
5.HashMap的每個entry需要占用額外的32B。 - 對象復用
1.復用系統自帶的資源。
2.ListView/GridView的ConvertView復用(ViewHolder)。
3.避免在onDraw方法裏執行對象的創建(onMeasure也會調用多次,推薦在onSizeChanged方法內操作)。 - 避免內存泄露
內存泄露:由於代碼瑕疵,導致這塊內存雖然停止不用了,但依然被其他東西引用著,導致GC無法對其進行回收。
1.內存泄露會導致剩余Heap越來越少,GC頻繁觸發。(視頻中在activity中點擊啟動線程(簡單的休眠5分鐘),然後退出進入該activity,啟動線程,重復多次,再進入Android Device Monitor,多次點擊Cause GC啟動GC回收,發現byte-array的count會有所減少,重復上述操作,count停止減少時的值不斷增加,說明發生了內存泄露。 原因是線程是自定義內部類,會隱含的引用activity對象,且該線5min內會一直執行,如果換成休眠較短時間會有所改善。解決方法:放在service裏執行)。
2.尤其是Activity泄露
3.用Application Context而不是Activity Context(可能會經常退出),某些View如Dialog一定要Activity Context(Token)。
4.Cursor對象用完要及時關閉。
OOM問題優化
1.OOM問題分析
- OOM的必然性與可解決性,不再贅述。
- OOM的絕大部分發生在圖片。
強引用、軟引用的意義
強引用就是平時的寫法。
軟引用的用法。(虛引用與之類似)
private Map<String, SoftReference<Bitmap>> imageCache =
new HashMap<String, SoftReference<Bitmap>>();
public void addBitmapToCache(String path) {
// 強引用的Bitmap對象
Bitmap bitmap = BitmapFactory.decodeFile(path);
// 軟引用的Bitmap對象
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
//WeakReference<Bitmap> weakBitmap=new WeakReference<Bitmap>(bitmap);
TranslateAnimation animation;
// 添加該對象到Map中使其緩存
imageCache.put(path, softBitmap);
}
public Bitmap getBitmapByPath(String path) {
// 從緩存中取軟引用的Bitmap對象
SoftReference<Bitmap> softBitmap = imageCache.get(path);
// 判斷是否存在軟引用
if (softBitmap == null) {
return null;
}
// 取出Bitmap對象,如果由於內存不足Bitmap被回收,將取得空
return softBitmap.get();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
考慮如下情景:有些成員變量使用幾次後就不使用了,但仍占據著內存空間,它們隨著Activity的銷毀而被回收,即使GC觸發垃圾回收也不會對其進行回收,此時可用把它們放在SoftReference中,放入與讀取見上述代碼,GC觸發垃圾回收時就可對其進行回收了。
2.優化OOM問題的方法
- 臨時Bitmap的優化
1.BitmapFactory.Options類
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(),R.drawable.yin,options);//不直接加載,獲取bitmap圖片寬高
BitmapFactory.Options options2 = new BitmapFactory.Options();
options2.inSampleSize = scale;
Bitmap bitmap1=
BitmapFactory.decodeResource(getResources(),R.drawable.yin,options2);//scale越大,圖片越模糊,所占內存越小
- 1
- 2
- 3
- 4
- 5
- 6
- 7
//RGB_565讓ARGB只占兩個字節,大小縮小一倍,且變化不明顯
BitmapFactory.Options options=new BitmapFactory.Options();
options.inPreferredConfig= Bitmap.Config.RGB_565;
Bitmap bitmap1=
BitmapFactory.decodeResource(getResources(),R.drawable.yin,options);
- 1
- 2
- 3
- 4
- 5
//BitmapRegionDecoder類可以實現範圍選取圖片細節
BitmapRegionDecoder decoder=
BitmapRegionDecoder.newInstance(,false);
BitmapFactory.Options options2 =
new BitmapFactory.Options();
bitmap=decoder.decodeRegion(new Rect(width/2-SCREEN_WIDTH/2+shiftpx,
height/2-SCREEN_HEIGHT/2,width/2+SCREEN_WIDTH/2+shiftpx,
height/2+SCREEN_HEIGHT/2),options2);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.通過軟引用。優點是讓系統在內存不足時可以直接回收,缺點是回收沒有優先級,可能回收的不是用過的而是將要顯示的。
public class BitmapCache {
static private BitmapCache cache;
private ArrayMap<String,MySoftRe> hashRef;
//軟引用被回收後,回收對象放在這,可以查看哪些被回收了
private ReferenceQueue<Bitmap> queue;
private BitmapCache(){
hashRef=new ArrayMap<>();
queue=new ReferenceQueue<>();
}
/*
繼承SoftReference,使得每一個實例都具有可識別的標識
*/
private class MySoftRe extends SoftReference<Bitmap>{
private String key="";
public MySoftRe(Bitmap referent, ReferenceQueue<? super Bitmap> q,String key) {
super(referent, q);
this.key=key;
}
}
public static BitmapCache getInstance(){
if (cache==null){
cache=new BitmapCache();
}
return cache;
}
/*
以軟引用的方式對一個bitmap對象的實例進行引用並保存該引用
*/
public void addCacheBitmap(String key, Bitmap bitmap){
cleanCache();
MySoftRe msf=new MySoftRe(bitmap,queue,key);
hashRef.put(key,msf);
}
public Bitmap getBitmap(String key){
Bitmap bitmap=null;
try {
if (hashRef.containsKey(key)){
MySoftRe msf=hashRef.get(key);
bitmap=msf.get();
}
return bitmap;
}catch (NullPointerException e){
return null;
}
}
private void cleanCache() {
MySoftRe msf=null;
while ((msf= (MySoftRe) queue.poll())!=null){
hashRef.remove(msf.key);
}
}
public void clearCache(){
cleanCache();
hashRef.clear();
System.gc();
System.runFinalization();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
3.使用LRU Cache。
public class MemoryCache {
private static final String TAG="jason";
//LinkedHashMap專門用來構建LRU算法,但是線程不安全
private Map<String,Bitmap> cache= Collections.synchronizedMap(
new LinkedHashMap<String, Bitmap>(8,0.75f,true));
private long size=0;//MemoryCache已經分配的大小
private long limit=1000000;
public MemoryCache(){
setLimit(Runtime.getRuntime().maxMemory()/4);
}
private void setLimit(long l) {
limit=l;
Log.d(TAG,"MemoryCache will use up to"+limit/1024/1024+"MB");
}
public Bitmap get(String id){
try {
if (!cache.containsKey(id)){
return null;
}
return cache.get(id);
}catch (NullPointerException e){
return null;
}
}
public void put(String id,Bitmap bitmap){
try {
if (cache.containsKey(id)){
size-=getSizeInBytes(cache.get(id));
}
cache.put(id,bitmap);
size+=getSizeInBytes(bitmap);
checkSize();
}catch (Throwable th){
th.printStackTrace();
}
}
private void checkSize() {
Log.i(TAG,"cache size="+size+"length="+cache.size());
if (size>limit){
Iterator<Map.Entry<String,Bitmap>> iterator=cache.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String,Bitmap> entry=iterator.next();
size-=getSizeInBytes(entry.getValue());
iterator.remove();
if (size<=limit){
break;
}
}
Log.d(TAG,"Clean cache,new size="+cache.size());
}
}
private long getSizeInBytes(Bitmap bitmap) {
if (bitmap==null) {
return 0;
}
return bitmap.getRowBytes()*bitmap.getHeight();
}
public void clear(){
cache.clear();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
App性能優化之內存優化