Android性能优化《Android开发艺术探索》笔记
文章目录
- 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.绘制优化
- 在onDraw中不要创建局部变量,因为onDraw会多次调用会占用内存,同时导致多次GC。
- 在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的优化:
- convertView参数用于将之前加载好的布局进行缓存。
- 当convertView为null的时候,首先加载子item布局,之后创建一个ViewHolder对象,将控件通过findById加载进来,并将控件实例存入ViewHolder对象中。然后调用convertView对象的setTag()方法,将ViewHolder对象存在View中。
- 当convertView不为null时,就调用convertView对象的getTag()方法,将ViewHolder对象重新取出。这样所有控件的实例都缓存在了这个ViewHolder中,就不用每次都用findViewById()方法来获取控件实例。
- 在Activity中为adapter设置listener,就不需要在每次加载getView时重新创建。
- 为控件设置监听器,同时要向控件传递位置(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的高效加载
- 将BitmapFactory.Options.inJustDecodeBounds参数设为true并加载图片。
- 从BitmapFactory.Options中取出图片的原始宽高信息
decodeResource(res,resId,options)
,对应outWidth和outHeight参数。 - 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
- 将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.其他建议
- 避免创建过多对象
- 不要过多使用枚举,枚举的占用的内存空间比整形大
- 常量要使用static final来修饰
- 使用一些Android自带的数据结构,比如Pair,他们具有更好的性能,Pair可以理解为一个带有xy坐标的Point类。
List
>list = new ArrayList<>(); - 适当使用软引用与弱引用(弱引用可以防止内存泄漏)。
- 采用内存缓存与磁盘缓存
- 尽量使用静态内部类,防止非静态内部类具有外部类的引用而导致内存泄漏。
更多相关文章
- Android(安卓)组件系列之Activity的传值和回传值
- Android――Android(安卓)lint工具项目资源清理详解
- Android(安卓)Binder入门指南之Binder Native Service的Java调用
- Android写一个简单的欢迎界面
- 关于安卓开发中Activity动画切换效果无效的一个总结
- Android中解析JSON(一)
- Android(安卓)Volley 框架的使用(一)
- 关于如何检测Android的内存泄漏:
- android窗口管理剖析