Android知识笔记:如果你深受其害,不妨来看看这篇内存优化,2024大厂Android知识点总结+面试题解析

| ALPHA_8 | Alpha 由 8 位组成 | 1B |

| ARGB_4444 | 4 个 4 位组成 16 位,每个色彩元素站 4 位 | 2B |

| ARGB_8888 | 4 个 8 为组成 32 位,每个色彩元素站 8 位(默认) | 4B |

| RGB_565 | R 为 5 位,G 为 6 位,B 为 5 位共 16 位,没有Alpha | 2B |

使用更小的图片

在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。

尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的 InflationException。假设有一张很大的图片被 XML 文件直接引用,很有可能在初始化视图的时候就会因为内存不足而发生 InflationException,这个问题的根本原因其实是发生了 OOM。

  • JPG vs PNG vs WebP

不了解这三种图片格式的建议看下 JPG 和 PNG 有什么区别?、WebP 原理和 Android 支持现状介绍 这两篇文章。

  • 图片压缩

图片压缩相关知识推荐看下腾讯音乐技术团队的 Android 中图片压缩分析(上)、 Android 中图片压缩分析(下) 两篇文章。

了解了图片压缩的相关知识,我们可以自己写算法来实现图片压缩,也可以使用优秀的开源库,比如:鲁班。

内存对象的重复利用

除了减小对象对内存的占用,合理的复用内存对象也是很好避免内存溢出的方式。

大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码的时候显式的在程序里面去创建对象池,然后处理好复用的实现逻辑,要么就是利用系统框架既有的某些复用特性达到减少对象的重复创建,从而减少内存的分配与回收。

LruCache

在 Android 中最常用的一个缓存算法是 LRU(Least Recently Use),就是当超出缓存容量的时候,就优先淘汰链表中最近最少使用的那个数据。

LruCache bitmapCache = new LruCache<String, Bitmap>();

// 根据内存空间设置缓存大小

ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);

int availMemInBytes = am.getMemoryClass() * 1024 *1024;

LruCache bitmapCache = new LruCache<String, Bitmap>(availMemInBytes/8);

使用 LruCache 可以缓存 Bitmap 对象,相同LruCache 只是对内存中对象有效,如果我们想把图片、视频等文件缓存在磁盘上可以使用 JakeWharton 大神开源的 DiskLruCache。

使用 Glide

Glide 是一个快速高效的 Android 图片加载库,注重于平滑的滚动。Glide 提供了易用的 API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。

Glide 也是 Google 推荐过的开源项目,详见:Glide。

复用系统自带的资源

Android 系统本身内置了很多的资源(例如:字符串、颜色、图片、动画、样式以、简单布局等等),这些资源都可以在应用程序中直接引用。

这样做不仅仅可以减少应用程序的自身负重,减小 APK 的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意 Android 系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去。

复用 ConvertView

在 ListView、GridView 等出现大量重复子组件的视图里面对 ConvertView 的复用。

onLowMemory()

OnLowMemory 是 Android 提供的API,在系统内存不足,所有后台程序(优先级为 Background 的进程,不是指后台运行的进程)都被杀死时,系统会调用 OnLowMemory。

系统提供的回调有:Application、Activity、Fragementice、Service、ContentProvider。

onTrimMemory()

OnTrimMemory 是 Android 4.0 之后提供的 API,系统会根据不同的内存状态来回调。

系统提供的回调有:Application、Activity、Fragementice、Service、ContentProvider。

OnTrimMemory的参数是一个 int 数值,代表不同的内存状态。

当 App 在前台运行时,该函数的 level (从低到高)有:

  • TRIM_MEMORY_RUNNING_MODERATE

系统开始运行在低内存状态下 App 正在运行,不会被杀掉。

  • TRIM_MEMORY_RUNNING_LOW

系统运行在更加低内存状态下,App 在运行,不会被杀掉 App 可以清理一些资源来保证系统的流畅。

  • TRIM_MEMORY_RUNNING_CRITICAL

系统运行在相当低内存状态下,App 在运行,且系统不认为可以杀掉此 App,系统要开始杀掉后台进程。此时,App 应该去释放一些不重要的资源。

当 App 在后台运行时,level 状态有:

  • TRIM_MEMORY_UI_HIDDEN:

App 的 UI 不可见,App 可以清理 UI 使用的较大的资源。

当 App 进入后台 LRU List 时:

  • TRIM_MEMORY_BACKGROUND

系统运行在低内存下,App 进程在 LRU List 开始处附近,尽管 App 没有被杀掉的风险,但是系统也许已经正在杀后台进程。App 应该清理一些容易恢复的资源。

  • TRIM_MEMORY_MODERATE

系统运行在低内存下,App 进程在 LRU List 中间处附件,App 此时有被杀的可能。

  • TRIM_MEMORY_COMPLETE

系统运行在低内存下,App 是首先被杀的选择之一,App 应该及时清理掉恢复 App 到前台状态,不重要的所有资源。

另外,一个 App 占用内存越多,则系统清理后台 LRU List 时,越可能优先被清理。所以,内存使用我们要谨慎使用。

避免在 onDraw 方法里面执行对象的创建

类似 onDraw 等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的 GC,甚至是内存抖动。

序列化

在 Android 中实现序列化一般用 Serializable 和 Parcelable 两种方式。

两者最大的区别在于 存储媒介的不同,Serializable 使用 I/O 读写存储在硬盘上,而 Parcelable 是直接 在内存中读写。很明显,内存的读写速度通常大于 IO 读写,所以在 Android 中传递数据优先选择 Parcelable。Serializable 会使用反射,序列化和反序列化过程需要大量 I/O 操作, Parcelable 自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在 Native 内存中,效率要快很多。

Parcelable 的性能比 Serializable 好,在内存开销方面较小,所以在内存间数据传输时推荐使用 Parcelable(如 Activity 间传输数据)。

而 Serializable 可将数据持久化方便保存,所以在需要保存或网络传输数据时选择 Serializable,因为 Android 不同版本 Parcelable 可能不同,所以不推荐使用 Parcelable进行数据持久化.

StringBuilder

在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用 StringBuilder 来替代频繁的 +

避免内存泄漏


内存泄漏(Memory Leak)指程序运行过程中分配内存给临时变量,用完之后却没有被 GC 回收,始终占用着内存,既不能被使用也不能分配给其他程序,于是就发生了内存泄漏。

内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,甚至会提示你 OOM。

Context 的泄露

在 Android 开发中,最容易引发的内存泄漏问题的是 Context。比如 Activity 的 Context,就包含大量的内存引用,一旦泄漏了 Context,也意味泄漏它指向的所有对象。

造成 Activity 泄漏的常见原因:

静态引用 Activity

在类中定义了静态 Activity 变量,把当前运行的 Activity 实例赋值于这个静态变量。如果这个静态变量在 Activity 生命周期结束后没有清空,就导致内存泄漏。

因为 static 变量是贯穿这个应用的生命周期的,所以被泄漏的 Activity 就会一直存在于应用的进程中,不会被垃圾回收器回收。

static Activity activity; // 这种代码要避免

单例中保存 Activity

在单例模式中,如果 Activity 经常被用到,那么在内存中保存一个 Activity 实例是很实用的。

但是由于单例的生命周期是应用程序的生命周期,这样会强制延长 Activity 的生命周期,这是相当危险而且不必要的,无论如何都不能在单例子中保存类似 Activity 的对象。

public class Singleton {

private static Singleton instance;

private Context mContext;

private Singleton(Context context){

this.mContext = context;

}

public static Singleton getInstance(Context context){

if (instance == null){

synchronized (Singleton.class){

if (instance == null){

instance = new Singleton(context);

}

}

}

return instance;

}

}

在调用 Singleton 的 getInstance() 方法时传入了 Activity。那么当 instance 没有释放时,这个 Activity 会一直存在,因此造成内存泄露。

考虑使用 Application Context 而不是 Activity Context

对于大部分非必须使用 Activity Context 的情况(Dialog 的 Context 就必须是 Activity Context),我们都可以考虑使用 Application Context 而不是 Activity 的 Context,这样可以避免不经意的 Activity 泄露。

Inner Classes

内部类的优势可以提高可读性和封装性,而且可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用(例如在内部类中持有 Activity 对象)。

解决方法:

  • 将内部类变成静态内部类;

  • 如果有强引用 Activity 中的属性,则将该属性的引用方式改为弱引用;

  • 在业务允许的情况下,当 Activity 执行 onDestory 时,结束这些耗时任务。

避免使用异步回调

异步回调被执行的时间不确定,很有可能发生在 Activity 已经被销毁之后,这不仅仅很容易引起 crash,还很容易发生内存泄露。

注意临时 Bitmap 对象的及时回收

虽然在大多数情况下,我们会对 Bitmap 增加缓存机制,但是在某些时候,部分 Bitmap 是需要及时回收的。

例如:临时创建的某个相对比较大的 Bitmap 对象,在经过变换得到新的 Bitmap 对象之后,应该尽快回收原始的 Bitmap,这样能够更快释放原始 Bitmap 所占用的空间。

需要特别留意的是 Bitmap 类里面提供的 createBitmap() 方法,这个函数返回的 Bitmap 有可能和 source bitmap 是同一个,在回收的时候,需要特别检查 source bitmap 与 return bitmap 的引用是否相同,只有在不等的情况下,才能够执行 source bitmap 的 recycle 方法。

注意监听器的注销

在 Android 程序里面存在很多需要 register 与 unregister 的监听器,我们需要确保在合适的时候及时 unregister 那些监听器。自己手动 add 的 listener,需要记得及时 remove 这个 listener。

注意 Cursor 对象是否及时关闭

在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用 Cursor 之后没有及时关闭的情况。这些 Cursor 的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对 Cursor 对象的及时关闭。

注意缓存容器中的对象泄漏

有时候,我们为了提高对象的复用性把某些对象放到缓存容器中,可是如果这些对象没有及时从容器中清除,也是有可能导致内存泄漏的。

例如:针对 2.3 的系统,如果把 drawable 添加到缓存容器,因为 drawable 与 View 的强应用,很容易导致 activity 发生泄漏。而从 4.0 开始,就不存在这个问题。解决这个问题,需要对 2.3 系统上的缓存 drawable 做特殊封装,处理引用解绑的问题,避免泄漏的情况。

注意 WebView 的泄漏

Android 中的 WebView 存在很大的兼容性问题,不仅仅是 Android 系统版本的不同对 WebView 产生很大的差异,另外不同的厂商出货的 ROM 里面 WebView 也存在着很大的差异。更严重的是标准的 WebView 存在内存泄露的问题,看这里 WebView causes memory leak - leaks the parent Activity。

所以通常根治这个问题的办法是为 WebView 开启另外一个进程,通过 AIDL 与主进程进行通信, WebView 所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

Lint Tool


Lint 是Android Studio 提供的代码扫描分析工具,它可以帮助我们发现代码结构/质量问题,同时提供一些解决方案,而且这个过程不需要我们手写测试用例。

Lint 发现的每个问题都有描述信息和等级(和测试发现 bug 很相似),我们可以很方便地定位问题,同时按照严重程度进行解决。

点击 Analyze > Inspect Code 可打开 Lint 工具。

详细使用介绍可查看 Android 开发文档 - 使用 Lint 改进您的代码、Android 性能优化:使用 Lint 优化代码、去除多余资源 这两篇文章,使用比较简单,网上介绍资源很多,这里不再详细介绍。

adb dumpsys


dumpsys 是 Android 系统提供的一个工具,可以查看系统服务的相关信息,dumpsys 通过 adb 命令来调用。

查看指定进程包名的内存使用情况:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。

image

image

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。

[外链图片转存中…(img-7O8K8fEA-1712434940097)]

[外链图片转存中…(img-98NxSJOb-1712434940097)]

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值