关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »从Android资源角度谈Android代码内存优化

    从Android资源角度谈Android代码内存优化

    2015-07-21 00:00:00 出处:ASCE1885
    分享

    该文主要介绍在实际Android应用程序的开发中,容易导致内存泄露的一些情况。开发人员假如在进行代码编写之前就有内存泄露方面的基础知 识,那么写出来的代码会强壮许多,写该文也是这个初衷。本文从Android开发中的资源使用情况入手,介绍了如何在Bitmap、数据库查询、9- patch、过渡绘制等方面优化内存的使用。

    Android资源优化

    1. Bitmap优化

    Android中的大部分内存问题归根结底都是Bitmap的问题,假如打开MAT(Memory analyzer tool)来看,实际占用内存大的都是一些Bitmap(以byte数组的形式存储)。所以Bitmap的优化应该是我们着重去解决的。Google在其 官方有针对Bitmap的使用专门写了一个专题 : Displaying Bitmaps Efficiently , 对应的中文翻译在 : displaying-bitmaps , 在优化Bitmap资源之前,请先看看这个系列的文档,以确保自己正确地使用了Bitmap。

    Bitmap假如没有被释放,那么一般只有两个问题:

    用户在使用完这个Bitmap之后,没有主动去释放Bitmap资源。 这个Bitmap资源被引用所以无法被释放 。

    1.1 主动释放Bitmap资源

    当你确定这个Bitmap资源不会再被使用的时候(当然这个Bitmap不释放可能会让程序下一次启动或者resume快一些,但是其占用的内存 资源太大,可能导致程序在后台的时候被杀掉,反而得不偿失),我们建议手动调用recycle()方法,释放其Native内存:

    if(bitmap != null && !bitmap.isRecycled()){  
        bitmap.recycle(); 
        bitmap = null; 
    }

    我们也可以看一下Bitmap.java中recycle()方法的说明:

        /**
         * Free the native object associated with this bitmap, and clear the
         * reference to the pixel data. This will not free the pixel data synchronously;
         * it simply allows it to be garbage collected if there are no other references.
         * The bitmap is marked as "dead", meaning it will throw an exception if
         * getPixels() or setPixels() is called, and will draw nothing. This operation
         * cannot be reversed, so it should only be called if you are sure there are no
         * further uses for the bitmap. This is an advanced call, and normally need
         * not be called, since the normal GC process will free up this memory when
         * there are no more references to this bitmap.
         */
        public void recycle() {
            if (!mRecycled) {
                if (nativeRecycle(mNativeBitmap)) {
                    // return value indicates whether native pixel object was actually recycled.
                    // false indicates that it is still in use at the native level and these
                    // objects should not be collected now. They will be collected later when the
                    // Bitmap itself is collected.
                    mBuffer = null;
                    mNinePatchChunk = null;
                }
                mRecycled = true;
            }
        }
    
    ......
    //假如使用过程中抛出异常的判断
    if (bitmap.isRecycled()) {
        throw new RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap);
    }

    调用bitmap.recycle之后,这个Bitmap假如没有被引用到,那么就会被垃圾回收器回收。假如不主动调用这个方法,垃圾回收器也会进 行回收工作,只不过垃圾回收器的不确定性太大,依赖其自动回收不靠谱(比如垃圾回收器一次性要回收好多Bitmap,那么需要的时间就会很多,导致回收的 时候会卡顿)。所以我们需要主动调用recycle。

    1.2 主动释放Imageview的图片资源

    由于我们在实际开发中,很多情况是在xml布局文件中设置ImageView的src或者在代码中调用 ImageView.setImageResource/setImageURI/setImageDrawable等方法设置图像,下面代码可以回收这 个ImageView所对应的资源:

    private static void recycleImageViewBitMap(ImageView imageView) {
        if (imageView != null) {
            BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();
            rceycleBitmapDrawable(bd);
        }
    }
    
    private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {
        if (bitmapDrawable != null) {
            Bitmap bitmap = bitmapDrawable.getBitmap();
            rceycleBitmap(bitmap);
        }
        bitmapDrawable = null;
    }
    
    private static void rceycleBitmap(Bitmap bitmap) {
        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
            bitmap = null;
        }
    }

    1.3 主动释放ImageView的背景资源

    假如你的ImageView是有Background,那么下面的代码可以释放他:

    public static void recycleBackgroundBitMap(ImageView view) {
        if (view != null) {
            BitmapDrawable bd = (BitmapDrawable) view.getBackground();
            rceycleBitmapDrawable(bd);
        }
    }
    
    public static void recycleImageViewBitMap(ImageView imageView) {
        if (imageView != null) {
            BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();
            rceycleBitmapDrawable(bd);
        }
    }
    
    private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {
        if (bitmapDrawable != null) {
            Bitmap bitmap = bitmapDrawable.getBitmap();
            rceycleBitmap(bitmap);
        }
        bitmapDrawable = null;
    }

    1.4 尽量少用Png图,多用NinePatch的图

    现在手机的分辨率越来越高,图片资源在被加载后所占用的内存也越来越大,所以要尽量避免使用大的PNG图,在产品设计的时候就要尽量避免用一张大图来进行展示,尽量多用NinePatch资源。

    Android中的NinePatch指的是一种拉伸后不会变形的特殊png图,NinePatch的拉伸区域可以自己定义。这种图的优点是体积 小,拉伸不变形,可以适配多机型。Android SDK中有自带NinePatch资源制作工具,Android-Studio中在普通png图片点击右键可以将其转换为NinePatch资源,使用起 来非常方便。

    Android代码内存优化建议-Android资源篇

    1.5 使用大图之前,尽量先对其进行压缩

    图片有不同的形状与大小。在大多数情况下它们的实际大小都比需要呈现出来的要大很多。例如,系统的Gallery程序会显示那些你使用设备camera拍摄的图片,但是那些图片的分辨率通常都比你的设备屏幕分辨率要高很多。

    考虑到程序是在有限的内存下工作,理想情况是你只需要在内存中加载一个低分辨率的版本即可。这个低分辨率的版本应该是与你的UI大小所匹配的,这 样才便于显示。一个高分辨率的图片不会提供任何可见的好处,却会占用宝贵的(precious)的内存资源,并且会在快速滑动图片时导致(incurs) 附加的效率问题。

    Google官网的Training中,有一篇文章专门介绍如何有效地加载大图,里面提到了两个比较重要的技术:

    在图片加载前获取其宽高和类型 加载一个按比例缩小的版本到内存中

    原文地址: Loading Large Bitmaps Efficiently ,中文翻译地址: 有效地加载大尺寸位图 ,强烈建议每一位Android开发者都去看一下,并在自己的实际项目中使用到。

    更多关于Bitmap的使用和优化,可以参考Android官方Training专题的 displaying-bitmaps

    2 查询数据库没有关闭游标

    程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。假如我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。示例代码:

    Cursor cursor = getContentResolver().query(uri ...);
      if (cursor.moveToNext()) {
     	... ... 
    }

    修正示例代码:

    Cursor cursor = null;
    try {
      	cursor = getContentResolver().query(uri ...);
      if (cursor != null && cursor.moveToNext()) {
      ... ... 
      }
      } finally {
        if (cursor != null) {
      try { 
        cursor.close();
      } catch (Exception e) {
        //ignore this
        }
      }
    }

    3 构造Adapter时,没有使用缓存的convertView

    以构造ListView的BaseAdapter为例,在BaseAdapter中提供了方法:

    public View getView(int position, View convertView, ViewGroup parent)

    来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一 定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。由此可以看出,假如我们不去使用 convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。 ListView回收list item的view对象的过程可以查看:android.widget.AbsListView.java —> void addScrapView(View scrap) 方法。

    Android代码内存优化建议-Android资源篇

    示例代码:

    public View getView(int position, View convertView, ViewGroup parent) {
     View view = new Xxx(...);
     ... ...
     return view;
    }

    `示例修正代码:

    public View getView(int position, View convertView, ViewGroup parent) {
     View view = null;
     if (convertView != null) {
     view = convertView;
     populate(view, getItem(position));
     ...
     } else {
     view = new Xxx(...);
     ...
     }
     return view;
    }

    关于ListView的使用和优化,可以参考这两篇文章:

    Using lists in Android (ListView) – Tutorial Making ListView Scrolling Smooth

    4 释放对象的引用

    前面有说过,一个对象的内存没有被释放是因为他被其他的对象所引用,系统不回去释放这些有GC Root的对象。

    示例A:假设有如下操作

    public class DemoActivity extends Activity {
      ... ...
      private Handler mHandler = ...
      private Object obj;
      public void operation() {
       obj = initObj();
       ...
       [Mark]
       mHandler.post(new Runnable() {
              public void run() {
               useObj(obj);
              }
       });
      }
    }

    我们有一个成员变量 obj,在operation()中我们希望能够将处理obj实例的操作post到某个线程的MessageQueue中。在以上的代码中,即便是 mHandler所在的线程使用完了obj所引用的对象,但这个对象仍然不会被垃圾回收掉,因为DemoActivity.obj还保有这个对象的引用。 所以假如在DemoActivity中不再使用这个对象了,可以在[Mark]的位置释放对象的引用,而代码可以修改为:

    public void operation() {
      obj = initObj();
      ...
      final Object o = obj;
      obj = null;
      mHandler.post(new Runnable() {
          public void run() {
              useObj(o);
          }
      }
    }

    示例B:假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen 中定义一个PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要 显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。

    但是假如在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen 无法被垃圾回收。假如不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得 system_ui进程挂掉。

    总之当一个生命周期较短的对象A,被一个生命周期较长的对象B保有其引用的情况下,在A的生命周期结束时,要在B中清除掉对A的引用。

    使用MAT可以很方便地查看对象之间的引用,

    5 在Activity的生命周期中释放资源

    Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中,在onPause()、onStop()、 onDestroy()方法中需要适当的释放资源的情况。由于此情况很基础,在此不详细说明,具体可以查看官方文档对Activity生命周期的介绍,以 明确何时应该释放哪些资源。

    6 消除过渡绘制

    过渡绘制指的是在屏幕一个像素上绘制多次(超过一次),比如一个TextView后有背景,那么显示文本的像素至少绘了两次,一次是背景,一次是 文本。GPU过度绘制或多或少对性能有些影响,设备的内存带宽是有限的,当过度绘制导致应用需要更多的带宽(超过了可用带宽)的时候性能就会降低。带宽的 限制每个设备都可能是不一样的。

    过渡绘制的原因:

    同一层级的View叠加 复杂的层级叠加

    减少过渡绘制能去掉一些无用的View,能有效减少GPU的负载,也可以减轻一部分内存压力。关于过渡绘制我专门写了一篇文章来介绍:过渡绘制及其优化

    7 使用Android系统自带的资源

    在Android应用开发过程中,屏幕上控件的布局代码和程序的逻辑代码通常是分开的。界面的布局代码是放在一个独立的xml文件中的,这个文件 里面是树型组织的,控制着页面的布局。通常,在这个页面中会用到很多控件,控件会用到很多的资源。Android系统本身有很多的资源,包括各种各样的字 符串、图片、动画、样式和布局等等,这些都可以在应用程序中直接使用。这样做的好处很多,既可以减少内存的使用,又可以减少部分工作量,也可以缩减程序安 装包的大小。

    比如下面的代码就是使用系统的ListView:

    <ListView 
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>

    8 使用内存相关工具检测

    在开发中,不可能保证一次就开发出一个内存管理非常棒的应用,所以在开发的每一个阶段,都要有意识地去针对内存进行专门的检查。目前Android提供了许多布局、内存相关的工具,比如Lint、MAT等。学会这些工具的使用是一个Android开发者必不可少的技能。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    小米路由器买哪款?Miwifi热门路由器型号对比分析

    IT头条

    Synology 对 Office 套件进行重大 AI 更新,增强私有云的生产力和安全性

    01:43

    StorONE 的高效平台将 Storage Guardian 数据中心占用空间减少 80%

    11:03

    年赚千亿的印度能源巨头Nayara 云服务瘫痪,被微软卡了一下脖子

    12:54

    国产6nm GPU新突破!砺算科技官宣:自研TrueGPU架构7月26日发布

    01:57

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    技术热点

    如何删除自带的不常用应用为windows 7减负

    MySQL中多表删除方法

    改进的二值图像像素标记算法及程序实现

    windows 7 32位系统下手动修改磁盘属性例如M盘修改为F盘

    windows 7中怎么样在家庭组互传文件

    Linux应用集成MySQL数据库访问技巧

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。