问:Android 中的线程有那些,原理与各自特点
答: - 有 AsyncTask、HandlerThread、IntentService
-
AsyncTask - 内部是Handler和两个线程池实现的,Handler用于将线程切换到主线程,两个线程池一个用于任务的排队,一个用于执行任务,当AsyncTask执行execute方法时会封装出一个FutureTask对象,将这个对象加入队列中,如果此时没有正在执行的任务,就执行它,执行完成之后继续执行队列中下一个任务,执行完成通过Handler将事件发送到主线程。AsyncTask必须在主线程初始化,因为内部的Handler是一个静态对象,在AsyncTask类加载的时候他就已经被初始化了。在Android3.0开始,execute方法串行执行任务的,一个一个来,3.0之前是并行执行的。如果要在3.0上执行并行任务,可以调用executeOnExecutor方法
-
HandlerThread - 继承自 Thread,start开启线程后,会在其run方法中会通过Looper 创建消息队列并开启消息循环,这个消息队列运行在子线程中,所以可以将HandlerThread 中的 Looper 实例传递给一个 Handler,从而保证这个 Handler 的 handleMessage 方法运行在子线程中,Android 中使用 HandlerThread的一个场景就是 IntentService
-
IntentService - 继承自Service,它的内部封装了 HandlerThread 和Handler,可以执行耗时任务,同时因为它是一个服务,优先级比普通线程高很多,所以更适合执行一些高优先级的后台任务。HandlerThread 底层通过Looper消息队列实现的,所以它是顺序的执行每一个任务。可以通过Intent的方式开启IntentService,IntentService通过handler将每一个intent加入HandlerThread子线程中的消息队列,通过looper按顺序一个个的取出并执行,执行完成后自动结束自己,不需要开发者手动关闭
问:如何实现进程保活
- Service 设置成 START_STICKY kill 后会被重启(等待5秒左右),重传 Intent,保持与重启前一样
- 通过 startForeground 将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill
- 双进程 Service,让2个进程互相保护对方,其中一个Service被清理后,另外没被清理的进程可以立即重启进程
- 用 C 编写守护进程(即子进程) : Android 系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响(Android5.0以上的版本不可行)联系厂商,加入白名单
问:如何避免OOM
- 使用更加轻量的数据结构:如使用 ArrayMap/SparseArray 替代 HashMap,HashMap 更耗内存,因为它需要额外的实例对象来记录 Mapping 操作,SparseArray 更加高效,因为它避免了 Key Value 的自动装箱,和装箱后的解箱操作
- 便面枚举的使用,可以用静态常量或者注解 @IntDef 替代
- 优化 Bitmap 策略:
- 尺寸压缩:通过 InSampleSize 设置合适的缩放
- 颜色质量:设置合适的 format,ARGB_6666/RBG_565/ARGB_4444/ALPHA_6,存在很大差异,这点谨慎,RBG_565 在 Glide 上会出现丢失透明通道,颜色变绿的问题
- inBitmap:使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小,但复用存在一些限制,具体体现在:在Android 4.4之前只能重用相同大小的Bitmap的内存,而Android 4.4及以后版本则只要后来的Bitmap比之前的小即可。使用inBitmap参数前,每创建一个Bitmap对象都会分配一块内存供其使用,而使用了inBitmap参数后,多个Bitmap可以复用一块内存,这样可以提高性能
- StringBuilder 替代 String,在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用 StringBuilder 来替代频繁的“+”
- 避免在类似 onDraw 这样的方法中创建对象,因为它会迅速占用大量内存,引起频繁的 GC 甚至内存抖动
- 减少内存泄漏
问:内存泄漏的场景和解决办法
- 非静态内部类的静态实例 - 非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类
- 多线程相关的匿名内部类和非静态内部类 - 匿名内部类同样会持有外部类的引用,如果在线程中执行耗时操作就有可能发生内存泄漏,导致外部类无法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务
- Handler内存泄漏 - Handler 导致的内存泄漏也可以被归纳为非静态内部类导致的,Handler 内部 message 是被存储在 MessageQueue 中的,有些 message 不能马上被处理,存在的时间会很长,导致 handler 无法被回收,如果 handler 是非静态的,就会导致它的外部类无法被回收,解决办法是1.使用静态 handler,外部类引用使用弱引用处理2.在退出页面时移除消息队列中的消息
- Context导致内存泄漏 - 根据场景确定使用 Activity 的 Context 还是 Application 的 Context,因为二者生命周期不同,对于不必须使用Activity的Context的场景(Dialog),一律采用 Application 的 Context,单例模式是最常见的发生此泄漏的场景,比如传入一个 Activity 的 Context 被静态类引用,导致无法回收
- 静态View导致泄漏 - 使用静态 View 可以避免每次启动 Activity 都去读取并渲染 View,但是静态 View会持有 Activity 的引用,导致无法回收,解决办法是在 Activity 销毁的时候将静态 View 设置为null(View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的 Activity,声明一个静态变量引用这个 View,也就引用了activity)
- WebView导致的内存泄漏 - WebView 只要使用一次,内存就不会被释放,所以 WebView 都存在内存泄漏的问题,通常的解决办法是为 WebView 单开一个进程,使用 AIDL 进行通信,根据业务需求在合适的时机释放掉
- 资源对象未关闭导致 - 如 Cursor,File 等,内部往往都使用了缓冲,会造成内存泄漏,一定要确保关闭它并将引用置为null
- 集合中的对象未清理 - 集合用于保存对象,如果集合越来越大,不进行合理的清理,尤其是入股集合是静态的
- Bitmap导致内存泄漏 - bitmap是比较占内存的,所以一定要在不使用的时候及时进行清理,避免静态变量持有大的 bitmap 对象
- 监听器未关闭 - 很多需要 register 和 unregister 的系统服务要在合适的时候进行 unregister,手动添加的 listener 也需要及时移除
网友评论