Android性能优化相关面试题

Android性能优化相关面试题

1、要做一个尽可能流畅的ListView,你平时在工作中如何进行优化的?

①Item布局,层级越少越好,使用hierarchyview工具查看优化。

②复用convertView

③使用ViewHolder

④item中有图片时,异步加载

⑤快速滑动时,不加载图片

⑥item中有图片时,应对图片进行适当压缩

⑦实现数据的分页加载

2、如何缩减APK包大小?

  • 代码

保持良好的编程习惯,不要重复或者不用的代码,谨慎添加libs,移除使用不到的libs。

使用proguard混淆代码,它会对不用的代码做优化,并且混淆后也能够减少安装包的大小。

native code的部分,大多数情况下只需要支持armabi与x86的架构即可。如果非必须,可以考虑拿掉x86的部分。

  • 资源

使用Lint工具查找没有使用到的资源。去除不使用的图片,String,XML等等。 assets目录下的资源请确保没有用不上的文件。

生成APK的时候,aapt工具本身会对png做优化,但是在此之前还可以使用其他工具如tinypng对图片进行进一步的压缩预处理。

jpeg还是png,根据需要做选择,在某些时候jpeg可以减少图片的体积。 对于9.png的图片,可拉伸区域尽量切小,另外可以通过使用9.png拉伸达到大图效果的时候尽量不要使用整张大图。

  • 策略

有选择性的提供hdpi,xhdpi,xxhdpi的图片资源。建议优先提供xhdpi的图片,对于mdpi,ldpi与xxxhdpi根据需要提供有差异的部分即可。

尽可能的重用已有的图片资源。例如对称的图片,只需要提供一张,另外一张图片可以通过代码旋转的方式实现。

能用代码绘制实现的功能,尽量不要使用大量的图片。例如减少使用多张图片组成animate-list的AnimationDrawable,这种方式提供了多张图片很占空间。

3、ANR是什么?怎样避免和解决ANR?

ANR:Application Not Responding,即应用无响应

ANR一般有三种类型:

  • 1:KeyDispatchTimeout(5 seconds) –主要类型

按键或触摸事件在特定时间内无响应

  • 2:BroadcastTimeout(10 seconds)

BroadcastReceiver在特定时间内无法处理完成

  • 3:ServiceTimeout(20 seconds) –小概率类型

Service在特定的时间内无法处理完成

超时的原因一般有两种:

  • (1)当前的事件没有机会得到处理(UI线程正在处理前一个事件没有及时完成或者looper被某种原因阻塞住)

  • (2)当前的事件正在处理,但没有及时完成

UI线程尽量只做跟UI相关的工作,耗时的工作(数据库操作,I/O或者其他可能阻碍UI线程的操作)放入单独的线程处理,尽量用Handler来处理UI thread和thread之间的交互。

(1)不要在主线程中做耗时的操作,而应放在子线程中来实现。如onCreate()和onResume()里尽可能少的去做创建操作。

(2)应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。

(3)避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。

(4)service是运行在主线程的,所以在service中做耗时操作,必须要放在子线程中。

4、Android内存泄露研究

Android内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。

场景

  • 类的静态变量持有大数据对象

静态变量长期维持到大数据对象的引用,阻止垃圾回收。

  • 非静态内部类的静态实例

非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉。

  • 资源对象未关闭

资源性对象如Cursor、File、Socket,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。

  • 注册对象未反注册

未反注册会导致观察者列表里维持着对象的引用,阻止垃圾回收。

  • Handler临时性内存泄露

Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个 target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。

  • 由于AsyncTask内部也是Handler机制,同样存在内存泄漏的风险。

此种内存泄露,一般是临时性的。

5、内存泄露检测有什么好方法?

检测:

  • 1、DDMS Heap发现内存泄露

dataObject totalSize的大小,是否稳定在一个范围内,如果操作程序,不断增加,说明内存泄露

  • 2、使用Heap Tool进行内存快照前后对比

BlankActivity手动触发GC进行前后对比,对象是否被及时回收

定位:

  • 1、MAT插件打开.hprof具体定位内存泄露:

查看histogram项,选中某一个对象,查看它的GC引用链,因为存在GC引用链的,说明无法回收

  • 2、AndroidStudio的Allocation Tracker:

观测到期间的内存分配,哪些对象被创建,什么时候创建,从而准确定位

6、进程保活(不死进程)

此处延伸:进程的优先级是什么

当前业界的Android进程保活手段主要分为 黑、白、灰 三种,其大致的实现思路如下:

  • 黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)

  • 白色保活:启动前台Service

  • 灰色保活:利用系统的漏洞启动前台Service

黑色保活

所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:

场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app

场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3

场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)

白色保活

白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如下方的LBE和QQ音乐这样:

灰色保活

灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:

思路一:API < 18,启动前台Service时直接传入new Notification();

思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理

熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生。

进程的重要性,划分5级:

  • 前台进程 (Foreground process)

  • 可见进程 (Visible process)

  • 服务进程 (Service process)

  • 后台进程 (Background process)

  • 空进程 (Empty process)

了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:

进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收

普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0

有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。

实现保活的方法:

  • a: Service设置成START_STICKY kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样


  • b: 通过 startForeground将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill


  • c: 双进程Service: 让2个进程互相保护对方,其中一个Service被清理后,另外没被清理的进程可以立即重启进程


  • d: 用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响(Android5.0以上的版本不可行)联系厂商,加入白名单


  • e.锁屏状态下,开启一个一像素Activity

7、Android内存泄露及管理

  • (1)内存溢出(OOM)和内存泄露(对象无法被回收)的区别。

  • (2)引起内存泄露的原因

  • (3) 内存泄露检测工具 ——>LeakCanary

内存溢出 out of memory:是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。内存溢出通俗的讲就是内存不够用。

内存泄露 memory leak:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光

内存泄露原因:

  • 一、Handler 引起的内存泄漏。

解决:将Handler声明为静态内部类,就不会持有外部类SecondActivity的引用,其生命周期就和外部类无关,

如果Handler里面需要context的话,可以通过弱引用方式引用外部类

  • 二、单例模式引起的内存泄漏。

解决:Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不会导致内存泄漏

  • 三、非静态内部类创建静态实例引起的内存泄漏。

解决:把内部类修改为静态的就可以避免内存泄漏了

  • 四、非静态匿名内部类引起的内存泄漏。

解决:将匿名内部类设置为静态的。

  • 五、注册/反注册未成对使用引起的内存泄漏。

注册广播接受器、EventBus等,记得解绑。

  • 六、资源对象没有关闭引起的内存泄漏。

在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等方法释放。

  • 七、集合对象没有及时清理引起的内存泄漏。

通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。

8、app优化

app优化:(工具:Hierarchy Viewer 分析布局 工具:TraceView 测试分析耗时的)

App启动优化

布局优化

响应优化

内存优化

电池使用优化

网络优化

  • App启动优化(针对冷启动)

App启动的方式有三种:

冷启动:App没有启动过或App进程被killed, 系统中不存在该App进程, 此时启动App即为冷启动。

热启动:热启动意味着你的App进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户。

介于冷启动和热启动之间, 一般来说在以下两种情况下发生:

(1)用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建。

(2)用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启, 但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复。

优化:

Application的onCreate(特别是第三方SDK初始化),首屏Activity的渲染都不要进行耗时操作,如果有,就可以放到子线程或者IntentService中

  • 布局优化

尽量不要过于复杂的嵌套。可以使用

  • 响应优化

Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity)。

页面卡顿的原因:

(1)过于复杂的布局.

(2)UI线程的复杂运算

(3)频繁的GC,导致频繁GC有两个原因:1、内存抖动, 即大量的对象被创建又在短时间内马上被释放.2、瞬间产生大量的对象会严重占用内存区域。

  • 内存优化:参考内存泄露和内存溢出部分

  • 电池使用优化(使用工具:Batterystats & bugreport)

(1)优化网络请求

(2)定位中使用GPS, 请记得及时关闭

  • 网络优化(网络连接对用户的影响:流量,电量,用户等待)

    可在Android studio下方logcat旁边那个工具Network Monitor检测API设计:App与Server之间的API设计要考虑网络请求的频次, 资源的状态等. 以便App可以以较少的请求来完成业务需求和界面的展示.Gzip压缩:使用Gzip来压缩request和response, 减少传输数据量, 从而减少流量消耗.图片的Size:可以在获取图片时告知服务器需要的图片的宽高, 以便服务器给出合适的图片, 避免浪费.网络缓存:适当的缓存, 既可以让我们的应用看起来更快, 也能避免一些不必要的流量消耗.

  • 图片优化

(1)对图片本身进行操作。尽量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存.

(2)图片进行缩放的比例,SDK中建议其值是2的指数值,值越大会导致图片不清晰。

(3)不用的图片记得调用图片的recycle()方法

9、内存泄漏的场景和解决办法

  • 1.非静态内部类的静态实例
非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类


  • 2.多线程相关的匿名内部类和非静态内部类
匿名内部类同样会持有外部类的引用,如果在线程中执行耗时操作就有可能发生内存泄漏,导致外部类无法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务


  • 3.Handler内存泄漏
Handler导致的内存泄漏也可以被归纳为非静态内部类导致的,Handler内部message是被存储在MessageQueue中的,有些message不能马上被处理,存在的时间会很长,导致handler无法被回收,如果handler是非静态的,就会导致它的外部类无法被回收,
    解决办法是1.使用静态handler,外部类引用使用弱引用处理2.在退出页面时移除消息队列中的消息


  • 4.Context导致内存泄漏
根据场景确定使用Activity的Context还是Application的Context,因为二者生命周期不同,对于不必须使用Activity的Context的场景(Dialog),一律采用Application的Context,单例模式是最常见的发生此泄漏的场景,比如传入一个Activity的Context被静态类引用,导致无法回收


  • 5.静态View导致泄漏
使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致无法回收,解决办法是在Activity销毁的时候将静态View设置为null(View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity)


  • 6.WebView导致的内存泄漏
WebView只要使用一次,内存就不会被释放,所以WebView都存在内存泄漏的问题,通常的解决办法是为WebView单开一个进程,使用AIDL进行通信,根据业务需求在合适的时机释放掉


  • 7.资源对象未关闭导致
如Cursor,File等,内部往往都使用了缓冲,会造成内存泄漏,一定要确保关闭它并将引用置为null


  • 8.集合中的对象未清理
集合用于保存对象,如果集合越来越大,不进行合理的清理,尤其是入股集合是静态的


  • 9.Bitmap导致内存泄漏
bitmap是比较占内存的,所以一定要在不使用的时候及时进行清理,避免静态变量持有大的bitmap对象


  • 10.监听器未关闭
很多需要register和unregister的系统服务要在合适的时候进行unregister,手动添加的listener也需要及时移除

10、如何避免OOM?

  • 1.使用更加轻量的数据结构:如使用ArrayMap/SparseArray替代HashMap,HashMap更耗内存,因为它需要额外的实例对象来记录Mapping操作,SparseArray更加高效,因为它避免了Key Value的自动装箱,和装箱后的解箱操作


  • 2.便面枚举的使用,可以用静态常量或者注解@IntDef替代


  • 3.Bitmap优化:
a.尺寸压缩:通过InSampleSize设置合适的缩放
b.颜色质量:设置合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异
c.inBitmap:使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小,但复用存在一些限制,具体体现在:在Android 4.4之前只能重用相同大小的Bitmap的内存,而Android 4.4及以后版本则只要后来的Bitmap比之前的小即可。使用inBitmap参数前,每创建一个Bitmap对象都会分配一块内存供其使用,而使用了inBitmap参数后,多个Bitmap可以复用一块内存,这样可以提高性能


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


  • 5.避免在类似onDraw这样的方法中创建对象,因为它会迅速占用大量内存,引起频繁的GC甚至内存抖动


  • 6.减少内存泄漏也是一种避免OOM的方法

11、说下冷启动与热启动是什么,区别,如何优化,使用场景等。

app冷启动: 当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用, 这个启动方式就叫做冷启动(后台不存在该应用进程)。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。

app热启动: 当应用已经被打开, 但是被按下返回键、Home键等按键时回到桌面或者是其他程序的时候,再重新打开该app时, 这个方式叫做热启动(后台已经存在该应用进程)。热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application

冷启动的流程
: 当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类、创建MainActivity类、加载主题样式Theme中的windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上

冷启动的生命周期简要流程:
Application构造方法 –> attachBaseContext()–>onCreate –>Activity构造方法 –> onCreate() –> 配置主体中的背景等操作 –>onStart() –> onResume() –> 测量、布局、绘制显示

冷启动的优化: 主要是视觉上的优化,解决白屏问题,提高用户体验,所以通过上面冷启动的过程。能做的优化如下:

  • 减少onCreate()方法的工作量

  • 不要让Application参与业务的操作

  • 不要在Application进行耗时操作

  • 不要以静态变量的方式在Application保存数据

  • 减少布局的复杂度和层级

  • 减少主线程耗时

12、Java多线程引发的性能问题,怎么解决?

主要的影响如下:

  • 1、消耗时间:线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失

  • 2、非常耗CPU和内存:大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM

  • 3、容易导致GC频繁的执行:大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿
    解决的办法归根到底就是:重用已有的线程,从而减少线程的创建

线程池的优点:

  • 节省系统的开销:线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销

  • 节省时间:线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量

  • 提高性能:在执行大量异步任务时提高了性能

  • 方便控制:Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等

13、启动页白屏及黑屏解决?

为什么冷启动会有白屏黑屏问题? (启动页白屏及黑屏解决?)
原因在于加载主题样式Theme中的windowBackground等属性设置给MainActivity发生在inflate布局当onCreate/onStart/onResume方法之前,而windowBackground背景被设置成了白色或者黑色,所以我们进入app的第一个界面的时候会造成先白屏或黑屏一下再进入界面。解决思路如下

  • 1.给他设置windowBackground背景跟启动页的背景相同,如果你的启动页是张图片那么可以直接给windowBackground这个属性设置该图片那么就不会有一闪的效果了

   <item name="android:windowBackground">@drawable/splash_bg</item><span class="ag-soft-line-break"></span>   <item name="android:windowNoTitle">true</item><span class="ag-soft-line-break"></span> `
  • 2.采用世面的处理方法,设置背景是透明的,给人一种延迟启动的感觉。,将背景颜色设置为透明色,这样当用户点击桌面APP图片的时候,并不会”立即”进入APP,而且在桌面上停留一会,其实这时候APP已经是启动的了,只是我们心机的把Theme里的windowBackground的颜色设置成透明的,强行把锅甩给了手机应用厂商(手机反应太慢了啦)

 <item name="android:windowIsTranslucent">true</item><span class="ag-soft-line-break"></span> <item name="android:windowNoTitle">true</item>
  • 3.以上两种方法是在视觉上显得更快,但其实只是一种表象,让应用启动的更快,有一种思路,将Application中的不必要的初始化动作实现懒加载,比如,在SpashActivity显示后再发送消息到Application,去初始化,这样可以将初始化的动作放在后边,缩短应用启动到用户看到界面的时间

14、启动太慢怎么解决?(或冷启动优化或怎么保证应用启动不卡顿?)

冷启动的优化: 主要是视觉上的优化,解决白屏问题,提高用户体验,所以通过上面冷启动的过程。能做的优化如下:

  • 减少onCreate()方法的工作量

  • 不要让Application参与业务的操作

  • 不要在Application进行耗时操作

  • 不要以静态变量的方式在Application保存数据

  • 减少布局的复杂度和层级

  • 减少主线程耗时

15、App启动崩溃异常捕捉

通过利用Thread类的setDefaultUncaughtExceptionHandler方法!defaultUncaughtHandlerThread类的静态成员变量,所以如果我们将自定义的UncaughtExceptionHandler设置给Thread的话,那么当前进程内的所有线程都能使用这个UncaughtExceptionHandler来处理异常了。

  private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
        public void uncaughtException(Thread t, Throwable e) {
            try {
               ......
                    Clog_e(TAG, message.toString(), e);//1. logcat打印出异常栈信息
              .......
                // Bring up crash dialog, wait for it to be dismissed
                ActivityManagerNative.getDefault().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
                                  //2. AMS处理crash的一系列行为,其中包括创建并提示crash对话框
            } catch (Throwable t2) {
               .....
            } finally {
                // Try everything to make sure this process goes away.
                Process.killProcess(Process.myPid());//3. 杀死应用进程
                System.exit(10);
            }
        }
    }

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                Log.e(TAG,e);
            }
        });

16、RecyclerView和ListView的性能对比,ListView的优化和RecycleView优化

17、Bitmap如何处理大图,如一张30M的大图,如何预防OOM?

避免OOM的问题就需要对大图片的加载进行管理,主要通过缩放来减小图片的内存占用。

  • BitmapFactory提供的加载图片的四类方法(decodeFile、decodeResource、decodeStream、decodeByteArray)都支持BitmapFactory.Options参数,通过inSampleSize参数就可以很方便地对一个图片进行采样缩放
  • 比如一张1024 x 1024的高清图片来说。那么它占有的内存为1024 x 1024 x 4,即4MB,如果inSampleSize为2,那么采样后的图片占用内存只有512 x 512 x 4,即1MB(注意:根据最新的官方文档指出,inSampleSize的取值应该总是为2的指数,即1、2、4、8等等,如果外界输入不足为2的指数,系统也会默认选择最接近2的指数代替,比如2)

通过采样率即可有效加载图片,流程如下

  • 将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片
  • 从BitmapFactory.Options中取出图片的原始宽高信息,它们对应outWidth和outHeight参数
  • 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
  • 将BitmapFactory.Options的inJustDecodeBounds参数设为false,重新加载图片

18、强引用置为null,会不会被回收?

不会立即释放对象占用的内存。 如果对象的引用被置为null,只是断开了当前线程栈帧中对该对象的引用关系,而 垃圾收集器是运行在后台的线程,只有当用户线程运行到安全点(safe point)或者安全区域才会扫描对象引用关系,扫描到对象没有被引用则会标记对象,这时候仍然不会立即释放该对象内存,因为有些对象是可恢复的(在 finalize方法中恢复引用 )。只有确定了对象无法恢复引用的时候才会清除对象内存。

19、优化自定义 view

  • 减少在onDraw里面大量计算和对象创建和大量内存分配.

  • 应该尽量少用invalidate()次数。

  • view里面耗时的操作layout。减少requestLayout()避免让UI系统重新遍历整棵树。Mearsure。

  • 如果你有一个很复杂的布局,不如将这个复杂的布局直接使用你自己的写的ViewGroup来实现。减少了一个树的层次关系 全部都是自己测量和layout,达到优化的目的。

20、LinearLayout 对比 RelativeLayout

性能对比:LinearLayout的性能要比RelativeLayout好。
因为RelativeLayout会测量两次。而默认情况下(没有设置weight)LinearLayout只会测量一次。
为什么RelativeLayout会测量两次?

首先RelativeLayout中的子view排列方式是基于彼此依赖的关系,而这个依赖可能和布局中view的顺序无关,在确定每一个子view的位置的时候,就需要先给每一个子view排一下序。又因为RelativeLayout允许横向和纵向相互依赖,所以需要横向纵向分别进行一次排序测量。

21、什么情况导致OOM

OOM产生的原因:内存不足,android系统为每一个应用程序都设置了一个硬性的条件:DalvikHeapSize最大阀值64M/48M/24M.如果你的应用程序内存占用接近这个阀值,此时如果再尝试内存分配的时候就会造成OOM。

  • 内存泄露多了就容易导致OOM

  • 大图的处理。压缩图片。平时开发就要注意对象的频繁创建和回收。

  • 可以适当的检测:ActivityManager.getMemoryClass()可以用来查询当前应用的HeapSize阀值。可以通过命名adb shellgetProp | grep dalvik.vm.heapxxxlimit查看。

如何避免内存泄露:

  • 减小对象的内存占用:
    a) 使用更加轻量级的数据结构:
    考虑适当的情况下替代HashMap等传统数据结构而使用安卓专门为手机研发的数据结构类ArrayMap/SparseArray。SparseLongMap/SparseIntMap/SparseBoolMap更加高效。
    HashMap.put(string,Object);Object o = map.get(string);会导致一些没必要的自动装箱和拆箱。
    b) 适当的避免在android中使用Enum枚举,替代使用普通的static常量。(一般还是提倡多用枚举—软件的架构设计方面;如果碰到这个枚举需要大量使用的时候就应该更加倾向于解决性能问题。)。
    c) 较少Bitmap对象的内存占用。
    使用inSampleSize:计算图片压缩比例进行图片压缩,可以避免大图加载造成OOM; decodeformat:图片的解码格式选择,ARGB_8888/RGB_565/ARGB_4444/ALPHA_8,还可以使用WebP。
    d) 使用更小的图片
    资源图片里面,是否存在还可以继续压缩的空间。

  • 2) 内存对象的重复利用:
    使用对象池技术,两种:1.自己写;2.利用系统既有的对象池机制。比如LRU(Last Recently Use)算法。
    a) ListView/GridView源码可以看到重用的情况ConvertView的复用。RecyclerView中Recycler源码。
    b) Bitmap的复用
    Listview等要显示大量图片。需要使用LRU缓存机制来复用图片。
    C) 避免在onDraw方法里面执行对象的创建,要复用。避免内存抖动。
    D) 常见的java基础问题—StringBuilder等

  • 3) 避免对象的内存泄露:

  • 4) 使用一些内存的优化策略


   转载规则


《Android性能优化相关面试题》 mikyou 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
计算机网络面试题 计算机网络面试题
计算机网络相关面试题1、网络分层 OSI七层模型OSI七层协议模型主要是: 应用层(Application) 表示层(Presentation) 会话层(Session) 传输层(Transport) 网络层(Network) 数据链路层(
2019-12-10
下一篇 
Android中BlockCanary原理分析 Android中BlockCanary原理分析
BlockCanary原理分析一、背景在Android开发中,经常会遇到应用程序卡顿的情况,对于卡顿问题的出现,有时候很难去复现和定位。卡顿可能和当时手机的ROM,系统环境有关,dump相关日志分析往往画了很多时间。然后BlockCanar
2019-12-03
  目录