热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android性能优化摘录

目录一、View的过度绘制(OverDraw)二、View的绘制流程三、三种常用布局的比较四、RecyclerViewVSListView之View层级关系五、高效布局标签六、去掉

目录
一、View的过度绘制(OverDraw)
二、View的绘制流程
三、三种常用布局的比较
四、RecyclerView VS ListView 之View层级关系
五、高效布局标签
六、去掉window的背景
七、去掉其他不必要的背景
八、ClipRect & QuickReject
九、善用draw9patch
十、慎用Alpha
十一、应该早点知道的API
十二、其他

本文是有心课堂-性能优化合辑 视频的学习笔记,也翻阅过网上的相关资料,整理了个人认为比较重要的知识点。

一、View的过度绘制(OverDraw)

OverDraw,是指在一帧的时间内(16.67ms)像素被绘制了多次,理论上一个像素只绘制一次是最优的,但由于重叠的布局导致一些像素被重复绘制多次,而每次绘制都会对应到CPU的一组绘图命令和GPU的一些操作,当这个操作超过16.67ms时就会出现掉帧的现象,即我们常说的卡顿,所以对重叠不可见元素的重复绘制会产生额外的开销,我们需要尽量减少OverDraw的发生。

Android提供了测量OverDraw的选项,在开发者选项->调试GPU过度绘制(Show GPU OverDraw),打开该选项就可以看到当前页面OverDraw的状态。

  • 没有颜色 : 没有OverDraw。像素只画了一次。
  • 蓝色:OverDraw 1倍。像素绘制了两次,大片的蓝色是可以接受的(若整个窗口都是蓝色,可以摆脱一层)。
  • 绿色:OverDraw 2倍。像素绘制了三次,中等大小的绿色区域是可以接受的,但你应该尝试去优化、减少它们。
  • 浅红:OverDraw 3倍。像素绘制了四次,小范围可接受。
  • 暗红:OverDraw 4倍。像素绘制了五次或更多,这是错误的,要修复它们。

《Android性能优化摘录》 GPU-OverDraw

二、View的绘制流程

  • measure :为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身的视图决定的。
  • layout:为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
  • draw:利用前两步得到的参数,将视图显示在屏幕上。

三、三种常用布局的比较

RelativeLayout:

优点:
1. View树扁平化,减少View层级
2. 使用场景广
缺点:
1. 测量效率稍差
2. 使用略复杂

LinearLayout:

优点:
1. 使用非常简单
2. 测量效率高
缺点:
1. 嵌套过多,易导致View层级复杂
2. 使用场景相对较窄

FrameLayout:

使用场景特殊,有些场景下可以替代RelativeLayout

选择布局容器的基本准则:

  • 尽可能的使用RelativeLayout以减少View层级,使View树趋于扁平化
  • 在不影响层级深度的情况下,使用LinearLayout和FrameLayout,而不是RelativeLayout

四、RecyclerView VS ListView 之View层级关系

| --- RecyclerView
ViewGroup----| |--- ListView
| --- AdapterView --- AbsListView ---|
|--- GridView

从View层级关系,我们可以看出,在实际开发中更推荐使用RecyclerView。

五、高效布局标签

  • Merge标签:减少视图的层级结构。

Merage标签一般配合FrameLayout和LinearLayout使用,当然RelativeLayout也可以用Merge标签,只是RelativeLayout本身比较复杂,如果我们也通过Merge标签去添加一些子View的时候很容易弄巧成拙。

另外Merge标签只能作为XML布局的根标签使用,当Inflate以开头的布局文件时,必须指定一个父ViewGroup,并且必须设定attachToRoot为true。

  • ViewStub标签:高效占位符。

我们经常会遇到这的情况,运行时动态根据条件来决定显示哪个View或者布局。常用的做法是把View都写在上面,先把他们的可见性都设为View.GONE,然后在代码中动态的更改它的可见性。这样的做法的优点是逻辑简单而且控制起来比较灵活。但是它的缺点就是耗费资源。虽然把View的初始化可见View.GONE,但是在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性,也就是说会耗费内存等资源。

ViewStub是一个轻量级的View,它是一个看不见的,不占布局位置,占用资源非常小的控件,可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候或是调用了ViewStub.inflate()的时候,ViewSub所指向的布局会被inflateh和实例化,然后ViewStub的布局属性都会传给它所指向的布局。

android:id="@+id/stub_view"
android:inflatedId="@+id/panel_stub"
android:layout="@layout/progress_overlay"
android:layout_
android:layout_
android:layout_gravity="bottom" />

当想加载布局时,可以使用下面其中的一种方法:

((ViewStub) findViewById(R.id.stub_view)).setVisibility(View.VISIBLE);

View importPanel = ((ViewStub) findViewById(R.id.stub_view)).inflate();

  • Space标签:空白控件。

六、去掉window的背景

在我们使用了Android自带的一些主题时,window会被默认添加一个纯色的背景,这个背景是被DecorView持有的,当我们的自定义布局时又添加一张背景图或者设置背景色,那么DecorView的background此时对我们来说是无用的,但是它会产生一次OverDraw,带来绘制性能损耗。

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

getWindow().setBackgroundDrawable(null);

或者在theme中添加

android:windowbackground="null";

七、去掉其他不必要的背景

  • 有时候为了方便会先给Layout设置一个整体的背景,再给子View设置背景,会造成重叠,如果子View宽度match_parent,可以看到完全覆盖了Layout的一部分,这里就可以通过分别设置背景来减少重绘
  • 如果采用的是selector的背景,将normal状态的color设置为”@android:color/transparent”也同样可以解决问题。

这里只简单举两个例子,我们在开发过程中的一些习惯性的思维定式会带来不经意的OverDraw,所以开发过程中我们为某个View或者ViewGroup设置背景的时候,先思考下是否真的有必要,或者思考下这个背景能不能分段设置在子View上,而不是图方便直接设置在根View上。

八、ClipRect & QuickReject

为了解决OverDraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗,但不幸的是,对于那些过于复杂的自定义View(通常重写了OnDraw方法),Android系统无法检测在OnDraw里面具体会执行什么操作,系统无法监控并自动优化,也就无法避免OverDraw了。但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域,这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视,这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域,同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,任然会得到绘制。除了clipRect方法之外,我们还可以使用canvas.quickReject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。

九、善用draw9patch

给ImageView加一个边框,遇到这种需求,通常在ImageView后面设置一张背景图,露出边框便完美解决问题,此时这个ImageView,设置了两次drawable,底下一层仅仅是为了作为图片的边框而已,但是两层drawable的重叠区域却绘制了两次,导致OverDraw。

优化方案:将背景drawable制作成draw9patch,并且将和前景重叠的部分设置为透明,由于Android的2D渲染器会优化draw9patch中的透明区域,从而优化了这次OverDraw。但是背景图片必须制作成draw9patch才行,因为Android 2D渲染器只对draw9patch有这个优化,否则,一张普通的png,就算你把中间的部分设置成透明,也不会减少这次OverDraw.

十、慎用Alpha

假如对一个View做Alpha转化,需要先将View绘制出来,然后做Alpha转化,最后将转换的效果在界面上。通俗得说,做Alpha转化就需要对当前View绘制两遍,可想而知,绘制效率会的大打折扣,耗时会翻倍,所以Alpha还是慎用。如果一定要做Alpha转化的话,可以采用缓存的方式。

view.setLayerType(LAYER_TYPE_HARDWARE);
doSmoeThing();
view.setLayerType(LAYER_TYPE_NONE);

通过setLayerType方式可以将当前界面缓存在GPU中,这样不需要每次绘制原始界面,但是GPU内存是相当宝贵的,所以用完要马上释放掉。

十一、应该早点知道的API

  1. android:lineSpacingExtra

    设置行间距,如“3dp”

  2. android:lineSpacingMultiPlier

    设置行间距的倍数,如“1.2”

  3. android:includeFOntPadding=”false”

    TextView默认上下是有一定的padding的,有时候我们可能不需要上下这部分留白,加上它即可

  4. android:titleMode(BitmapDrawable)

    可以指定图片使用重复填充的模式

  5. android:fillViewport

    设置ScrollView撑满父容器

  6. ArgbEvaluator类

    实现丰富的色彩效果,提高体验度。譬如:滑动Viewpager时,背景色渐变;随着EditText输入框的长度变化背景色等等

十二、其他

  • 尽量避免过多的使用static变量
  • Avtivity和Activity之间或Fragment和Fragment之间使用Intent、Bundle传递数据
  • 常量提取到一个单独的类中,并注意命名规范。如Intent传值的key
  • 善用 Gradle

有时候我们的App会把HOST_URL、DEBUG等常量写在Constants中,这样我们在打正式包或测试包除了要修改代码外,studio还需要重新build一次,是比较耗时的,所以我们可以把一些常量的设置可以定义到build.gradle文件中


buildTypes {
debug {
buildConfigField("String", "HOST_URL", "\"http://api-test.ganxin.com\"")
buildConfigField("Boolean", "LOG_DEBUG", "true")
}
release {
buildConfigField("String", "HOST_URL", "\"http://api.ganxin.com\"")
buildConfigField("Boolean", "LOG_DEBUG", "false")
}
}

又或者在AndroidManifest文件中的元素在不同的场景下需要替换不同的值的,可以尝试用productFlavors,如 :


productFlavors {
productive{
manifestPlaceholders =[
LAUCHER_LOGO:"@mipmap/ic_logo_normal" ,
CHANNEL_NAME : "normal"
]
}
customA{
manifestPlaceholders =[
LAUCHER_LOGO:"@mipmap/ic_logo_custom" ,
CHANNEL_NAME : "customA"
]
}
}

在AndroidManifest文件中的引用方式:

android:icon="${LAUCHER_LOGO}"
android:name="UMENG_CHANNEL"
android:value="${CHANNEL_NAME}" />

  • 使用递归方法的时候避免造成OOM(内存溢出)
  • 注意使用Handler造成的内存泄漏

一段简单的使用Handler示例


Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}

a. 当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然怎么可能通过Handler来操作Activity中的View ?)。而Handler通常会伴随一个耗时的后台线程(例如网络拉取图片)一起出现,这个后台线程在任务执行完毕之后,通过消息机制通知Handler,然后Handler把图片更新到界面,然后用户在网络请求过程中关闭了Activity,在正常情况下,Activity不再被使用,它就可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄漏),直到网络请求结束。

b.另外,如果执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MesageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue->Message->Handler->Activity的链,导致你的Activity被引用而无法被回收。

解决方案:

a. 通过程序逻辑来进行保护

- 在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收
- 如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息对象移除就行
关于Handler.remove方法:
removeCallbacks(Runnable r) —— 清除r匹配上的Message
removeCallbacks(Runnable r, Object token) —— 清除r匹配且匹配token(Message.obj)的Message,token为空时,只匹配r
removeCallbacksAndMessage(Object token) —— 清除token匹配的Message
removeMessages(int what) —— 按what来匹配
removeMessages(int what,Object object) —— 按what来匹配
如果需要清除以Handler为target的所有Message(包括Callback),调用如下方法即可:
handler.removeCallbacksAndMessages(null);

b. 将Handler声明为静态类

静态类不持有外部类的对象,所以Activity可以随意回收。


static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}

但使用了以上代码后,会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity的对象了,所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):


static class MyHandler extends Handler {
WeakReference mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}

推荐阅读
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • “你永远都不知道明天和‘公司的意外’哪个先来。”疫情期间,这是我们最战战兢兢的心情。但是显然,有些人体会不了。这份行业数据,让笔者“柠檬” ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 2016 linux发行版排行_灵越7590 安装 linux (manjarognome)
    RT之前做了一次灵越7590黑苹果炒作业的文章,希望能够分享给更多不想折腾的人。kawauso:教你如何给灵越7590黑苹果抄作业​zhuanlan.z ... [详细]
author-avatar
陈应锋forever
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有