美文网首页
Android内存管理(三)

Android内存管理(三)

作者: 风淋天下 | 来源:发表于2018-09-10 10:06 被阅读0次
    声明:大部分内容为从其他文章中摘录感兴趣的部分,只为记录给自己看。
    

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

    相关文章

      网友评论

          本文标题:Android内存管理(三)

          本文链接:https://www.haomeiwen.com/subject/xithgftx.html