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 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内存优化(大汇总)

你可能感兴趣的:(Android内存管理(三))