知识的领域是无限的,我们的学习也是无限期的。这篇文章主要讲述App性能优化之内存优化相关的知识,希望能为你提供帮助。
本文为慕课网《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分配的最大内存限制,随不同设备而不同。查看方式:`
ActivityManager manager= (ActivityManager)getSystemService(ACTIVITY_SERVICE);
int memory=manager.getMemoryClass();
int large=manager.getLargeMemoryClass();
//大部分情况下二者相同
- 1
- 2
- 3
- 吃内存大户:图片
- app切换时的LRU cache (LRU算法,最近使用的排在最前面,最少可能的被清理掉)
- 系统清理(或内存变化)时会回调应用里activity的onTrimMemory(int level)方法。这时我们可以判断系统内存是否不足了,如果是就清理掉应用的一些不用的内存来使应用的占用内存变小,减少被系统清理掉的可能性。level对应信息
- 数据结构优化
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的必然性与可解决性,不再赘述。
- 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
2.优化OOM问题的方法
- 临时Bitmap的优化
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
public class BitmapCache {
static private BitmapCache cache;
private ArrayMap<
String,MySoftRe>
hashRef;
//软引用被回收后,回收对象放在这,可以查看哪些被回收了
private ReferenceQueue<
Bitmap>
queue;
private BitmapCache(){
hashRef=https://www.songbingjia.com/android/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
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
推荐阅读
- Android App压力测试之Monkey
- Android基础系列合集
- WPF入门教程系列三——Application介绍(续)
- 完整Android开发基础入门博客专栏
- WPF入门教程系列二——Application介绍
- WPF入门教程系列七——布局之WrapPanel与StackPanel
- Android Studio环境解读
- Android应用层View绘制流程与源码分析
- Android 菜单动态变化添加或去除