Skip to content

Latest commit

 

History

History
105 lines (71 loc) · 9.2 KB

2016-04-11.md

File metadata and controls

105 lines (71 loc) · 9.2 KB

#2016-04-11

安卓性能优化总结

内存泄露

了解GC机制,理解四种引用在GC机制下的表现和发挥的作用
长期存在的对象引用了短期存在的对象,使得短期存在的对象无法销毁,比如单例模式持有对某短期存在对象的引用
长期存在的对象引用了短期存在的对象,使得短期存在的对象无法销毁,比如静态内部类对外部类的引用

关于静态内部类,可以看这篇文章对静态内部类的解释,注意最后一点:

在java提高篇-----关键字static中提到Static可以修饰成员变量、方法、代码块,其他它还可以修饰内部类,使用static修饰的内部类我们称之为静态内> 部类,不过我们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着>> 一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。

也就是说,静态内部类本身就有用于避免内存的特质,因为它不会对外部类保存引用。比如最常见的是Handler类的编写不规范,具体可以参见这篇文章,注意其中说到:

这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。

创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息。

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。

注意这里的这种情况又扯到了Android中的消息机制,Looper和消息队列的概念,而这些又可以扯到ThreadLocal和线程安全。

但是如果我们再静态内部类中引用了外部的对象,这个时候静态内部类所具有的不引用外部类的特质就被人为破坏了,所以这个时候我们就需要用弱引用来持有这个对外部类的引用。

长期存在的对象引用了短期存在的对象,使得短期存在的对象无法销毁,比如生命周期比Activity长的线程持有对Activity的引用

注意手机屏幕旋转的时候的处理。

如果你的任务需要更新 UI,那么考虑用 AsyncTask 吧,AsyncTask 虽然相对容易,但是有些坑得留意。当你旋转手机的时候,Activity 会被关闭,然后重启。不然可能造成内存泄露。

长期存在的对象引用了短期存在的对象,使得短期存在的对象无法销毁,比如无线循环的属性动画没有在Activity的onDestroy中停止
资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

一般怎么知道内存泄露?
  1. MAT分析内存状况,重点分析重复的对象、bitmap对象,分析引用路径。
  2. LeakCanary

加载大图

Loading Large Bitmaps Efficiently

  1. 先得到图的长宽
  2. 计算最小应加载多大,计算采样率
  3. 加载

不要在主线程中解析JSON数据,可以用某些比较快的JSON解析库

比如ig-json-parserjackson

不要在主线程中读数据库

判断当前线程是否为主线程Looper.myLooper() == Looper.getMainLooper()

不要通过反射来调用私有 API

良好的代码规范:私有成员用m,静态成员用s,常量全大写

布局优化

  1. 布局层级能少就少
  2. 在同样布局层级的前提下,用简单的ViewGroup,比如FrameLayout和LinearLayout
  3. 标签可以帮助我们复用布局
  4. 在用标签复用布局的时候,可以用标签减少布局层级
  5. 用ViewStub来缓加载布局

绘制优化

  1. onDraw中不要创建对象,onDraw有可能频繁调用,这样会产生大量无用对象
  2. onDraw中不要进行耗时操作,影响帧率

通过traces文件分析ANR原因

使用合理的线程、线程池

  1. 使用IntentService,这丫不是服务,所以不会阻塞主线程,它会开启worker线程去干活,既可以不占用主线程,又可以有服务级别的高优先级,可以参见这篇文章。IntentService用了HandlerThread。
  2. HandlerThread继承了Thread,是可以使用Handler的Thread。普通Thread主要用于执行耗时任务,而HandlerThread用于外界通过Handler的方式来执行具体的耗时任务。注意HandlerThread的run方法是死循环,不需要HandlerThread的时候,应该将其quitquitSafely
  3. 使用AsyncTask,注意:1. 底层是用线程池实现;2. 最好用来进行快速完成的任务,而不是耗时很多的任务;3. 其中有两个线程池,一个用于任务排队,一个用于执行任务;4. AsyncTask要在主线程里初始化,因为这样才能通过Handler在后续的操作中切换到主进程;5. 在Android3.0版本以前,AsyncTask默认并发,其后,默认串行。如果要在Android3.0及之后并发,可以用executeOnExecutor方法,注意这个方法只在Android3.0及以上有。
  4. 线程池工作原理:1. 如果线程池中的线程数量没有达到核心线程数量,直接启动核心线程来进行任务;2. 如果线程池中的线程数量已经达到了核心线程数量,那么任务会被插入到任务队列中等待;3. 如果任务队列满了,且当前线程数量没有达到最大线程数量,那么开启非核心进程;4. 如果线程数量达到了线程数量最大值,用RejectedExecutionHandler#rejectedExecution方法通知调用者:我不干了。
  5. 线程池分类:1. FixedThreadPool,这种线程池只有核心线程,也就是说,其内部的线程是不会被回收的,所以它可以用来快速响应;2. CachedThreadPool,只有非核心线程,最大线程数可以认为是无穷,适合执行大量、耗时少的任务;3. ScheduledThreadPool,用于执行定时、具有固定周期的重复任务;4. SingleThreadExecutor,只有一个核心进程,用于保证线程能够排队执行。

尽量别用枚举

常量都用static final修饰

使用Android中特有的数据结构,比如SparseArray和Pair

Bitmap三级缓存

  1. LruCache
  2. DiskLruCache
  3. 四大图片加载库

ListView优化

  1. ListView原理
  2. 用ViewHolder优化getView的执行效率(RecycleView和ListView的区别?)
  3. 列表快速滑动时可以不用加载
  4. 开启硬件加速

Activity, AppCompatActivity, ActionBarActivity and FragmentActivity

Article 1

Article 2

CAS

CAS

比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。 该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。

在使用上,通常会记录下某块内存中的旧值,通过对旧值进行一系列的操作后得到新值,然后通过CAS操作将新值与旧值进行交换。如果这块内存的值在这期间内没被修改过,则旧值会与内存中的数据相同,这时CAS操作将会成功执行 使内存中的数据变为新值。如果内存中的值在这期间内被修改过,则一般[2]来说旧值会与内存中的数据不同,这时CAS操作将会失败,新值将不会被写入内存。