Android内存管理(三)

声明:大部分内容为从其他文章中摘录感兴趣的部分,只为记录给自己看。

Dalvik(Just in Time)

什么是Dalvik?Dalvik是Google公司自己设计用于Android平台的Java虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android异动设备平台的核心组件部分之一,它可以支持已经转换为.dex(Dalvik Executable)格式的Java程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik经过优化,允许在有限的内存中同事运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。

ART(Ahead of Time)

ART上应用启动快,运行快,但是耗费更多存储空间,安装时间长,总的来说ART的功效就是“空间换时间”。

什么是ART?Android操作系统已经成熟,Google的Android团队开始将注意力转向一些底层组件,其中之一是负责应用程序运行的Dalvik运行时。Google开发者已经花了两年时间开发更快执行效率更高更省电的替代ART运行时。ART代表Android Runtime,其处理应用程序的方式完全不同于Dalvik,Dalvik是依靠一个Just-In-Time编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。

ART的优点:

  • 系统性能的显著提升
  • 应用启动更快、运行更快、体验更流畅、触感反馈更及时
  • 更长的电池续航能力
    ART的缺点:
  • 更大的存储空间占用,可能会增加10%-20%
  • 更长的应用安装时间

Dalvik和Java字节码的区别

Dalvik执行.dex格式的字节码文件,JVM执行的是.class格式的字节码文件,Android程序在编译之后产生的.class文件会被aapt工具处理生成R.class等文件,然后dx工具会把.class文件处理成.dex文件,最终资源文件和.dex文件等打包成.apk文件。

在Android2.3以后的版本中,系统会优先将SoftReference的对象提前回收掉,即使内存够用。所以谷歌官方建议用LruCache(least recentlly use,最少最近使用算法)。会将内存控制在一定的大小内,超出最大值时会自动回收,这个最大值开发者自己定。其实LruCache就是用了很多HashMap。

在开发过程中,保存对象,这时我们可以直接用LruCache来代替Bitmap对象。

在Android开发过程中,我们常常使用HashMap保存对象,但是为了防止内存泄漏,在保存内存占用较大、生命周期较长的对象的时候,尽量使用LruCache代替HashMap。

// 制定最大缓存空间
private static final int MAX_SIZE = (int) (Runtime.getRuntime().maxMemory() / 8);
LruCache<String, Bitmap> mBitmapLruCache = new LruCache<>(MAX_SIZE);

滑动

实现流畅滑动的技巧:UI线程只用作UI渲染。这一条真谛能够解决99%的滑动卡顿问题。不要在UI线程做下面的事情:

  • 载入图片
  • 网络请求
  • 解析JSON
  • 读取数据库

做这些操作是很慢的,像图片、网络、JSON考虑用现成的库,有很多社区提供的解决方案,数据考虑用Loader,支持批量更新和载入。

图片相关的库有很多,比如Glide,Picasso,Fresco。

Bitmap操作是很需要技巧的,图片一般比较大,而且系统对最大内存又有限制和要求。在我面对4.0之前的系统时,我简直要崩溃了。内存管理也很需要技巧。有的时候需要放到文件里,有时候需要放到内存里,别忘了,我们还有一个很有用的工具:LRUCache。

Java的网络请求确实是Android的一个阻碍。很多Java.net的API都是阻断执行的,切记不可再UI线程执行网络请求。在线程里执行或者直接使用第三方库吧。

异步HTTP其实也挺麻烦的,4.4起OkHttp就成了Android代码的一部分,然而如果你需要最新版本的OkHttp,可以考虑自己引入。另外有个不错的库叫Volley,也可以试试Square的Retrofit。这些都能让你的网络请求变得更友好。

在UI线程,也不做解析JSON的事情,因为这是一个很耗时的事情。试着用Google的GSON来做反序列化的操作。对于巨大的JSON解析,建议用更快的Jackson以及ig-json-parser。这两个工具在JSON的解析上做的非常漂亮。

Looper.myLooper() == Looper.getMainLooper()是可以帮助你确定是否在主线程的代码。

如何优化滑动速度?

  • UI线程只做UI更新。
  • 理解并发API。
  • 开始使用优秀的第三方库。
  • 使用Loader加载数据库数据。

并发APIs

如何让App快速响应请求很重要。开发者们经常忘记Service的方法是在UI线程执行的。请考虑使用

  • IntentService
    IntentService是一个单线程,一次一个任务的工作流。
  • AsyncTask
    如果你的任务需要更新UI,那么考虑用AsyncTask吧,AsyncTask虽然相对容易,单有些坑得留意。当你旋转手机的时候,Activity会被关闭,然后重启。不然可能造成内存泄漏。

界面卡顿的主要元凶——过度绘制(overdraw)

什么是过度绘制?过度绘制是指屏幕上某个像素在同一帧的时间内绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制操作,这就会导致某些像素区域被绘制了多次,这就是很大程度上浪费了CPU和GPU。最常见的过度绘制,就是设置了无用的背景颜色。

解决办法:

  • 通过Hierarchy Viewer去检测渲染效率,去除不必要的嵌套
  • 通过Show GPU Overdraw去检测Overdraw,最终可以通过移除不必要的背景

Bitmap是内存消耗大户,绝大多数的OOM崩溃都是在操作Bitmap时产生的,下面来看看几个处理图片的方法:

  • 图片显示
    我们需要根据需求去加载图片的大小。例如在列表中仅用于预览时加载缩略图(thumbnails )。只有当用户点击具体条目想看详细信息的时候,这时另启动一个fragment/activity/对话框等等,去显示整个图片。
  • 图片大小
    直接使用ImageView显示bitmap会占用较多资源,特别是图片较大时,可能导致崩溃。 使用BitmapFactory.Options设置inSampleSize, 这样做可减少对系统资源的要求。
    属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();  
bitmapFactoryOptions.inJustDecodeBounds = true;  
bitmapFactoryOptions.inSampleSize = 2;  
// 这里一定要将其设置回false,因为之前我们将其设置成了true    
// 设置inJustDecodeBounds为true后,decodeFile并不分配空间,即,BitmapFactory解码出来的Bitmap为Null,但可计算出原始图片的长度和宽度    
options.inJustDecodeBounds = false;  
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没有透明度属性),如下:

publicstaticBitmapreadBitMap(Contextcontext, intresId) {  
    BitmapFactory.Optionsopt = newBitmapFactory.Options();  
    opt.inPreferredConfig = Bitmap.Config.RGB_565;  
    opt.inPurgeable = true;  
    opt.inInputShareable = true;  
    //获取资源图片   
    InputStreamis = context.getResources().openRawResource(resId);  
    returnBitmapFactory.decodeStream(is, null, opt);  
}  
  • 图片回收
    使用Bitmap过后,就需要及时的调用Bitmap.recycle()方法来释放Bitmap占用的内存空间,而不要等Android系统来进行释放。
// 先判断是否已经回收  
if(bitmap != null && !bitmap.isRecycled()){  
    // 回收并且置为null  
    bitmap.recycle();  
    bitmap = null;  
}  
System.gc();  

了解并使用类库

选择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底层执行。

Bitmap缓存分为两种:

一种是内存缓存,一种是硬盘缓存。

  • 内存缓存(LruCache):
    以牺牲宝贵的应用内存为代价,内存缓存提供了快速的Bitmap访问方式。系统提供的LruCache类是非常适合用作缓存Bitmap任务的,它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。
    注意:以前有一个非常流行的内存缓存实现是SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,然而现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,这使得上述的方案相当无效。

  • 硬盘缓存(DiskLruCache):
    一个内存缓存对加速访问最近浏览过的Bitmap非常有帮助,但是你不能局限于内存中的可用图片。GridView这样有着更大的数据集的组件可以很轻易消耗掉内存缓存。你的应用有可能在执行其他任务(如打电话)的时候被打断,并且在后台的任务有可能被杀死或者缓存被释放。一旦用户重新聚焦(resume)到你的应用,你得再次处理每一张图片。
    在这种情况下,硬盘缓存可以用来存储Bitmap并在图片被内存缓存释放后减小图片加载的时间(次数)。当然,从硬盘加载图片比内存要慢,并且应该在后台线程进行,因为硬盘读取的时间是不可预知的。
    注意:如果访问图片的次数非常频繁,那么ContentProvider可能更适合用来存储缓存图片,例如Image Gallery这样的应用程序。

参考
10条提升Android性能的建议
ANDROID内存优化(大汇总)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348

推荐阅读更多精彩内容