文章目录

  • Android性能优化
    • 普通优化
    • 1.布局优化
    • 2.绘制优化
    • 3.内存泄漏优化
      • Android内存的管理
      • 内存泄漏的实例
        • 1.静态变量
          • 单例模式的使用
        • 2.集合类
        • 3.非静态内部类/匿名类
          • 3.1创建非静态内部类的静态对象
          • 3.2 匿名类持有外部类的引用
          • 3.3 Handler
        • 3.4 资源使用未关闭
        • 3.5 ListView的Adapter导致的内存泄漏
    • 4ListView优化
    • 5.BitMap优化
      • Bitmap的高效加载
      • Bitmap的缓存策略
    • 6.线程优化
    • 7.响应速度优化
    • 8.其他建议

部分图片来自 Android性能优化:关于 内存泄露 的知识都在这里了!

Android性能优化

普通优化

1.布局优化

  • 尽量避免嵌套,可以使用RelativeLayout来取代LinearLayout的多层嵌套,但在使用Relativelayout和LinearLayout都可以的时候,使用LinearLayout。
  • 使用include来复用xml文件
<include layout = "@layout/titlebar">
  • 使用merge来减少布局的层级
  • 使用ViewStub可以让布局在使用时再被加载,提高初始化时的性能。其中id是当前ViewStub的id,而layout则是ViewStub所包含的layout文件,ll_include则是当前layout/include_main_test布局所对应的根元素的id。
    <ViewStub        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout="@layout/include_main_test"        android:inflatedId="@id/ll_include"        android:id="@+id/stud_import"        />

2.绘制优化

  1. 在onDraw中不要创建局部变量,因为onDraw会多次调用会占用内存,同时导致多次GC。
  2. 在onDraw中不要进行耗时操作,也不能进行成千上万次的循环,这样会导致View的绘制不流畅。

3.内存泄漏优化

内存泄漏:程序申请的内存,在使用后无法释放,是导致OOM的主要原因。

内存泄漏的实质无意识地持有对象引用,使得 持有引用者的生命周期 > 被引用者的生命周期

内存溢出:程序在申请内存时,没有足够的内存够其使用

Android内存的管理


进程的内存管理:(内存分配与内存回收)
通过AMS对进程进行内存分配由Framework决定回收的进程类型,当内存紧缺时,会按照优先级的顺序对进程进行回收Linux 内核真正回收具体进程。

对象的内存管理与Java虚拟机类似。

存储包括

方法区中存放已被加载的类信息,以及常量静态变量
栈帧存放方法执行时的局部变量,包括数据类型对象引用
而对象的实例存于堆中。实例包括对象以及对象所在类的成员变量等等

释放是对堆中进行GC操作

内存泄漏的实例

1.静态变量

被 Static 关键字修饰的成员变量的生命周期 = 应用程序的生命周期

当前情况下context(成员变量)的生命周期为应用程序的生命周期大于Activity(引用实例)的生命周期,因此Activity在回收时,由于Context对Activity的持有,因此Activity无法回收。

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    //内存泄漏    private static Context mContext;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mContext = this;    }}

解决方法
尽量避免静态成员变量引用资源耗费过多的实例(如 Context)如果需要,可以使用Applaction的Context
context.getApplicationContext()

单例模式的使用

我们需要通过context来创建一个单例的对象(静态对象,因为是单例模式),但我们不能直接使用context,而应该通过context.getApplicationContext()来获取。(不能让单例的对象一直引用Activity,这样当Activity准备销毁时,也会因为被单例对象引用而无法销毁,导致内存泄漏)

public class SingleInstanceClass {        private static SingleInstanceClass instance;        private Context mContext;        private SingleInstanceClass(Context context) {                this.mContext = context.getApplicationContext(); // 传递的是Application 的context    }        public static SingleInstanceClass getInstance(Context context) {                if (instance == null) {            instance = new SingleInstanceClass(context);        }                return instance;    }}

2.集合类

集合类添加元素后,会对 对象具有引用,即使已经将对象置为null,也无法回收。

// 通过 循环申请Object 对象 & 将申请的对象逐个放入到集合ListList<Object> objectList = new ArrayList<>();               for (int i = 0; i < 10; i++) {            Object o = new Object();            objectList.add(o);            o = null;        }// 虽释放了集合元素引用的本身:o=null)// 但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象

解决方法:在不使用时,必须使用clear方法将所有对象删除,并将集合类对象置为null。

 // 释放objectList        objectList.clear();        objectList=null;

3.非静态内部类/匿名类

3.1创建非静态内部类的静态对象

非静态内部类默认持有外部类的引用,而静态内部类则不会。

而造成内存泄漏的原因是创建了一个非静态内部类的静态对象。则该对象的生命周期=应用的生命周期,因此会在外部类对象销毁的时候,仍然保留着对外部类的引用,则外部类的实例无法被GC。

public class TestActivity extends AppCompatActivity {         // 非静态内部类的实例的引用    // 注:设置为静态      public static InnerClass innerClass = null;        @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {                super.onCreate(savedInstanceState);           // 保证非静态内部类的实例只有1个        if (innerClass == null)            innerClass = new InnerClass();    }    // 非静态内部类的定义        private class InnerClass {                //...    }}

解决方法:将内部类改成静态内部类,这样就不会有外部类的引用

3.2 匿名类持有外部类的引用

以Thread为例,我们需要创建一个匿名内部类Thread并实现Runnable接口

工作线程正在处理任务且外部类需销毁时, 由于工作线程的实例持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄露。

public class MainActivity extends AppCompatActivity { @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ...        new Thread(new Runnable() {            @Override            public void run() {                try {                    Thread.sleep(1000);                }catch (InterruptedException e)                {                    e.printStackTrace();                }            }        }).start();        group.setVisibility(View.GONE);    }}

解决方法是可以使用静态内部类(Thread)

 protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        new MyThread().start();    } private static class MyThread extends Thread{        @Override        public void run() {            try {                Thread.sleep(1000);            }catch (InterruptedException e)            {                e.printStackTrace();            }        }    }

或者在外部类的回收时,强制关闭线程

 @Override    protected void onDestroy() {        super.onDestroy();        Thread.stop();        // 外部类Activity生命周期结束时,强制结束线程    }
3.3 Handler

在匿名内部类时,会默认持有外部的引用,也就是说Handler会持有外部类Activity的引用。

  • 当Handler消息队列 还有未处理的消息 / 正在处理消息时,存在引用关系: “未被处理 / 正处理的消息 -> Handler实例 -> 外部类”(Looper的生命周期与Application相同)

  • 若出现 Handler的生命周期 > 外部类的生命周期 时(即 Handler消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时),将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露。

解决方法:静态内部类+弱引用
静态内部类不持有外部类的引用,从而使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 的引用关系 不复存在。而弱引用不论内存是否够用,在GC时都会被回收

实例如下:

 // 分析1:自定义Handler子类    // 设置为:静态内部类    private static class FHandler extends Handler{        // 定义 弱引用实例        private WeakReference<Activity> reference;        // 在构造方法中传入需持有的Activity实例        public FHandler(Activity activity) {            // 使用WeakReference弱引用持有Activity实例            reference = new WeakReference<Activity>(activity); }        @Override        public void handleMessage(Message msg) {           ...            }        }    }

3.4 资源使用未关闭

未及时注销资源导致内存泄漏,如BraodcastReceiver、File、Cursor、Stream、Bitmap等。

解决办法:在Activity销毁的时候要及时关闭或者注销

BraodcastReceiver:调用unregisterReceiver()注销;
Cursor,Stream、File:调用close()关闭;
Bitmap:调用recycle()释放内存(2.3版本后无需手动)
属性动画:需要在onDestory中调用Animator.cancel方法来停止无限循环动画。

3.5 ListView的Adapter导致的内存泄漏

不使用缓存,而一直在getView中重新实例化Item。

解决方法:使用ContertView和ViewHolder,防止多次实例化Item与Item中的子控件。

4ListView优化

主要体现在getView的优化

  1. convertView参数用于将之前加载好的布局进行缓存
    • 当convertView为null的时候,首先加载子item布局,之后创建一个ViewHolder对象,将控件通过findById加载进来,并将控件实例存入ViewHolder对象中。然后调用convertView对象的setTag()方法,将ViewHolder对象存在View中
    • 当convertView不为null时,就调用convertView对象的getTag()方法,将ViewHolder对象重新取出。这样所有控件的实例都缓存在了这个ViewHolder中,就不用每次都用findViewById()方法来获取控件实例。
  2. 在Activity中为adapter设置listener,就不需要在每次加载getView时重新创建。
  3. 为控件设置监听器,同时要向控件传递位置(setTag(position)),以便触发了点击事件后,可以通过Integer) v.getTag()知道当前点击的是哪个子项。
   @Override    public View getView(int position, View convertView, ViewGroup parent){        ViewHolder viewHolder;        if (convertView==null) {            //convertView=View.inflate(mContext, R.layout.series_of_courses_item,null);            convertView= LayoutInflater.from(mContext).inflate(R.layout.series_of_courses_item,parent,false);            ViewHolder holder=new ViewHolder();            holder.course_title=convertView.findViewById(R.id.soc_course_title);            holder.course_time=convertView.findViewById(R.id.soc_course_time);            holder.enter_class=convertView.findViewById(R.id.enter_class);            holder.btn_son_course_delete = convertView.findViewById(R.id.btn_son_course_delete);            convertView.setTag(holder);        }        viewHolder=(ViewHolder)convertView.getTag();        final SeriesCourseModel seriesCourses=data.get(position);        //Log.d("SeriesCoursesAdapter", "getView: " + courses.getCourseName());        viewHolder.course_title.setText(seriesCourses.getName());        //int minutes= (int) LangUtils.parseLong(seriesCourses.getCourseTime(),0)/60;        viewHolder.course_time.setText(seriesCourses.getCourseTime());        //注册Item控件点击事件,首先在Activity中要设置监听器        if (onClickListener != null){            viewHolder.enter_class.setOnClickListener(onClickListener);            viewHolder.enter_class.setTag(position);            viewHolder.btn_son_course_delete.setOnClickListener(onClickListener);            viewHolder.btn_son_course_delete.setTag(position);        }        //type=1就显示那个button        if (type==1) {            viewHolder.enter_class.setVisibility(View.VISIBLE);        }        else viewHolder.enter_class.setVisibility(View.INVISIBLE);        return convertView;    }    class ViewHolder{        TextView course_title;        TextView course_time;        Button enter_class;        ImageButton btn_son_course_delete;    }     //向XListView添加数据项    public void addItems(SeriesCourseModel seriesCourseModel){        this.data.add(seriesCourseModel);        notifyDataSetChanged();    }    //向XListView删除数据项 注意添加了headView    public void deleteItems(int position){        this.data.remove(position);        notifyDataSetChanged();    }

5.BitMap优化

Bitmap的高效加载

  1. 将BitmapFactory.Options.inJustDecodeBounds参数设为true并加载图片。
  2. 从BitmapFactory.Options中取出图片的原始宽高信息decodeResource(res,resId,options),对应outWidth和outHeight参数。
  3. 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
  4. 将BitmapFactory.Options.inJustDecodeBounds参数设为false,然后重新加载图片decodeResource(res,resId,options)
  /**     * 对一个Resources的资源文件进行指定长宽来加载进内存, 并把这个bitmap对象返回     *     * @param res   资源文件对象     * @param resId 要操作的图片id     * @param reqWidth 最终想要得到bitmap的宽度     * @param reqHeight 最终想要得到bitmap的高度     * @return 返回采样之后的bitmap对象     */public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){        BitmapFactory.Options options = new BitmapFactory.Options();        //1.设置inJustDecodeBounds=true获取图片尺寸        options.inJustDecodeBounds = true;        BitmapFactory.decodeResource(res,resId,options);        //3.计算缩放比        options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);        //4.再设为false,重新从资源文件中加载图片        options.inJustDecodeBounds =false;        return BitmapFactory.decodeResource(res,resId,options);    }   /**     *  一个计算工具类的方法, 传入图片的属性对象和想要实现的目标宽高. 通过计算得到采样值     * @param options 要操作的原始图片属性     * @param reqWidth 最终想要得到bitmap的宽度     * @param reqHeight 最终想要得到bitmap的高度     * @return 返回采样率     */    private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {        //2.height、width为图片的原始宽高        int height = options.outHeight;        int width = options.outWidth;        int inSampleSize = 1;        if(height>reqHeight||width>reqWidth){            int halfHeight = height/2;            int halfWidth = width/2;            //计算缩放比,是2的指数            while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){                inSampleSize*=2;            }        }            return inSampleSize;    }

Bitmap的缓存策略

当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。

LruCache类是一个线程安全的泛型类:内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,并提供get和put方法来完成缓存的获取和添加操作,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。

常用属性accessOrder:决定LinkedHashMap的链表顺序。值为true:以访问顺序维护链表。值为false:以插入的顺序维护链表

而LruCache利用是accessOrder=true 时的LinkedHashMap实现LRU算法,使得最近访问的数据会在链表尾部,在容量溢出时,将链表头部的数据移除

使用方法
计算当前可用的内存大小;
分配LruCache缓存容量;
创建LruCache对象并传入最大缓存大小的参数、重写sizeOf()用于计算每个缓存对象的大小;
通过put()、get()和remove()实现数据的添加、获取和删除

 lruCache = new LruCache<String, Bitmap>(maxSize)        {            @Override            protected int sizeOf(String key, Bitmap value) {                // TODO Auto-generated method stub                return value.getWidth() * value.getHeight() / 1024;            }        };

6.线程优化

使用线程池,避免创建大量的Thread。Android线程与线程池《开发艺术》探索

7.响应速度优化

避免在主线程中做耗时操作。Activity规定如果在5s之内无法响应点击事件或键盘输入事件就会ANR,如果10s内BroadCastReciver还没有完成执行逻辑就会ANR。当发生ANR后可以通过traces文件定位。

8.其他建议

  1. 避免创建过多对象
  2. 不要过多使用枚举,枚举的占用的内存空间比整形大
  3. 常量要使用static final来修饰
  4. 使用一些Android自带的数据结构,比如Pair,他们具有更好的性能,Pair可以理解为一个带有xy坐标的Point类。List>list = new ArrayList<>();
  5. 适当使用软引用与弱引用(弱引用可以防止内存泄漏)。
  6. 采用内存缓存与磁盘缓存
  7. 尽量使用静态内部类,防止非静态内部类具有外部类的引用而导致内存泄漏

更多相关文章

  1. Android(安卓)组件系列之Activity的传值和回传值
  2. Android――Android(安卓)lint工具项目资源清理详解
  3. Android(安卓)Binder入门指南之Binder Native Service的Java调用
  4. Android写一个简单的欢迎界面
  5. 关于安卓开发中Activity动画切换效果无效的一个总结
  6. Android中解析JSON(一)
  7. Android(安卓)Volley 框架的使用(一)
  8. 关于如何检测Android的内存泄漏:
  9. android窗口管理剖析

随机推荐

  1. android 自动检测版本升级
  2. Android切近实战(八)
  3. Android分享到微信好友、朋友圈
  4. Android(安卓)创建自己的签名证书文件key
  5. Android(安卓)中的线程池
  6. 一些关于android屏幕的小知识
  7. Android按键监听与模拟分析
  8. Android中Toast显示时间的自定义
  9. 简谈WP,IOS,Android智能手机OS
  10. 在Android里如何判断一个指定的经纬度点