Android 性能调优面试题

1.1 谈谈你对Android性能优化方面的了解?

  • 启动优化: application中不要做大量耗时操作,如果必须的话,建议异步做耗时操作
  • 布局优化:使用合理的控件选择,少嵌套。(合理使用 include,merge,viewStub等使用)
  • apk优化(资源文件优化,代码优化,lint检查,.9.png,合理使用shape替代图片,webp等)
  • 性能优化,网络优化,电量优化
    避免轮询,尽量使用推送
    应用处于后台时,禁用某些数据传输
    限制访问频率,失败后不要无限重连
    选用合适的定位服务(GPS定位,网络定位,被动定位)
    使用缓存
    startActivityForResult替代发送广播
  • 内存优化
    循环尽量不使用局部变量
    避免在onDraw中创建对象,onDraw会被频繁调用,容易造成内存抖动。循环中创建大的对象,也是如此。
    不用的对象及时释放
    数据库的cursor及时关闭
    adapter使用缓存
    注册广播后,在生命周期结束时反注册
    及时关闭流操作
    图片尽量使用软引用,较大的图片可以通过BitmapFactory缩放后再使用,并及时recycler。另外加载巨图时不要使用setImageBitmap或setImageResourse或BitmapFactory.decodeResource,这些方法拿到的都是bitmap的对象,占用内存较大。可以用BitmapFactory.decodeStream方法配合BitmapFactory.Options进行缩放避免static成员变量引用资源耗费过多实例避免静态内部类的引用

1.2 一般什么情况下会导致内存泄漏问题?

  1. 资源对象没关闭造成的内存泄漏(如: Cursor、File等)
  2. ListView 的 Adapter 中没有使用缓存的ConvertView
  3. Bitmap 对象不在使用时调用recycle()释放内存
  4. 集合中对象没清理造成的内存泄漏(特别是 static 修饰的集合)
  5. 接收器、监听器注册没取消造成的内存泄漏
  6. Activity 的 Context 造成的泄漏,可以使用ApplicationContext
  7. Handler 造成的内存泄漏问题(一般由于 Handler 生命周期比其外部类的生命周期长引起的)

1.3 自定义 Handler 时如何有效地避免内存泄漏问题?

  1. 自定义的静态Handler
  2. 可以加一个弱引用
  3. 还有一个主意的就是当你Activity被销毁的时候如果还有消息没有发出去就remove掉吧
  4. removeCallbackSandMessages去清除Message和 Runnable 加 null 写在生命周的onDestroy()就行

1.4 哪些情况下会导致oom问题?

  1. 例如 Handler 在界面销毁的时候消息还未发送
  2. File没有关流
  3. 查询到结果还没有停止
  4. 内部类持有外部类引用得不到释放
  5. 不用的对象最好 null 让 GC 回收一下
  6. 图片资源什么的最好加一个软引用

1.5 ANR 出现的场景以及解决方案?

在Android中,应用的响应性被活动管理器(ActivityManager)和窗口管理器(Window Manager)这两个系统服务所监视。当用户触发了输入事件(如键盘输入,点击按钮等),如果应用5秒内没有响应用户的输入事件,那么,Android会认为该应用无响应,便弹出ANR对话框。而弹出ANR异常,也主要是为了提升用户体验。
解决方案是对于耗时的操作,比如访问网络、访问数据库等操作,需要开辟子线程,在子线程处理耗时的操作,主线程主要实现UI的操作。

1.6 谈谈Android中内存优化的方式?

关于内存泄漏,一般像单例模式的使用不当、集合的操作不当、资源的缺乏有效的回收机制、Handler、线程的使用不当等等都有可能引发内存泄漏。

  1. 单例模式引发的内存泄漏
    原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用
    优化:改为持有Application的引用,或者不持有使用的时候传递。
  2. 集合操作不当引发的内存泄漏
    原因:集合只增不减
    优化:有对应的删除或卸载操作
  3. 线程的操作不当引发的内存泄漏
    原因:线程持有对象的引用在后台执行,与对象的生命周期不一致
    优化:静态实例+弱引用(WeakReference)方式,使其生命周期一致
  4. 匿名内部类/非静态内部类操作不当引发的内存泄漏
    原因:内部类持有对象引用,导致无法释放,比如各种回调
    优化:保持生命周期一致,改为静态实例+对象的弱引用方式(WeakReference)
  5. 常用的资源未关闭回收引发的内存泄漏
    原因:BroadcastReceiver,File,Cursor,IO流,Bitmap等资源使用未关闭
    优化:使用后有对应的关闭和卸载机制
  6. Handler使用不当造成的内存泄漏
    原因:Handler持有Activity的引用,其发送的Message中持有Handler的引用,当队列处理Message的时间过长会导致Handler无法被回收
    优化:静态实例+弱引用(WeakReference)方式

内存溢出

原因:

  1. 内存泄漏长时间的积累
  2. 业务操作使用超大内存
    优化:
  3. 调整图像大小后再放入内存、及时回收
  4. 不要过多的创建静态变量

1.7 谈谈布局优化的技巧?

1、降低Overdraw(过度绘制),减少不必要的背景绘制
2、减少嵌套层次及控件个数
3、使用Canvas的clipRect和clipPath方法限制View的绘制区域
4、通过imageDrawable方法进行设置避免ImageView的background和imageDrawable重叠
5、借助ViewStub按需延迟加载
6、选择合适的布局类型
7、熟悉API尽量借助系统现有的属性来实现一些UI效果

1.8 Android中的图片优化方案?

  1. 首先我们可以对图片进行二次采样,从本质上减少图片的内存占用。就是将大图片缩小之后放入到内存中,以实现减小内存的目的
  2. 其次就是采用三层缓存架构,提高图片的访问速度。三层缓存架构是内存-文件-网络。内存是访问速度最快的部分但是分配的空间有限,所以不可能占用太多。其中内存缓存可以采用LRU算法(最近最少使用算法),来确定要删除内存中的那些图片,保存那些图片。文件就是将图片保存到本地,可以使SD卡中,也可以是手机内部存储中。网络就是访问网络下载图片,进行图片的加载。
  3. 常见的png,JPG,webp等格式的图片在设置到UI上之前需要经过解码过程,而图片采用不同的码率,也会造成对内存的占用不同。
  4. 最后一点,也是图片优化最重要的一点。重用Bitmap.
  5. 不使用Bitmap要记得实时回收,减小内存的开销

1.9 Android Native Crash问题如何分析定位?

  1. 利用breakpad,dump Native崩溃时日志信息
  2. 利用addr2line跟ndk-strace等工具,根据崩溃日志偏移量定位具体源码位置
  3. 根据信号提示进行具体处理
    此步骤的难点在于:
    Native Crash的时候整个app的状态是极其不稳定的,很可能由于保存日志(或者上报日志)等操作引起其他异常,所以此时最好fork一个子进程来保存当前进程的日志。

1.10 谈谈怎么给apk瘦身?

第1条:使用一套资源

这是最基本的一条规则,但非常重要。
对于绝大对数APP来说,只需要取一套设计图就足够了。
鉴于现在分辨率的趋势,建议取720p的资源,放到xhdpi目录。

相对于多套资源,只使用720P的一套资源,在视觉上差别不大,很多大公司的产品也是如此,但却能显著的减少资源占用大小,顺便也能减轻设计师的出图工作量了。

注意,这里不是说把不是xhdpi的目录都删除,而是强调保留一套设计资源就够了。

第2条:开启minifyEnabled混淆代码

在gradle使用minifyEnabled进行Proguard混淆的配置,可大大减小APP大小:

    android {
        buildTypes {
            release {
                minifyEnabled true
            }
        }
    }

在proguard中,是否保留符号表对APP的大小是有显著的影响的,可酌情不保留,但是建议尽量保留用于调试。
详细proguard的相关的配置和原理可自行查阅。

第3条:开启shrinkResources去除无用资源

在gradle使用shrinkResources去除无用资源,效果非常好。

    android {
        buildTypes {
            release {
                shrinkResources true
            }
        }
    }

第4条:删除无用的语言资源
大部分应用其实并不需要支持几十种语言的国际化支持。
还好强大的gradle支持语言的配置,比如国内应用只支持中文:

    android {
        defaultConfig {
            resConfigs "zh"
        }
    }

第5条:使用Tinypng有损压缩

android打包本身会对png进行无损压缩,所以使用像Tinypng这样的有损压缩是有必要的。
重点是Tinypng使用智能有损压缩技术,以尽量少的失真换来图片大小的锐减,效果非常好,强烈推荐。
Tinypng的官方网站:TinyPNG – Compress WebP, PNG and JPEG images intelligently

第6条:使用jpg格式

如果对于非透明的大图,jpg将会比png的大小有显著的优势,虽然不是绝对的,但是通常会减小到一半都不止。
在启动页,活动页等之类的大图展示区采用jpg将是非常明智的选择。

第7条:使用webp格式

webp支持透明度,压缩比比jpg更高但显示效果却不输于jpg,官方评测quality参数等于75均衡最佳。
相对于jpg、png,webp作为一种新的图片格式,限于android的支持情况暂时还没用在手机端广泛应用起来。从Android 4.0+开始原生支持,但是不支持包含透明度,直到Android 4.2.1+才支持显示含透明度的webp,使用的时候要特别注意。
官方介绍

第8条:缩小大图

如果经过上述步骤之后,你的工程里面还有一些大图,考虑是否有必要维持这样的大尺寸,是否能适当的缩小。
事实上,由于设计师出图的原因,我们拿到的很多图片完全可以适当的缩小而对视觉影响是极小的。

第9条:覆盖第三库里的大图

有些第三库里引用了一些大图但是实际上并不会被我们用到,就可以考虑用1x1的透明图片覆盖。
你可能会有点不舒服,因为你的drawable下竟然包含了一些莫名其妙的名称的1x1图片…

第10条:删除armable-v7包下的so

基本上armable的so也是兼容armable-v7的,armablev7a的库会对图形渲染方面有很大的改进,如果没有这方面的要求,可以精简。
这里不排除有极少数设备会Crash,可能和不同的so有一定的关系,请大家务必测试周全后再发布。

第11条:删除x86包下的so

与第十条不同的是,x86包下的so在x86型号的手机是需要的,如果产品没用这方面的要求也可以精简。
建议实际工作的配置是只保留armable、x86下的so文件,算是一个折中的方案。

第12条:使用微信资源压缩打包工具

微信资源压缩打包工具通过短资源名称,采用7zip对APP进行极致压缩实现减小APP的目标,效果非常的好,强烈推荐。
详情参考:Android资源混淆工具使用说明
原理介绍:安装包立减1M–微信Android资源混淆打包工具建议开启7zip,注意白名单的配置,否则会导致有些资源找不到,官方已经发布AndResGuard到gradle中了,非常方便:

    apply plugin:'AndResGuard'

    buildscript {
        dependencies {
            classpath 'com.tencent.mm:AndResGuard-gradleplugin:1.1.7'
        }
    }

    andResGuard {
        mappingFile = null
        use7zip = true
        useSign = true
        keepRoot = false
        // add < your_application_id >.R.drawable.icon into whitelist.
        // because the launcher will get thgge icon with his name
        def packageName = <your_application_id > whiteList = [
        //for your icon 
                packageName + ".R.drawable.icon",
                //for fabric 
                packageName + ".R.string.com.crashlytics.*",
                //for umeng update
                packageName + ".R.string.umeng*",
                packageName + ".R.string.UM*",
                packageName + ".R.string.tb_*",
                packageName + ".R.layout.umeng*",
                packageName + ".R.layout.tb_*",
                packageName + ".R.drawable.umeng*",
                packageName + ".R.drawable.tb_*",
                packageName + ".R.anim.umeng*",
                packageName + ".R.color.umeng*",
                packageName + ".R.color.tb_*",
                packageName + ".R.style.*UM*",
                packageName + ".R.style.umeng*",
                packageName + ".R.id.umeng*"
        ]
        compressFilePattern = [
                "*.png",
                "*.jpg",
                "*.jpeg",
                "*.gif",
                "resources.arsc"
        ]
        sevenzip {
            artifact = 'com.tencent.mm:SevenZip:1.1.7'
            //path = "/usr/local/bin/7za"
        }
    }

会生成一个andresguard/resguard的Task,自动读取release签名进行重新混淆打包。

第13条:使用provided编译

对于一些库是按照需要动态的加载,可能在某些版本并不需要,但是代码又不方便去除否则会编译不过。
使用provided可以保证代码编译通过,但是实际打包中并不引用此第三方库,实现了控制APP大小的目标。
但是也同时就需要开发者自己判断不引用这个第三方库时就不要执行到相关的代码,避免APP崩溃。

第14条:使用shape背景

特别是在扁平化盛行的当下,很多纯色的渐变的圆角的图片都可以用shape实现,代码灵活可控,省去了量的背景图片。

第15条:使用着色方案

相信你的工程里也有很多selector文件,也有很多相似的图片只是颜色不同,通过着色方案我们能大大减轻这样的工作量,减少这样的文件。
借助于android support库可实现一个全版本兼容的着色方案,参考代码:DrawableLess.java

第16条:在线化素材库

如果你的APP支持素材库(比如聊天表情库)的话,考虑在线加载模式,因为往往素材库都有不小的体积。
这一步需要开发者实现在线加载,一方面增加代码的复杂度,一方面提高了APP的流量消耗,建议酌情选择。

第17条:避免重复库

避免重复库看上去是理所当然的,但是秘密总是藏的很深,一定要当心你引用的第三方库又引用了哪个第三方库,这就很容易出现功能重复的库了,比如使用了两个图片加载库:Glide和Picasso。
通过查看exploded-aar目录和External Libraries或者反编译生成的APK,尽量避免重复库的大小,减小APP大小。

第18条:使用更小的库

同样功能的库在大小上是不同的,甚至会悬殊很大。
如果并无对某个库特别需求而又对APP大小有严格要求的话,比较这些相同功能第三方库的大小,选择更小的库会减小APP大小。

第19条:支持插件化

过去的一年,插件化技术雨后春笋一样的都冒了出来,这些技术支持动态的加载代码和动态的加载资源,把APP的一部分分离出来了,对于业务庞大的项目来说非常有用,极大的分解了APP大小。
因为插件化技术需要一定的技术保障和服务端系统支持,有一定的风险,如无必要(比如一些小型项目,也没什么扩展业务)就不需要了,建议酌情选择。

第20条:精简功能业务

这条完全取决于业务需求。
从统计数据分析砍掉一些没用的功能是完全有可能的,甚至干脆去掉一些花哨的功能出个轻聊版、极速版也不是不可以的。

第21条:重复执行第1到20条

多次执行上述步骤,你总能发现一些蛛丝马迹,这是一个好消息,不是吗?

第22条:Facebook的redex优化字节码
redex是facebook发布的一款android字节码的优化工具,需要按照说明文档自行配置一下。

redex input.apk -o output.apk --sign -s <KEYSTORE> -a <KEYALIAS> -p <KEYPASS>

1.11 谈谈你是如何优化App启动过程的?

  1. 把Application onCreate中要执行的方法分为同步和异步,尽量去延迟执行或者使用空闲线程去初始化一些方法
  2. 配置一个启动背景,避免白屏或者黑屏,然后做一个空的Activity这个Activity只做一件事,就是跳转到真的Activity,因为启动速度和Application onCreate的耗时和第一个Activity的绘制有关,

上面都是easy的做法

  1. 利用redex工具优化dex , 因为class字节码分布在不同的dex中,所以启动的时候必须逐个查找一些文件,他们散列分布在不同的dex中,查找起来耗时又不方便,利用redex把相关的class放在同一个dex包下,避免同一个dex包被多次查找
  2. 在attachBaseContext中新起一个进程去加载mutildex可以加速App启动页的打开(可能在启动页中会等待,但是加速了从launcher到启动页的速度)

1.12 谈谈代码混淆的步骤?

开启混淆

  1. 查看项目使用的第三方哪些需要设置混淆
  2. 保持反射native对应的类和方法不混淆
  3. 保持自定义类不混淆
  4. 保持实体类参与序列化的不混淆
  5. 系统组件等一些固定方法会被系统调用的不混淆

1.13 谈谈App的电量优化?

优化方案总结

(1)GPS

使用要谨慎,如精确度不高可用WiFi定位或者基站定位,可用
非要用的话,注意定位数据的复用和定位频率的阈值

(2)Process和Service

按需启动,用完就退出

(3)网络数据交互

减少网络网络请求次数和数据量
WiFi比手机网络省电

(4)CPU

减少I/O操作(包括数据库操作)
减少大量的计算

(5)减少手机硬件交互

使用频率优化和选择低功耗模式

1.14 谈谈如何对WebView进行优化?

  1. 单/多进程化:WebView在独立的进程里面,那么WebView的进程崩溃不会影响到主进程运行;同时WebView的安全漏洞也很难影响到主进程;如果是多进程的话,可以使用WebView的容器池,有二次秒开的作用;不过缺点就是需要你做好和WebView的跨进程通讯了
  2. 网络优化:我们可以让WebView的host和客户端的host保持一致,那么就达到复用DNS缓存的效果;如果客户端有针对网络请求进行了优化,那么可以让WebView的全部网络请求托管给客户端
  3. H5离线包:这个是手Q的H5方案之一,让客户端提前去下载离线的H5数据包,WebView只需要加载本地H5数据包即可,这么做不仅可以避免一些http的劫持,而且跳过了WebView的建立TCP连接和H5、CCS等数据下载的过程,直接开始UI渲染,大大提高了WebView的效率

1.15 如何处理大图的加载?

1、首先确定大图的用途,精度需求:

  • 完整显示,对精度要求不高,图片本身就很大
  • 对精度需求比较高,不需要完整显示

2、解决方案

  • 针对第一种的处理图片本身,按需加载(根据显示设备本身大小进行缩放),降低精度加载(改变图片模式,如将ARGB8888改成RGB565,ARGB4444),修改图片格式(png改成webp,jpg)
  • 第二种的一般采用局部加载,主要要用到的是BitmapRegionDecoder这个类decodeRegion的方法,读取图片指定大小的数据,然后通过移动来动态改变显示区域的图片

1.16 谈谈如何对网络请求进行优化?

  1. 最开始的是DNS,当我们发起一个网络请求,首先要经过DNS服务,将域名转化为IP地址,为避免DNS解析异常问题,可以直接使用 IP 建立连接;
  2. 使用 Gzip 压缩 Response 减少数据传输量;使用Protocol Buffer 代替 JSON;
  3. 请求图片的 url 中可以添加格式、质量、宽高等参数;使用缩略图、使用WebP格式图片,大图分片传输;
  4. 使用网络缓存,使用图片加载框架;
  5. 监听设备网络状态,根据不同网络状态选择对应情况下的网络请求策略:网络良好和弱网、离线等情况下分别设计不同的请求策略,比如 WIFI 下一个请求可以获取几十个数据,甚至可以一次性执行多个请求;而弱网下一个请求获取几个数据,且文本类型优先,富文本其次,除文本数据外其它类型的数据一开始只显示占位符;离线下事先保存请求数据到磁盘,在离线时从磁盘加载数据。

1.17 请谈谈如何加载Bitmap并防止内存溢出?

首先我们 要知道Bitmap内存是怎么计算的例子:
手机屏幕大小 1080 x 1920(inTarget = 420),加载xhdpi (inDensity = 320)中的图片 1920 x 1080,scale = 420 / 320,最总我们可以得知他的占用内存 1418 * 2520 * 4 很明显 被放大了。

防止内存溢出:

  1. 对图片进行内存压缩;
  2. 高分辨率的图片放入对应文件夹;
  3. 内存复用
  4. 及时回收
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容