栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 产品运营 > 运营营销 > 网络营销

网络请求优化,安卓开发优化性能

网络营销 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

网络请求优化,安卓开发优化性能

在app开发中,图片是必不可少的。各种图标图片资源,如果不能处理好图片的使用。会导致app性能严重下降,影响用户体验。最直观的感受就是手机卡顿,发烫,有时候还会OOM,

所以今天我们就来分析一下oom和内存优化的总结;

Android性能优化(史上最全总结)的文章网络优化看得很明白。

1.OOM是什么?

OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError;当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理);在客户端App中这个现象通常出现在用到很多图片或者很大图片的APP开发中;通俗讲就是当我们的APP需要申请一块内存来装图片的时候,系统觉得我们的APP所使用的内存已经够多了,即使它有1G的空余内存,它不同意给我的APP更多的内存里,然后即使系统马上抛出OOM错误,而程序没有捕捉该错误,故弹框崩溃了;

第二,OOM的类型

1.JVM内存模型:

根据JVM规范,JAVA虚拟机将在运行时管理以下内存区域:

程序计数器:当前线程执行的字节码的行号指示器,线程私有;JAVA虚拟机栈:Java方法执行的内存模型,每个Java方法的执行对应着一个栈帧的进栈和出栈的操作;本地方法栈:类似“ JAVA虚拟机栈 ”,但是为native方法的运行提供内存环境;JAVA堆:对象内存分配的地方,内存垃圾回收的主要区域,所有线程共享。可分为新生代,老生代;方法区:用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot中的“永久代”;运行时常量池:方法区的一部分,存储常量信息,如各种字面量、符号引用等;直接内存:并不是JVM运行时数据区的一部分, 可直接访问的内存, 比如NIO会用到这部分;按照JVM规范,除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM;

2.最常见的OOM情况如下:

java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改;java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出;java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小;

第三,为什么OOM?

android app的每个进程或虚拟机都有一个最大内存限制。如果应用的内存资源超过这个限制,系统将抛出一个OOM错误。

和整个设备的剩余内存关系不大。比如早期的android系统,一个虚拟机最大内存16M。当一个app启动时,虚拟机不断申请内存资源来加载图片。超过内存限制时,会出现OOM。

为什么没有记忆?有两个原因:

1.分配少:比如虚拟机本身的可用内存(一般在启动时由VM参数指定)太少;

2.应用用的太多,用完浪费了也不释放,会造成内存泄漏或者内存溢出;

内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用;内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出;

在没有自动垃圾回收的日子里,比如C语言和C++语言,我们要负责内存的申请和释放。如果我们申请了内存,但在使用后忘记释放它,比如C++中的new但没有delete,这可能会导致内存泄漏。偶尔的内存泄漏可能不会导致问题,而大量的内存泄漏可能会导致内存溢出;

但在Java中,由于有自动垃圾回收机制,我们一般不用主动释放未使用对象占用的内存,也就是理论上不会出现“内存泄漏”。但是如果编码不当,比如把一个对象的引用放到全局映射中,虽然方法完成了,但是垃圾收集器会根据对象的引用来回收内存,这样就不能及时回收对象。如果多次出现这种情况,就会导致内存溢出,比如系统中经常使用的缓存机制。Java中的内存泄漏,不同于C++中的忘记删除,往往是因为逻辑原因。

第四,如何避免OOM,优化内存

1.减少对象的内存占用。

避免OOM的第一步是最小化新分配对象的内存大小,尽量使用更轻的对象。

1)使用更简单的数据结构

我们可以考虑用ArrayMap/SparseArray代替HashMap等传统的数据结构。

相对于Android专门为移动操作系统编写的ArrayMap容器,HashMap的简要工作原理在大多数情况下效率较低,占用内存较多。

通常,HashMap的实现会消耗更多的内存,因为它需要一个额外的实例对象来记录映射操作。

另外,SparseArray的效率更高,因为它们避免了键和值的自动装箱,避免了装箱后的解包。

2)避免在Android中使用Enum

枚举通常需要两倍于静态常量的内存。你应该严格避免在Android上使用枚举。所以请避免在Android中使用枚举。

3)减少位图对象的内存占用

位图是个容易消耗内存的胖子。减少创建位图的内存占用是最重要的。一般来说,有以下两种措施。

InSampleSize:缩放,在将图片加载到内存之前,我们需要计算一个合适的缩放比例,以避免不必要的大图片加载。

Decodiformat:解码格式,选择argb _ 8888/rbg _ 565/argb _ 4444/alpha _ 8,差别较大。

4)使用较小的图片

说到给资源图片,需要特别注意这个图片中是否有可压缩空的房间,是否可以使用更小的图片。尽可能使用较小的图像,既可以减少内存占用,又可以避免大量的InflationException。假设有一个XML文件直接引用的大图,初始化视图时很可能会因为内存不足而出现InflationException。这个问题的根源其实是OOM。

2.内存对象的重用。

大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码时显式地在程序里创建对象池,然后处理好复用的实现逻辑。要么就是利用系统框架既有的某些复用特性,减少对象的重复创建,从而降低内存的分配与回收;复用系统自带的资源:Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去;注意在ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用;Bitmap对象的复用;避免在onDraw方法里面执行对象的创建;类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动;StringBuilder:在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”;

3.避免对象的内存泄漏。

内存的泄露会导致一些没有使用的对象没有被及时释放,一方面占用了宝贵的内存空,在后期需要分配内存的时候容易因为空空余时间空的不足而导致OOM。显然,这也使得每一级Generation 空的可用内存面积变小,因此GC会更容易被触发,容易出现内存抖动,从而引发性能问题。

1)注意活动的泄漏。

通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,我们需要特别注意以下两种情况导致的Activity泄漏:内部类引用导致Activity的泄漏:最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏;内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露;

2)考虑使用应用程序上下文而不是活动上下文

对于大多数不需要使用活动上下文的情况(对话框的上下文必须是活动上下文),我们可以考虑使用应用上下文而不是活动上下文,这样可以避免无意中的活动泄露;

3)注意及时恢复临时位图对象。

虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。需要特别留意的是Bitmap类里面提供的createBitmap()方法:这个函数返回的bitmap有可能和source bitmap是同一个,在回收的时候,需要特别检查source bitmap与return bitmap的引用是否相同,只有在不等的情况下,才能够执行source bitmap的recycle方法。

4)注意监听器的注销。

Android程序中有很多需要注册和注销的监听器。我们需要确保这些侦听器在适当的时候及时取消注册。手动添加监听器,需要记得及时移除监听器。

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

有时候,为了提高对象的可重用性,我们把一些对象放在缓存容器中,但是如果这些对象没有及时从容器中清除,也可能导致内存泄漏。比如2.3系统,如果在缓存容器中加入drawable,因为drawable和View的强应用性,很容易造成活动泄露。从4.0开始,就没有这个问题了。为了解决这个问题,我们需要为2.3系统上的缓存drawable做一个专门的包,处理引用解绑定的问题,避免泄露。

6)注意WebView的泄露。

WebView在Android中存在很大的兼容性问题,不仅是Android系统版本的差异,不同厂商出货的rom中WebView也存在差异。更严重的是,标准的WebView有内存泄露的问题。请看这里。因此,解决这个问题的方法是为WebView启动另一个进程,并通过AIDL与主进程通信。WebView所在的进程可以根据业务需要选择合适的时间销毁,从而实现内存的完全释放。

7)注意光标对象是否及时关闭

在程序中,我们经常查询数据库,但有时在不小心使用了光标后没有及时关闭。这些游标的泄漏,如果重复的话,会对内存管理产生很大的负面影响。我们需要记得及时关闭光标对象。

4.内存使用策略优化。

1)小心使用大堆。

正如前面提到的,Android设备根据硬件与软件的设置差异而存在不同大小的内存空间,他们为应用程序设置了不同大小的Heap限制阈值。你可以通过调用getMemoryClass()来获取应用的可用Heap大小。在一些特殊的情景下,你可以通过在manifest的application标签下添加largeHeap=true的属性来为应用声明一个更大的heap空间。然后,你可以通过getLargeMemoryClass()来获取到这个更大的heap size阈值。然而,声明得到更大Heap阈值的本意是为了一小部分会消耗大量RAM的应用(例如一个大图片的编辑应用)。不要轻易的因为你需要使用更多的内存而去请求一个大的Heap Size。只有当你清楚的知道哪里会使用大量的内存并且知道为什么这些内存必须被保留时才去使用large heap。因此请谨慎使用large heap属性。使用额外的内存空间会影响系统整体的用户体验,并且会使得每次gc的运行时间更长。在任务切换时,系统的性能会大打折扣。另外, large heap并不一定能够获取到更大的heap。在某些有严格限制的机器上,large heap的大小和通常的heap size是一样的。因此即使你申请了large heap,你还是应该通过执行getMemoryClass()来检查实际获取到的heap大小。

2)综合考虑设备内存阈值等因素,设计合适的缓存大小。

在设计ListView或GridView的位图LRU缓存时,需要考虑以下几点:

应用程序剩下了多少可用的内存空间?有多少图片会被一次呈现到屏幕上?有多少图片需要事先缓存好以便快速滑动时能够立即显示到屏幕?设备的屏幕大小与密度是多少? 一个xhdpi的设备会比hdpi需要一个更大的Cache来hold住同样数量的图片。不同的页面针对Bitmap的设计的尺寸与配置是什么,大概会花费多少内存?页面图片被访问的频率?是否存在其中的一部分比其他的图片具有更高的访问频繁?如果是,也许你想要保存那些最常访问的到内存中,或者为不同组别的位图(按访问频率分组)设置多个LruCache容器。

3)onLowMemory()和onTrimMemory()

Android用户可以随意在不同应用之间快速切换。为了让后台的应用快速切换到前台,后台的每个应用都会占用一定的内存。Android系统会根据当前系统的内存使用情况决定回收后台的部分应用内存。如果后台的应用直接从暂停状态恢复到forground,可以获得更快的恢复体验,而如果后台的应用从Kill状态恢复,则稍慢。

onLowMemory():Android系统提供了一些回调来通知当前应用的内存使用情况,通常来说,当所有的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。onTrimMemory(int):Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递以下的参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用。TRIM_MEMORY_UI_HIDDEN:你的应用程序的所有UI界面被隐藏了,即用户点击了Home键或者Back键退出应用,导致应用的UI界面完全不可见。这个时候应该释放一些不可见的时候非必须的资源

当程序在前台运行时,它可能会接收到从onTrimMemory()返回的下列值之一:

TRIM_MEMORY_RUNNING_MODERATE:你的应用正在运行并且不会被列为可杀死的。但是设备此时正运行于低内存状态下,系统开始触发杀死LRU Cache中的Process的机制。TRIM_MEMORY_RUNNING_LOW:你的应用正在运行且没有被列为可杀死的。但是设备正运行于更低内存的状态下,你应该释放不用的资源用来提升系统性能。TRIM_MEMORY_RUNNING_CRITICAL:你的应用仍在运行,但是系统已经把LRU Cache中的大多数进程都已经杀死,因此你应该立即释放所有非必须的资源。如果系统不能回收到足够的RAM数量,系统将会清除所有的LRU缓存中的进程,并且开始杀死那些之前被认为不应该杀死的进程,例如那个包含了一个运行态Service的进程。

当应用程序进程退回到后台并被缓存时,它可能会收到从Ontario()返回的下列值之一:

TRIM_MEMORY_BACKGROUND: 系统正运行于低内存状态并且你的进程正处于LRU缓存名单中最不容易杀掉的位置。尽管你的应用进程并不是处于被杀掉的高危险状态,系统可能已经开始杀掉LRU缓存中的其他进程了。你应该释放那些容易恢复的资源,以便于你的进程可以保留下来,这样当用户回退到你的应用的时候才能够迅速恢复。TRIM_MEMORY_MODERATE: 系统正运行于低内存状态并且你的进程已经已经接近LRU名单的中部位置。如果系统开始变得更加内存紧张,你的进程是有可能被杀死的。TRIM_MEMORY_COMPLETE: 系统正运行于低内存的状态并且你的进程正处于LRU名单中最容易被杀掉的位置。你应该释放任何不影响你的应用恢复状态的资源。

因为onTrimMemory()的回调只是在API 14中添加的,所以对于老版本,为了兼容,可以使用onLowMemory()回调。onLowMemory相当于TRIM_MEMORY_COMPLETE。

请注意:当系统开始清除LRU缓存中的进程时,虽然首先按照LRU的顺序执行操作,但也会考虑进程的内存使用情况等因素。这个过程占用的时间越少,就越容易被落下。

4)资源文件需要存储在适当的文件夹中。

我们知道hdpi/xhdpi/xxhdpi等不同dpi文件夹下的图片会在不同设备上进行缩放处理。比如我们在hdpi的目录里只放了一张100,100的图片,那么根据转换关系,xxhdpi的手机在引用那张图片的时候会拉伸到200,200。需要注意的是,在这种情况下,内存使用量会显著增加。不想拉伸的图片需要放在assets或者nodpi目录下。

5)尝试捕获一些大内存分配操作

在某些情况下,我们需要提前评估可能发生的代码。对于这些可能发生OOM的代码,加入catch机制,考虑在catch中尝试降级的内存分配操作。比如解码位图的时候,catch到OOM,可以尝试加倍采样率,重新尝试解码。

6)谨慎使用静态对象。

由于static的生命周期太长,且与应用程序进程一致,使用不当可能导致对象泄露,所以在Android中要谨慎使用static对象。

7)特别注意单个对象中的不合理持有。

虽然singleton模式简单实用,提供了很多便利,但是由于单个案例的生命周期与应用是一致的,如果使用不当,很容易泄露持有的对象。

8)珍惜服务资源

如果您的应用程序需要在后台使用服务,除非它被触发并执行任务,否则该服务应在其他时间停止。另外,你要注意服务完成任务后停止服务失败导致的内存泄漏。当你启动一个服务时,系统会倾向于保持服务的进程,以便保持服务。这使得进程的运行成本很高,因为系统无法将服务占用的RAM空释放给其他组件,服务也无法进行页面调出。这就减少了系统在LRU缓存中可以存储的进程数量,会影响应用之间的切换效率,甚至导致系统内存使用不稳定,从而无法保留当前运行的所有服务。建议使用IntentService,它会在处理完分配给它的任务后尽快自行结束。有关更多信息,请阅读在后台服务中运行。

9)优化布局层次,降低内存消耗。

视图布局越扁平,占用内存越少,效率越高。我们需要确保布局足够扁平,当系统提供的视图不能足够扁平时,可以考虑使用自定义视图来达到目的。

10)谨慎使用“抽象”编程。

很多时候,开发人员会将抽象类作为“良好的编程实践”,因为抽象可以提高代码的灵活性和可维护性。然而,抽象会导致大量额外的内存开销:它们需要相同数量的可执行代码,这些代码将被映射到内存中,所以如果您的抽象没有显著提高效率,您应该尽量避免它们。

1)用nano protobufs序列化数据

协议缓冲区是由Google设计的,用于序列化结构化数据。它与语言和平台无关,具有良好的可扩展性。类似于XML,但是比XML更轻、更快、更简单。如果需要对数据进行序列化和协议化,建议使用nano protobufs。更多详情请参考protobuf readme的“Nano版”章节。

12)谨慎使用依赖注入框架。

那些注入框架会通过扫描你的代码来执行很多初始化操作,这会导致你的代码需要大量的内存空来映射代码,映射页面会在内存中保存很长时间。除非真的有必要,否则建议慎用该技术;

13)谨慎使用多进程。

使用多进程可以把应用中的部分组件运行在单独的进程当中,这样可以扩大应用的内存占用范围,但是这个技术必须谨慎使用,绝大多数应用都不应该贸然使用多进程,一方面是因为使用多进程会使得代码逻辑更加复杂,另外如果使用不当,它可能反而会导致显著增加内存。当你的应用需要运行一个常驻后台的任务,而且这个任务并不轻量,可以考虑使用这个技术;一个典型的例子是创建一个可以长时间后台播放的Music Player。如果整个应用都运行在一个进程中,当后台播放的时候,前台的那些UI资源也没有办法得到释放。类似这样的应用可以切分成2个进程:一个用来操作UI,另外一个给后台的Service。

14)使用ProGuard删除不必要的代码。

ProGuard可以通过删除不必要的代码、重命名类、字段和方法等方式对代码进行压缩、优化和混淆。使用ProGuard可以让你的代码更加紧凑,可以减少映射代码所需的内存空。

15)谨慎使用第三方库。

许多开源库代码不是为移动网络环境编写的,因此它们可能不适合移动设备。即使是为Android设计的库也需要特别谨慎,尤其是当你不知道引入的库做了什么的时候。例如,其中一个库使用nano protobufs,而另一个库使用micro protobufs。这样,有两种方法可以在应用程序中实现protobuf。类似的冲突也可能发生在输出日志、加载图片、缓存等模块中。另外,不要为了一两个函数而导入整个库。如果没有合适的符合你需求的库,你应该考虑自己实现,而不是导入一个庞大完整的解决方案。

摘要

内存优化并不就是说程序占用的内存越少就越好,如果因为想要保持更低的内存占用,而频繁触发执行gc操作,在某种程度上反而会导致应用性能整体有所下降,这里需要综合考虑做一定的权衡。Android的内存优化涉及的知识面还有很多:内存管理的细节,垃圾回收的工作原理,如何查找内存泄漏等等都可以展开讲很多。OOM是内存优化当中比较突出的一点,尽量减少OOM的概率对内存优化有着很大的意义
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/819718.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号