Android面试指南-面霸之路07-优化合集

引起内存泄漏的情况

  • 对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
  • 静态内部类持有外部成员变量(或context):可以使用弱引用或使用ApplicationContext。
  • 内部类持有外部类引用,异步任务中,持有外部成员变量。
  • 集合中没用的对象没有及时remove。
  • 不用的对象及时释放,如使用完Bitmap后掉用recycle(),再赋null。
  • handler引起的内存泄漏,MessageQueue里的消息如果在activity销毁时没有处理完,就会引起内存的泄漏,可以使用弱引用解决。
  • 设置过的监听不用时,及时移除。如在Destroy时及时remove。尤其以addListener开头的,在Destroy中都需要remove。
  • activity泄漏可以使用LeakCanary。

  • 在内存引用上做些处理,常用的有软引用、弱引用
  • 在内存中加载图片时直接在内存中作处理,如:边界压缩
  • 动态回收内存
  • 优化Dalvik虚拟机的堆内存分配
  • 自定义堆内存大小

Android中常见的内存泄漏汇总
  • 单例造成的内存泄漏
  • 非静态内部类创建静态实例造成的内存泄漏
  • Handler造成的内存泄漏
  • 线程造成的内存泄漏
  • 资源未关闭造成的内存泄漏

一些建议

  1. 对于生命周期比Activity长的对象如果需要应该使用ApplicationContext
  2. 对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏
  3. 对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null
  4. 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期
  5. 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
    1. 将内部类改为静态内部类
    2. 静态内部类中使用弱引用来引用外部类的成员变量
  6. 在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:

    其中:NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建


  • 强引用、软引用、弱引用、虚引用总结
总结:
强引用:普通new对象的引用
User user = new User();
jvm宁愿抛出OutOfMemory异常也不会去回收该对象
软引用:
SoftReference引用的装饰的对象,该对象的强引用应该赋值空
这里配合ReferenceQueue使用(也可以不配合),因为SoftReference本身也是new了一个对象
当他把别人的问题解决掉以后,也需要把自己的问题解决,所以当jvm把软引用
对象回收后,就会把自己的对象引用放到这个队列中,我们可以通过队列的poll()
方法查看,如果存在该ReferenceQueue,那么就说明该对象的软引用对象已经被jvm
回收,我们需要将该ReferenceQueue引用也赋值为空,等待被jvm回收。
注意:软引用对象是在jvm内存不够的时候才会被回收,我们调用System.gc()方法只是
起通知作用,jvm什么时候扫描回收对象是,是jvm自己的状态决定的。就算扫描到软引用
对象也不一定会回收它。只有内存不够的时候才会回收
ReferenceQueue queue = new ReferenceQueue();
User user = new User();
SoftReference ref=new SoftReference(user, queue);
user=null;
弱引用:弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,只要扫描到,
无论内存是否充足(与软引用的区别),都会回收被弱引用关联的对象。在java中,
用java.lang.ref.WeakReference类来表示
WeakReference<User> sr = new WeakReference<User>(new User());
System.out.println(sr.get());
System.gc(); //通知JVM的gc进行垃圾回收
System.out.println(sr.get());
虚引用:
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。
在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,
则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
要注意的是,虚引用必须和引用队列关联

ListView卡顿原因
Adapter的getView方法里面convertView没有使用setTag和getTag方式;
在getView方法里面ViewHolder初始化后的赋值或者是多个控件的显示状态和背景的显示没有优化好,抑或是里面含有复杂的计算和耗时操作;
在getView方法里面 inflate的row 嵌套太深(布局过于复杂)或者是布局里面有大图片或者背景所致;
Adapter多余或者不合理的notifySetDataChanged;
listview 被多层嵌套,多次的onMessure导致卡顿,如果多层嵌套无法避免,建议把listview的高和宽设置为fill_parent. 如果是代码继承的listview,那么也请你别忘记为你的继承类添加上LayoutPrams,注意高和宽都是fill_parent的;

Android APP内存优化之图片优化
通过DDMS的APP内存占用查看工具分析发现,APP中占用内存最多的是图片,每个Activity中图片占用内存占大半
不要将Button的背景设置为selector
将背景图片放在非UI线程绘制,提升APP的效率
解决方案是将背景图片通过SurfaceView来绘制,这样相当于是在非UI线程绘制,不会影响到UI线程做其它事情
没有必要使用硬件加速的界面建议关掉硬件加速
通过DDMS的heap跟踪发现,相比于关闭硬件加速,在打开硬件加速的情况下会消耗更多的内存,但有的界面打开或者关闭硬件加速对程序的运行效率并没有太大的影响
尽量少用AnimationDrawable,如果必须要可以自定义图片切换器代替AnimationDrawable
AnimationDrawable也是一个耗内存大户,图片帧数越多耗内存越大,具体可以查看AnimationDrawable的源码

其它优化方式

  • 尽量将Activity中的小图片和背景合并,一张小图片既浪费布局的时间,又平白地增加了内存占用;

  • 不要在Activity的主题中为Activity设置默认的背景图片,这样会导致Activity占用的内存翻倍:

    <!--千万不要在主题中为Activity设置默认背景

    <style name="Activity_Style" parent="@android:Theme.Holo.Light.NoActionBar">
    <item name="android:background">@drawable/*</item>
    </style>

  • 对于在需要时才显示的图片或者布局,可以使用ViewStub标签,通过sdk/tools目录下的hierarchyviewer.bat查看布局文件会发现,使用viewstub标签的组件几乎不消耗布局的时间,在代码中当需要显示时再去实例化有助于提高Activity的布局效率和节省Activity消耗的内存。

Android性能优化之如何避免Overdraw
第一招:合理选择控件容器
LinearLayout易用,效率高,表达能力有限。RelativeLayout复杂,表达能力强,效率稍逊。
第二招:去掉window的默认背景

去掉window的背景可以在onCreate()中setContentView()之后调用

getWindow().setBackgroundDrawable(null);

或者在theme中添加

android:windowbackground="null";
第三招:去掉其他不必要的背景
第四招:ClipRect & QuickReject
第五招:ViewStub
ViewStub是个什么东西?一句话总结:高效占位符。
第六招:Merge
Merge标签有什么用呢?简单粗暴点回答:干掉一个view层级。
第七招:善用draw9patch
第八招:慎用Alpha
第九招:避免“OverDesign”

FC(Force Close)什么时候会出现
  • Error
  • OOM,内存溢出
  • StackOverFlowError
  • Runtime,比如说空指针异常

解决的办法

  • 注意内存的使用和管理
  • 使用Thread.UncaughtExceptionHandler接口

ANR(ANR:Application Not Responding)

ANR产生的原因?

ANR产生的根本原因是APP阻塞了UI线程。在android系统中每个App只有一个UI线程,是在App创建时默认生成的,UI线程默认初始化了一个消息循环来处理UI消息,ANR往往就是处理UI消息超时了。那么UI消息来源有哪些呢?主要有两种来源:

2.1 来自于AMS的回调消息

在Android系统中,应用程序是有Android的四大组件组成,AMS负责对应用程序四大组件生命周期的管理,当AMS对应用程序组件的生命周期进行回调超过AMS定义的响应时间时,AMS就会报ANR。出现这种情况,一般是因为在这些组件的回调函数里面进行了耗时操作(如网络操作、SD卡文件操作、数据库操作、大量计算等),AMS对组件常见的回调函数及超时时间如下:
Activity: onCreate(), onResume(), onDestroy(), 
onKeyDown(), onClick()等,超时时间5s
Application: onCreate(), onTerminate()等,超时时间5s
Service: onCreate(), onStart(), onDestroy()等,超时时间20s
BroadcastReceiver:onReceiver(),前台APP广播超时时间是10s,后台App是60s。
2.2 App自己的发出的消息

除了AMS对四大组件的回调消息运行在UI线程外,有些操作也是运行在UI线程的:
AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(), onCancel()等,超时5s
Mainthread handler: handleMessage(), post*(runnable r)等,超时5s
3.怎样避免ANR
1:UI线程尽量只做跟UI相关的工作,但一些复杂的UI操作,还是需要一些技巧来处理,不如你让一个Button去setText一个10M的文本,UI肯定崩掉了,不过对于此类问题,分段加载貌似是最好的方法了。
2:让耗时的工作(比如数据库操作,I/O,连接网络或者别的有可能阻碍UI线程的操作)把它放入单独的线程处理。
3:尽量用Handler来处理UIthread和别的thread之间的交互。

4.发布的程序怎样收集ANR异常
对于发布的程序,ANR异常是很那捕获不到的(我查找过很多资料,如果您有很好的捕获办法,欢迎再下方留言),所以我们需要采用其它的方法来分析ANR。app在产生ANR异常后,会将异常信息写入"/data/anr/traces.txt"文件我们可以通过收集用户的这个文件,就可以来获取用户产生ANR的地方了。
我在一个按钮的onClick事件里写了如下代码
while(tru){}
来故意产生一个ANR异常,然后打开/data/anr/traces.tx文件,主要有用的地方如下图:

我们可以看到,在trace.txt文件里已经定位到异常产生的地方。所以,在用户反馈界面,当我们发现户反馈内容里是否出现了"无响应"等字眼的时候,就可以提示用户是否上传异常文件,来帮助我们改善产品之类的。当然,现在流氓猖狂,貌似做到这么细致产品的,为数不多了。


内存优化
核心思想:减少内存使用,能不new的不new,能少分配的少分配。因为分配更多的内存就意味着发生更多的GC,每次触发GC都会占用CPU时间,影响性能。

  • 集合优化:Android提供了一系列优化过后的数据集合工具类,如SparseArray、SparseBooleanArray、LongSparseArray,使用这些API可以让我们的程序更加高效。HashMap工具类会相对比较低效,因为它需要为每一个键值对都提供一个对象入口,而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间。
  • Bitmap优化:读取一个Bitmap图片的时候,千万不要去加载不需要的分辨率。可以压缩图片等操作。
  • 尽量避免使用依赖注入框架。
  • 避免创作不必要的对象:字符串拼接使用StringBuffer,StringBuilder。
  • onDraw方法里面不要执行对象的创建.
  • 重写onTrimMemory,根据传入的参数,进行内存释放。
  • 使用static final 优化成员变量。

移动端获取网络数据优化的几个点

连接复用:节省连接建立时间,如开启 keep-alive。
对于Android来说默认情况下HttpURLConnection和HttpClient都开启了keep-alive。只是2.2之前HttpURLConnection存在影响连接池的Bug,具体可见:Android HttpURLConnection及HttpClient选择

请求合并:即将多个请求合并为一个进行请求,比较常见的就是网页中的CSS Image Sprites。如果某个页面内请求过多,也可以考虑做一定的请求合并。

减少请求数据的大小:对于post请求,body可以做gzip压缩的,header也可以做数据压缩(不过只支持http 2.0)。
返回数据的body也可以做gzip压缩,body数据体积可以缩小到原来的30%左右。(也可以考虑压缩返回的json数据的key数据的体积,尤其是针对返回数据格式变化不大的情况,支付宝聊天返回的数据用到了)
根据用户的当前的网络质量来判断下载什么质量的图片(电商用的比较多)


Android系统启动过程,App启动过程
]从桌面点击到activity启动的过程

1.Launcher线程捕获onclick的点击事件,调用Launcher.startActivitySafely,进一步调用Launcher.startActivity,最后调用父类Activity的startActivity。

2.Activity和ActivityManagerService交互,引入Instrumentation,将启动请求交给Instrumentation,调用Instrumentation.execStartActivity。

3.调用ActivityManagerService的startActivity方法,这里做了进程切换(具体过程请查看源码)。

4.开启Activity,调用onCreate方法


Android 屏幕适配

屏幕适配的方式:xxxdpi, wrap_content,match_parent. 获取屏幕大小,做处理。

dp来适配屏幕,sp来确定字体大小

drawable-xxdpi, values-1280*1920等 这些就是资源的适配。

wrap_content,match_parent, 这些是view的自适应

weight,这是权重的适配。



  • 比如问一下内存泄露,那么试着多列举一些情况:
首先解释内存泄露是什么。
最常见的handler引用Activity,static+WeakReference解决
耗时线程
bitmap调用recycle,3.0前和后,3.0后偷换native内存
想持久化Drawable,定义成static,老版本他持有view,view持有activity,可能间接造成activity泄露,4.0已修复。
广播记得取消注册
除此之外,如能提到jdk1.6中String的substring方法,那效果更好。
再比如问到性能优化:
从布局优化ViewStub,merge,include
overdraw,GPU选项观察overdraw情况
view的ondraw,尽量别new对象,不能耗时,60fps,16ms,GPU加速
必要情况下使用SurfaceView
  • SurfaceView和View最本质的区别在于,surfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面。那么在UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。

避免内存泄露(又回到上一个问题了)
ListView的优化:holder,分页加载,滑动停止后再下载图片等
Bitmap的优化,加载单个bitmap,多个bitmap时的内存硬盘缓存
利用android中的一些数据结构比如ArrayMap和SparseArray
少用枚举,多用static访问快



Android 中布局的优化措施都有哪些? 
这个问题也属于Android 性能优化的一部分。 
1、尽可能减少布局的嵌套层级 
可以使用 sdk 提供的 hierarchyviewer 工具分析视图树,帮助我们发现没有用到的布局。 
2、不用设置不必要的背景,避免过度绘制 比如父控件设置了背景色,子控件完全将父控件给覆盖的情况下,那么父控件就没有必要设置背景。 
3、使用<include>标签复用相同的布局代码 
4、使用<merge>标签减少视图层次结构 
该标签主要有两种用法: 
1) 因为所有的 Activity 视图的根节点都是 FrameLayout,因此如果我们的自定义的布局也是 FragmenLayout 的时候那么可以使用 merge 替换。 
2) 当应用 Include 或者 ViewStub 标签从外部导入 xml 结构时,可以将被导入的 xml 用 merge 作为根节 点表示,这样当被嵌入父级结构中后可以很好的将它所包含的子集融合到父级结构中,而不会出现冗余的节点。 
<merge>只能作为 xml 布局的根元素。 
5、通过<ViewStub>实现 View 的延迟加载

Listview 优化问题

1.ListView 如何提高其效率? 

当 convertView 为空时,用 setTag()方法为每个 View 绑定一个存放控件的 ViewHolder 对象。当 convertView 不为空,重复利用已经创建的 view 的时候, 使用 getTag()方法获取绑定的 ViewHolder 对象,这样就避免了 findViewById 
对控件的层层查询,而是快速定位到控件。

  1. 复用 ConvertView,使用历史的 view,提升效率 200%
  2. 自定义静态类 ViewHolder,减少 findViewById 的次数。提 升效率 50%
  3. 异步加载数据,分页加载数据。
  4. 使用 WeakRefrence 引用 ImageView 对象

ListView 中加载优化图片

ListView 图片的优化策略比较多。

  1. 处理图片的方式: 
    如果 ListView 中自定义的 Item 中有涉及到大量图片的,一定要对图片进行 
    细心的处理,因为图片占的内存是 ListView 项中最头疼的,处理图片的方法大 致有以下几种:

    1. 不要直接拿路径就去循环 BitmapFactory.decodeFile;使用 Options 保 存图片大小、不要加载图片到内存去。
    2. 对图片一定要经过边界压缩尤其是比较大的图片,如果你的图片是后台 服务器处理好的那就不需要了
    3. 在 ListView 中取图片时也不要直接拿个路径去取图片,而是以 WeakReference(使用 WeakReference 代替强引用。比如可以使用 WeakReference mContextRef)、SoftReference、WeakHashMap 等的来存 储图片信息。
    4. 在 getView 中做图片转换时,产生的中间变量一定及时释放
异步加载图片基本思想: 
1). 先从内存缓存中获取图片显示(内存缓冲) 
2). 获取不到的话从 SD 卡里获取(SD 卡缓冲) 
3). 都获取不到的话从网络下载图片并保存到 SD 卡同时加入内存并显示(视情况看是否要显示)

Listview 的替代方案

RecyclerView

RecyclerView是一个比ListView更灵活的一个控件,以后可以直接抛弃ListView了,也可以替代Gridview。 
RecyclerView改善了ListView的编程接口,他其实是一个ViewGroup ,能配合任何基于adapter的view实现多种布局

更好的实现局部刷新 ,调用notifyItemChanged(position)即可

Listview 原理

首先要理解Listview 三个重要的成员,用MVC思路来理解: 
* ListVeiw: 用来展示列表的View (V) 
* Adapter : 用来把数据映射到ListView上,(C) 
* 数据:item显示的数据(M)

ListView 针对每个item,要求 adapter “返回一个视图” (getView),也就是说ListView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到ListView的长度,然后根据这个长度,调用getView()一行一行的绘制ListView的每一项。如果你的getCount()返回值是0的话,列表一行都不会显示,如果返回1,就只显示一行.如果我们有几千几万甚至更多的item要显示怎么办?为每个Item创建一个新的View?不可能!!!实际上Android早已经缓存了这些视图.

有哪些开源的ListView控件?

这里主要列出几个比较常见的:



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值