精华内容
下载资源
问答
  • 优化指导】2013高中地理总复习 课时作业43 山区农业资源的开发 商业农业区域开发练习 人教版
  • matlab开发-用于验证和优化打印机的平台区域控制器的竞争性心律疗法。我们提出了一种用于APBI中HDR间质近距离放射治疗的剂量验证和优化工具。
  • 优化指导】2013高考地理总复习 区域可持续发展 2-5 矿产资源合理开发区域可持续发展 以德国 鲁尔区为例 湘教版
  • 2017_2018学年高中地理第三单元区域资源环境与可持续发展第二节资源开发区域可持续发展__以德国鲁尔区为例优化练习鲁教版必修320180803315
  • 优化设计】2015-2016学年高中地理 第三章 区域自然资源综合开发利用单元测评 新人教版必修3
  • 开发眼中的一些前端交互优化

    千次阅读 2017-01-03 21:31:42
    一、移动web开发与PCweb开发有哪些不同?首先一点就是性能:手机端的性能,要求更高;M中有很多meta标签;V中的css有很多屏幕适配;C中的交互优化与性能优化;交互优化与性能优化的差别系统真的快于用户感觉很快,是...

    一、移动web开发与PCweb开发有哪些不同?

    首先一点就是性能:手机端的性能,要求更高;

    M中有很多meta标签;V中的css有很多屏幕适配;C中的交互优化与性能优化;

    交互优化与性能优化的差别

    系统真的快于用户感觉很快,是两码事;交互优化是更多的根据用户的实际感受来优化。

    1. 交互优化 – 点击事件

    如下场景:延时,卡顿;

    根本原因:默认点击有300ms延迟:有很多pc网页没有做移动端分辨率兼容,需要放大缩小来浏览,则简单暴力用双击300ms以内来决定放大缩小。所以单击的时候会有延迟

    移动web优化之tap事件替代click

    移动web优化之添加点击状态

    2. 交互优化 – 滚动的交互优化

    基本概念:全局滚动与局部滚动

    在ios系统中,全局滚动是默认支持的,局部滚动:默认没有滚动条,而且滚动起来比较干涩;

    如何让局部滚动具有全局滚动这么流畅的效果呢?

    子节点可以继承这个属性;

    那么在Android上呢?

    出界的情况

    在滚动的时候,滚到边界之后,继续滚动,会有黑边出来;

    如何解决出界的问题呢?

    在iOS中,可以使用ScrollFix组件来结局,其核心的代码如下;

    对于局部滚动,页面上有一些固定的导航区域,这时候可以禁止他的touchmove默认事件;

    在全局滚动,怎么样才能阻止出界呢?

    在android中呢?使用局部滚动,有时候是的滚动条会断裂;

    比如一些比较常见的布局,如何实现全局滚动呢?举个例子:

    通过上下的设置padding值,这样就可以使用全局滚动了;

    总结一下

    3. 交互优化 – 键盘定制

    比如我们再有一些键盘输入时,系统怎么知道我们是要数字还是英文,中文呢?如下图所示

    这时候,我们就可以根据业务的需求,定制自己的键盘模式

    1. 英文键盘

    2. 数字键盘

    两种不同的数字键盘,还有一种高级的配置键盘方式:

    3. 设置搜索框键盘

    为什么还要用form表单包裹起来呢?这是因为键盘上有一个搜索按钮,当用户点击之后,我们可以处理点击的一些事件;

    4. 定制键盘的行为

    去掉一些首字母大写的情况;比如用户名

    去掉一些多余的纠错行为

    兼容性:样式与行为;ios基本都支持,但是android中,各种版本兼容性表现不一致,但是可以先使用,达到尖晶增强的效果;

    展开全文
  • 区域电子信息产业人才需求与高职教育专业结构优化研究——以武汉东湖新技术开发区为例.pdf
  • Android应用开发性能优化完全分析

    万次阅读 多人点赞 2015-10-11 02:20:58
    不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结、我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只给出啥啥啥不能用,啥啥啥该咋用等,却很少有较为系统的进行...

    1 背景

    其实有点不想写这篇文章的,但是又想写,有些矛盾。不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结、我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只给出啥啥啥不能用,啥啥啥该咋用等,却很少有较为系统的进行真正性能案例分析的,大多数都是嘴上喊喊或者死记住规则而已(当然了,这话我自己听着都有些刺耳,实在不好意思,其实关于性能优化的优质博文网上也还是有很多的,譬如Google官方都已经推出了优化专题,我这里只是总结下自的感悟而已,若有得罪欢迎拍砖,我愿挨打,因为我之前工作的一半时间都是负责性能优化)。

    当然了,本文不会就此编辑这么一次,因为技术在发展,工具在强大(写着写着Android Studio 1.4版本都推送了),自己的经验也在增加,所以本文自然不会覆盖所有性能优化及分析;解决的办法就是该文章会长期维护更新,同时在评论区欢迎你关于性能优化点子的探讨。

    Android应用的性能问题其实可以划分为几个大的模块的,而且都具有相对不错的优化调试技巧,下面我们就会依据一个项目常规开发的大类型来进行一些分析讲解。

    PS:之前呆过一家初创医疗互联网公司,别提性能优化了,老板立完新项目后一个月就要求见到上线成品,这种压迫下谈何性能优化,纯属扯蛋,所以不到三个月时间我主动选择撤了,这种现象后来我一打听发现在很多初创公司都很严重,都想速成却忽略了体验。

    PPPS:本文只是达到抛砖引玉的作用,很多东西细究下去都是值得深入研究的,再加上性能优化本来就是一个需要综合考量的任务,不是说会了本文哪一点就能做性能分析了,需要面面俱到才可高效定位问题原因。

    这里写图片描述

    【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

    2 应用UI性能问题分析

    UI可谓是一个应用的脸,所以每一款应用在开发阶段我们的交互、视觉、动画工程师都拼命的想让它变得自然大方美丽,可是现实总是不尽人意,动画和交互总会觉得开发做出来的应用用上去感觉不自然,没有达到他们心目中的自然流畅细节;这种情况之下就更别提发布给终端用户使用了,用户要是能够感觉出来,少则影响心情,多则卸载应用;所以一个应用的UI显示性能问题就不得不被开发人员重视。

    2-1 应用UI卡顿原理

    人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这种流畅的帧率规定为60fps。

    有了上面的背景,我们开发App的帧率性能目标就是保持在60fps,也就是说我们在进行App性能优化时心中要有如下准则:

    换算关系:60帧/秒-----------16ms/帧;
    
    准则:尽量保证每次在16ms内处理完所有的CPU与GPU计算、绘制、渲染等操作,否则会造成丢帧卡顿问题。

    从上面可以看出来,所谓的卡顿其实是可以量化的,每次是否能够成功渲染是非常重要的问题,16ms能否完整的做完一次操作直接决定了卡顿性能问题。

    当然了,针对Android系统的设计我们还需要知道另一个常识;虚拟机在执行GC垃圾回收操作时所有线程(包括UI线程)都需要暂停,当GC垃圾回收完成之后所有线程才能够继续执行(这个细节下面小节会有详细介绍)。也就是说当在16ms内进行渲染等操作时如果刚好遇上大量GC操作则会导致渲染时间明显不足,也就从而导致了丢帧卡顿问题。

    有了上面这两个简单的理论基础之后我们下面就会探讨一些UI卡顿的原因分析及解决方案。

    2-2 应用UI卡顿常见原因

    我们在使用App时会发现有些界面启动卡顿、动画不流畅、列表等滑动时也会卡顿,究其原因,很多都是丢帧导致的;通过上面卡顿原理的简单说明我们从应用开发的角度往回推理可以得出常见卡顿原因,如下:

    1. 人为在UI线程中做轻微耗时操作,导致UI线程卡顿;

    2. 布局Layout过于复杂,无法在16ms内完成渲染;

    3. 同一时间动画执行的次数过多,导致CPU或GPU负载过重;

    4. View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;

    5. View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;

    6. 内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;

    7. 冗余资源及逻辑等导致加载和执行缓慢;

    8. 臭名昭著的ANR;

    可以看见,上面这些导致卡顿的原因都是我们平时开发中非常常见的。有些人可能会觉得自己的应用用着还蛮OK的,其实那是因为你没进行一些瞬时测试和压力测试,一旦在这种环境下运行你的App你就会发现很多性能问题。

    2-3 应用UI卡顿分析解决方法

    分析UI卡顿我们一般都借助工具,通过工具一般都可以直观的分析出问题原因,从而反推寻求优化方案,具体如下细说各种强大的工具。

    2-3-1 使用HierarchyViewer分析UI性能

    我们可以通过SDK提供的工具HierarchyViewer来进行UI布局复杂程度及冗余等分析,如下:

    xxx@ThinkPad:~$ hierarchyviewer   //通过命令启动HierarchyViewer

    选中一个Window界面item,然后点击右上方Hierarchy window或者Pixel Perfect window(这里不介绍,主要用来检查像素属性的)即可操作。

    先看下Hierarchy window,如下:

    这里写图片描述

    一个Activity的View树,通过这个树可以分析出View嵌套的冗余层级,左下角可以输入View的id直接自动跳转到中间显示;Save as PNG用来把左侧树保存为一张图片;Capture Layers用来保存psd的PhotoShop分层素材;右侧剧中显示选中View的当前属性状态;右下角显示当前View在Activity中的位置等;左下角三个进行切换;Load View Hierarchy用来手动刷新变化(不会自动刷新的)。当我们选择一个View后会如下图所示:

    这里写图片描述

    类似上图可以很方便的查看到当前View的许多信息;上图最底那三个彩色原点代表了当前View的性能指标,从左到右依次代表测量、布局、绘制的渲染时间,红色和黄色的点代表速度渲染较慢的View(当然了,有些时候较慢不代表有问题,譬如ViewGroup子节点越多、结构越复杂,性能就越差)。

    当然了,在自定义View的性能调试时,HierarchyViewer上面的invalidate Layout和requestLayout按钮的功能更加强大,它可以帮助我们debug自定义View执行invalidate()和requestLayout()过程,我们只需要在代码的相关地方打上断点就行了,接下来通过它观察绘制即可。

    可以发现,有了HierarchyViewer调试工具,我们的UI性能分析变得十分容易,这个工具也是我们开发中调试UI的利器,在平时写代码时会时常伴随我们左右。

    2-3-2 使用GPU过度绘制分析UI性能

    我们对于UI性能的优化还可以通过开发者选项中的GPU过度绘制工具来进行分析。在设置->开发者选项->调试GPU过度绘制(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面过度绘制进行分析):

    这里写图片描述

    可以发现,开启后在我们想要调试的应用界面中可以看到各种颜色的区域,具体含义如下:

    颜色含义
    无色WebView等的渲染区域
    蓝色1x过度绘制
    绿色2x过度绘制
    淡红色3x过度绘制
    红色4x(+)过度绘制


    由于过度绘制指在屏幕的一个像素上绘制多次(譬如一个设置了背景色的TextView就会被绘制两次,一次背景一次文本;这里需要强调的是Activity设置的Theme主题的背景不被算在过度绘制层级中),所以最理想的就是绘制一次,也就是蓝色(当然这在很多绚丽的界面是不现实的,所以大家有个度即可,我们的开发性能优化标准要求最极端界面下红色区域不能长期持续超过屏幕三分之一,可见还是比较宽松的规定),因此我们需要依据此颜色分布进行代码优化,譬如优化布局层级、减少没必要的背景、暂时不显示的View设置为GONE而不是INVISIBLE、自定义View的onDraw方法设置canvas.clipRect()指定绘制区域或通过canvas.quickreject()减少绘制区域等。

    2-3-3 使用GPU呈现模式图及FPS考核UI性能

    Android界面流畅度除过视觉感知以外是可以考核的(测试妹子专用),常见的方法就是通过GPU呈现模式图或者实时FPS显示进行考核,这里我们主要针对GPU呈现模式图进行下说明,因为FPS考核测试方法有很多(譬如自己写代码实现、第三方App测试、固件支持等),所以不做统一说明。

    通过开发者选项中GPU呈现模式图工具来进行流畅度考量的流程是(注意:如果是在开启应用后才开启此功能,记得先把应用结束后重新启动)在设置->开发者选项->GPU呈现模式(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面上下滑动列表后的图表):

    这里写图片描述

    当然,也可以在执行完UI滑动操作后在命令行输入如下命令查看命令行打印的GPU渲染数据(分析依据:Draw + Process + Execute = 完整的显示一帧时间 < 16ms):

    adb shell dumpsys gfxinfo [应用包名]

    打开上图可视化工具后,我们可以在手机画面上看到丰富的GPU绘制图形信息,分别展示了StatusBar、NavgationBar、Activity区域等的GPU渲染时间信息,随着界面的刷新,界面上会以实时柱状图来显示每帧的渲染时间,柱状图越高表示渲染时间越长,每个柱状图偏上都有一根代表16ms基准的绿色横线,每一条竖着的柱状线都包含三部分(蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间),只要我们每一帧的总时间低于基准线就不会发生UI卡顿问题(个别超出基准线其实也不算啥问题的)。

    可以发现,这个工具是有局限性的,他虽然能够看出来有帧耗时超过基准线导致了丢帧卡顿,但却分析不到造成丢帧的具体原因。所以说为了配合解决分析UI丢帧卡顿问题我们还需要借助traceview和systrace来进行原因追踪,下面我们会介绍这两种工具的。

    2-3-4 使用Lint进行资源及冗余UI布局等优化

    上面说了,冗余资源及逻辑等也可能会导致加载和执行缓慢,所以我们就来看看Lint这个工具是如何发现优化这些问题的(当然了,Lint实际的功能是非常强大的,我们开发中也是经常使用它来发现一些问题的,这里主要有点针对UI性能的说明了,其他的雷同)。

    在Android Studio 1.4版本中使用Lint最简单的办法就是将鼠标放在代码区点击右键->Analyze->Inspect Code–>界面选择你要检测的模块->点击确认开始检测,等待一下后会发现如下结果:

    这里写图片描述

    可以看见,Lint检测完后给了我们很多建议的,我们重点看一个关于UI性能的检测结果;上图中高亮的那一行明确说明了存在冗余的UI层级嵌套,所以我们是可以点击跳进去进行优化处理掉的。

    当然了,Lint还有很多功能,大家可以自行探索发挥,这里只是达到抛砖引玉的作用。

    2-3-5 使用Memory监测及GC打印与Allocation Tracker进行UI卡顿分析

    关于Android的内存管理机制下面的一节会详细介绍,这里我们主要针对GC导致的UI卡顿问题进行详细说明。

    Android系统会依据内存中不同的内存数据类型分别执行不同的GC操作,常见应用开发中导致GC频繁执行的原因主要可能是因为短时间内有大量频繁的对象创建与释放操作,也就是俗称的内存抖动现象,或者短时间内已经存在大量内存暂用介于阈值边缘,接着每当有新对象创建时都会导致超越阈值触发GC操作。

    如下是我工作中一个项目的一次经历(我将代码回退特意抓取的),出现这个问题的场景是一次压力测试导致整个系统卡顿,瞬间杀掉应用就OK了,究其原因最终查到是一个API的调运位置写错了方式,导致一直被狂调,当普通使用时不会有问题,压力测试必现卡顿。具体内存参考图如下:
    这里写图片描述
    与此抖动图对应的LogCat抓取如下:

    //截取其中比较密集一段LogCat,与上图Memory检测到的抖动图对应,其中xxx为应用包名
    ......
    10-06 00:59:45.619 xxx I/art: Explicit concurrent mark sweep GC freed 72515(3MB) AllocSpace objects, 65(2028KB) LOS objects, 80% free, 17MB/89MB, paused 3.505ms total 60.958ms
    10-06 00:59:45.749 xxx I/art: Explicit concurrent mark sweep GC freed 5396(193KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.079ms total 100.522ms
    ......
    10-06 00:59:48.059 xxx I/art: Explicit concurrent mark sweep GC freed 4693(172KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.227ms total 101.692ms
    ......

    我们知道,类似上面logcat打印一样,触发垃圾回收的主要原因有以下几种:

    • GC_MALLOC——内存分配失败时触发;

    • GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发;

    • GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ;

    • GC_EXTERNAL_ALLOC——外部内存分配失败时触发;

    可以看见,这种不停的大面积打印GC导致所有线程暂停的操作必定会导致UI视觉的卡顿,所以我们要避免此类问题的出现,具体的常见优化方式如下:

    • 检查代码,尽量避免有些频繁触发的逻辑方法中存在大量对象分配;
    • 尽量避免在多次for循环中频繁分配对象;
    • 避免在自定义View的onDraw()方法中执行复杂的操作及创建对象(譬如Paint的实例化操作不要写在onDraw()方法中等);
    • 对于并发下载等类似逻辑的实现尽量避免多次创建线程对象,而是交给线程池处理。

    当然了,有了上面说明GC导致的性能后我们就该定位分析问题了,可以通过运行DDMS->Allocation Tracker标签打开一个新窗口,然后点击Start Tracing按钮,接着运行你想分析的代码,运行完毕后点击Get Allocations按钮就能够看见一个已分配对象的列表,如下:

    这里写图片描述

    点击上面第一个表格中的任何一项就能够在第二个表格中看见导致该内存分配的栈信息,通过这个工具我们可以很方便的知道代码分配了哪类对象、在哪个线程、哪个类、哪个文件的哪一行。譬如我们可以通过Allocation Tracker分别做一次Paint对象实例化在onDraw与构造方法的一个自定义View的内存跟踪,然后你就明白这个工具的强大了。

    PS一句,Android Studio新版本除过DDMS以外在Memory视图的左侧已经集成了Allocation Tracker功能,只是用起来还是没有DDMS的方便实用,如下图:

    这里写图片描述

    2-3-6 使用Traceview和dmtracedump进行分析优化

    关于UI卡顿问题我们还可以通过运行Traceview工具进行分析,他是一个分析器,记录了应用程序中每个函数的执行时间;我们可以打开DDMS然后选择一个进程,接着点击上面的“Start Method Profiling”按钮(红色小点变为黑色即开始运行),然后操作我们的卡顿UI(小范围测试,所以操作最好不要超过5s),完事再点一下刚才按的那个按钮,稍等片刻即可出现下图,如下:

    这里写图片描述

    花花绿绿的一幅图我们怎么分析呢?下面我们解释下如何通过该工具定位问题:

    整个界面包括上下两部分,上面是你测试的进程中每个线程运行的时间线,下面是每个方法(包含parent及child)执行的各个指标的值。通过上图的时间面板可以直观发现,整个trace时间段main线程做的事情特别多,其他的做的相对较少。当我们选择上面的一个线程后可以发现下面的性能面板很复杂,其实这才是TraceView的核心图表,它主要展示了线程中各个方法的调用信息(CPU使用时间、调用次数等),这些信息就是我们分析UI性能卡顿的核心关注点,所以我们先看几个重要的属性说明,如下:

    属性名含义
    name线程中调运的方法名;
    Incl CPU Time当前方法(包含内部调运的子方法)执行占用的CPU时间;
    Excl CPU Time当前方法(不包含内部调运的子方法)执行占用的CPU时间;
    Incl Real Time当前方法(包含内部调运的子方法)执行的真实时间,ms单位;
    Excl Real Time当前方法(不包含内部调运的子方法)执行的真实时间,ms单位;
    Calls+Recur Calls/Total当前方法被调运的次数及递归调运占总调运次数百分比;
    CPU Time/Call当前方法调运CPU时间与调运次数比,即当前方法平均执行CPU耗时时间;
    Real Time/Call当前方法调运真实时间与调运次数比,即当前方法平均执行真实耗时时间;(重点关注)


    有了对上面Traceview图表的一个认识之后我们就来看看具体导致UI性能后该如何切入分析,一般Traceview可以定位两类性能问题:

    • 方法调运一次需要耗费很长时间导致卡顿;

    • 方法调运一次耗时不长,但被频繁调运导致累计时长卡顿。

    譬如我们来举个实例,有时候我们写完App在使用时不觉得有啥大的影响,但是当我们启动完App后静止在那却十分费电或者导致设备发热,这种情况我们就可以打开Traceview然后按照Cpu Time/Call或者Real Time/Call进行降序排列,然后打开可疑的方法及其child进行分析查看,然后再回到代码定位检查逻辑优化即可;当然了,我们也可以通过该工具来trace我们自定义View的一些方法来权衡性能问题,这里不再一一列举喽。

    可以看见,Traceview能够帮助我们分析程序性能,已经很方便了,然而Traceview家族还有一个更加直观强大的小工具,那就是可以通过dmtracedump生成方法调用图。具体做法如下:

    dmtracedump -g result.png target.trace  //结果png文件  目标trace文件

    通过这个生成的方法调运图我们可以更加直观的发现一些方法的调运异常现象。不过本人优化到现在还没怎么用到它,每次用到Traceview分析就已经搞定问题了,所以说dmtracedump自己酌情使用吧。

    PS一句,Android Studio新版本除过DDMS以外在CPU视图的左侧已经集成了Traceview(start Method Tracing)功能,只是用起来还是没有DDMS的方便实用(这里有一篇AS MT个人觉得不错的分析文章(引用自网络,链接属于原作者功劳)),如下图:

    这里写图片描述

    2-3-7 使用Systrace进行分析优化

    Systrace其实有些类似Traceview,它是对整个系统进行分析(同一时间轴包含应用及SurfaceFlinger、WindowManagerService等模块、服务运行信息),不过这个工具需要你的设备内核支持trace(命令行检查/sys/kernel/debug/tracing)且设备是eng或userdebug版本才可以,所以使用前麻烦自己确认一下。

    我们在分析UI性能时一般只关注图形性能(所以必须选择Graphics和View,其他随意),同时一般对于卡顿的抓取都是5s,最多10s。启动Systrace进行数据抓取可以通过两种方式,命令行方式如下:

    python systrace.py --time=10 -o mynewtrace.html sched gfx view wm

    图形模式:
    打开DDMS->Capture system wide trace using Android systrace->设置时间与选项点击OK就开始了抓取,接着操作APP,完事生成一个trace.html文件,用Chrome打开即可如下图:

    这里写图片描述
    在Chrome中浏览分析该文件我们可以通过键盘的W-A-S-D键来搞定,由于上面我们在进行trace时选择了一些选项,所以上图生成了左上方相关的CPU频率、负载、状态等信息,其中的CPU N代表了CPU核数,每个CPU行的柱状图表代表了当前时间段当前核上的运行信息;下面我们再来看看SurfaceFlinger的解释,如下:

    这里写图片描述

    可以看见上面左边栏的SurfaceFlinger其实就是负责绘制Android程序UI的服务,所以SurfaceFlinger能反应出整体绘制情况,可以关注上图VSYNC-app一行可以发现前5s多基本都能够达到16ms刷新间隔,5s多开始到7s多大于了15ms,说明此时存在绘制丢帧卡顿;同时可以发现surfaceflinger一行明显存在类似不规律间隔,这是因为有的地方是不需要重新渲染UI,所以有大范围不规律,有的是因为阻塞导致不规律,明显可以发现0到4s间大多是不需要渲染,而5s以后大多是阻塞导致;对应这个时间点我们放大可以看到每个部分所使用的时间和正在执行的任务,具体如下:
    这里写图片描述

    可以发现具体的执行明显存在超时性能卡顿(原点不是绿色的基本都代表存在一定问题,下面和右侧都会提示你选择的帧相关详细信息或者alert信息),但是遗憾的是通过Systrace只能大体上发现是否存在性能问题,具体问题还需要通过Traceview或者代码中嵌入Trace工具类等去继续详细分析,总之很蛋疼。

    PS:如果你想使用Systrace很轻松的分析定位所有问题,看明白所有的行含义,你还需要具备非常扎实的Android系统框架的原理才可以将该工具使用的得心应手。

    2-3-8 使用traces.txt文件进行ANR分析优化

    ANR(Application Not Responding)是Android中AMS与WMS监测应用响应超时的表现;之所以把臭名昭著的ANR单独作为UI性能卡顿的分析来说明是因为ANR是直接卡死UI不动且必须要解掉的Bug,我们必须尽量在开发时避免他的出现,当然了,万一出现了那就用下面介绍的方法来分析吧。

    我们应用开发中常见的ANR主要有如下几类:

    • 按键触摸事件派发超时ANR,一般阈值为5s(设置中开启ANR弹窗,默认有事件派发才会触发弹框ANR);

    • 广播阻塞ANR,一般阈值为10s(设置中开启ANR弹窗,默认不弹框,只有log提示);

    • 服务超时ANR,一般阈值为20s(设置中开启ANR弹窗,默认不弹框,只有log提示);

    当ANR发生时除过logcat可以看见的log以外我们还可以在系统指定目录下找到traces文件或dropbox文件进行分析,发生ANR后我们可以通过如下命令得到ANR trace文件:

    adb pull /data/anr/traces.txt ./

    然后我们用txt编辑器打开可以发现如下结构分析:

    //显示进程id、ANR发生时间点、ANR发生进程包名
    ----- pid 19073 at 2015-10-08 17:24:38 -----
    Cmd line: com.example.yanbo.myapplication
    //一些GC等object信息,通常可以忽略
    ......
    //ANR方法堆栈打印信息!重点!
    DALVIK THREADS (18):
    "main" prio=5 tid=1 Sleeping
      | group="main" sCount=1 dsCount=0 obj=0x7497dfb8 self=0x7f9d09a000
      | sysTid=19073 nice=0 cgrp=default sched=0/0 handle=0x7fa106c0a8
      | state=S schedstat=( 125271779 68162762 280 ) utm=11 stm=1 core=0 HZ=100
      | stack=0x7fe90d3000-0x7fe90d5000 stackSize=8MB
      | held mutexes=
      at java.lang.Thread.sleep!(Native method)
      - sleeping on <0x0a2ae345> (a java.lang.Object)
      at java.lang.Thread.sleep(Thread.java:1031)
      - locked <0x0a2ae345> (a java.lang.Object)
    //真正导致ANR的问题点,可以发现是onClick中有sleep导致。我们平时可以类比分析即可,这里不详细说明。
      at java.lang.Thread.sleep(Thread.java:985)
      at com.example.yanbo.myapplication.MainActivity$1.onClick(MainActivity.java:21)
      at android.view.View.performClick(View.java:4908)
      at android.view.View$PerformClick.run(View.java:20389)
      at android.os.Handler.handleCallback(Handler.java:815)
      at android.os.Handler.dispatchMessage(Handler.java:104)
      at android.os.Looper.loop(Looper.java:194)
      at android.app.ActivityThread.main(ActivityThread.java:5743)
      at java.lang.reflect.Method.invoke!(Native method)
      at java.lang.reflect.Method.invoke(Method.java:372)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:988)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
    ......
    //省略一些不常关注堆栈打印
    ......

    至此常见的应用开发中ANR分析定位就可以解决了。

    2-4 应用UI性能分析解决总结

    可以看见,关于Android UI卡顿的性能分析还是有很多工具的,上面只是介绍了应用开发中我们经常使用的一些而已,还有一些其他的,譬如Oprofile等工具不怎么常用,这里就不再详细介绍。

    通过上面UI性能的原理、原因、工具分析总结可以发现,我们在开发应用时一定要时刻重视性能问题,如若真的没留意出现了性能问题,不妨使用上面的一些案例方式进行分析。但是那终归是补救措施,在我们知道上面UI卡顿原理之后我们应该尽量从项目代码架构搭建及编写时就避免一些UI性能问题,具体项目中常见的注意事项如下:

    • 布局优化;尽量使用include、merge、ViewStub标签,尽量不存在冗余嵌套及过于复杂布局(譬如10层就会直接异常),尽量使用GONE替换INVISIBLE,使用weight后尽量将width和heigh设置为0dp减少运算,Item存在非常复杂的嵌套时考虑使用自定义Item View来取代,减少measure与layout次数等。

    • 列表及Adapter优化;尽量复用getView方法中的相关View,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新等。

    • 背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。

    • 自定义View等绘图与布局优化;尽量避免在draw、measure、layout中做过于耗时及耗内存操作,尤其是draw方法中,尽量减少draw、measure、layout等执行次数。

    • 避免ANR,不要在UI线程中做耗时操作,遵守ANR规避守则,譬如多次数据库操作等。

    当然了,上面只是列出了我们项目中常见的一些UI性能注意事项而已,相信还有很多其他的情况这里没有说到,欢迎补充。还有一点就是我们上面所谓的UI性能优化分析总结等都是建议性的,因为性能这个问题是一个涉及面很广很泛的问题,有些优化不是必需的,有些优化是必需的,有些优化掉以后又是得不偿失的,所以我们一般着手解决那些必须的就可以了。

    【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

    3 应用开发Memory内存性能分析优化

    说完了应用开发中的UI性能问题后我们就该来关注应用开发中的另一个重要、严重、非常重要的性能问题了,那就是内存性能优化分析。Android其实就是嵌入式设备,嵌入式设备核心关注点之一就是内存资源;有人说现在的设备都在堆硬件配置(譬如国产某米的某兔跑分手机、盒子等),所以内存不会再像以前那么紧张了,其实这句话听着没错,但为啥再牛逼配置的Android设备上有些应用还是越用系统越卡呢?这里面的原因有很多,不过相信有了这一章下面的内容分析,作为一个移动开发者的你就有能力打理好自己应用的那一亩三分地内存了,能做到这样就足以了。关于Android内存优化,这里有一篇Google的官方指导文档,但是本文为自己项目摸索,会有很多不一样的地方。

    3-1 Android内存管理原理

    系统级内存管理:

    Android系统内核是基于Linux,所以说Android的内存管理其实也是Linux的升级版而已。Linux在进程停止后就结束该进程,而Android把这些停止的进程都保留在内存中,直到系统需要更多内存时才选择性的释放一些,保留在内存中的进程默认(不包含后台service与Thread等单独UI线程的进程)不会影响整体系统的性能(速度与电量等)且当再次启动这些保留在内存的进程时可以明显提高启动速度,不需要再去加载。

    再直白点就是说Android系统级内存管理机制其实类似于Java的垃圾回收机制,这下明白了吧;在Android系统中框架会定义如下几类进程、在系统内存达到规定的不同level阈值时触发清空不同level的进程类型。

    这里写图片描述

    可以看见,所谓的我们的Service在后台跑着跑着挂了,或者盒子上有些大型游戏启动起来就挂(之前我在上家公司做盒子时遇见过),有一个直接的原因就是这个阈值定义的太大,导致系统一直认为已经达到阈值,所以进行优先清除了符合类型的进程。所以说,该阈值的设定是有一些讲究的,额,扯多了,我们主要是针对应用层内存分析的,系统级内存回收了解这些就基本够解释我们应用在设备上的一些表现特征了。

    应用级内存管理:

    在说应用级别内存管理原理时大家先想一个问题,假设有一个内存为1G的Android设备,上面运行了一个非常非常吃内存的应用,如果没有任何机制的情况下是不是用着用着整个设备会因为我们这个应用把1G内存吃光然后整个系统运行瘫痪呢?

    哈哈,其实Google的工程师才不会这么傻的把系统设计这么差劲。为了使系统不存在我们上面假想情况且能安全快速的运行,Android的框架使得每个应用程序都运行在单独的进程中(这些应用进程都是由Zygote进程孵化出来的,每个应用进程都对应自己唯一的虚拟机实例);如果应用在运行时再存在上面假想的情况,那么瘫痪的只会是自己的进程,不会直接影响系统运行及其他进程运行。

    既然每个Android应用程序都执行在自己的虚拟机中,那了解Java的一定明白,每个虚拟机必定会有堆内存阈值限制(值得一提的是这个阈值一般都由厂商依据硬件配置及设备特性自己设定,没有统一标准,可以为64M,也可以为128M等;它的配置是在Android的属性系统的/system/build.prop中配置dalvik.vm.heapsize=128m即可,若存在dalvik.vm.heapstartsize则表示初始申请大小),也即一个应用进程同时存在的对象必须小于阈值规定的内存大小才可以正常运行。

    接着我们运行的App在自己的虚拟机中内存管理基本就是遵循Java的内存管理机制了,系统在特定的情况下主动进行垃圾回收。但是要注意的一点就是在Android系统中执行垃圾回收(GC)操作时所有线程(包含UI线程)都必须暂停,等垃圾回收操作完成之后其他线程才能继续运行。这些GC垃圾回收一般都会有明显的log打印出回收类型,常见的如下:

    • GC_MALLOC——内存分配失败时触发;

    • GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发;

    • GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ;

    • GC_EXTERNAL_ALLOC——外部内存分配失败时触发;

    通过上面这几点的分析可以发现,应用的内存管理其实就是一个萝卜一个坑,坑都一般大,你在开发应用时要保证的是内存使用同一时刻不能超过坑的大小,否则就装不下了。

    3-2 Android内存泄露性能分析

    有了关于Android的一些内存认识,接着我们来看看关于Android应用开发中常出现的一种内存问题—-内存泄露。

    3-2-1 Android应用内存泄露概念

    众所周知,在Java中有些对象的生命周期是有限的,当它们完成了特定的逻辑后将会被垃圾回收;但是,如果在对象的生命周期本来该被垃圾回收时这个对象还被别的对象所持有引用,那就会导致内存泄漏;这样的后果就是随着我们的应用被长时间使用,他所占用的内存越来越大。如下就是一个最常见简单的泄露例子(其它的泄露不再一一列举了):

    public final class MainActivity extends Activity {
        private DbManager mDbManager;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_main);
    
            //DbManager是一个单例模式类,这样就持有了MainActivity引用,导致泄露
            mDbManager = DbManager.getInstance(this);
        }
    }

    可以看见,上面例子中我们让一个单例模式的对象持有了当前Activity的强引用,那在当前Acvitivy执行完onDestroy()后,这个Activity就无法得到垃圾回收,也就造成了内存泄露。

    内存泄露可以引发很多的问题,常见的内存泄露导致问题如下:

    • 应用卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC);

    • 应用被从后台进程干为空进程(上面系统内存原理有介绍,也就是超过了阈值);

    • 应用莫名的崩溃(上面应用内存原理有介绍,也就是超过了阈值OOM);

    造成内存泄露泄露的最核心原理就是一个对象持有了超过自己生命周期以外的对象强引用导致该对象无法被正常垃圾回收;可以发现,应用内存泄露是个相当棘手重要的问题,我们必须重视。

    3-2-2 Android应用内存泄露察觉手段

    知道了内存泄露的概念之后肯定就是想办法来确认自己的项目是否存在内存泄露了,那该如何察觉自己项目是否存在内存泄露呢?如下提供了几种常用的方式:

    察觉方式场景
    AS的Memory窗口平时用来直观了解自己应用的全局内存情况,大的泄露才能有感知。
    DDMS-Heap内存监测工具同上,大的泄露才能有感知。
    dumpsys meminfo命令常用方式,可以很直观的察觉一些泄露,但不全面且常规足够用。
    leakcanary神器比较强大,可以感知泄露且定位泄露;实质是MAT原理,只是更加自动化了,当现有代码量已经庞大成型,且无法很快察觉掌控全局代码时极力推荐;或者是偶现泄露的情况下极力推荐。

    AS的Memory窗口如下,详细的说明这里就不解释了,很简单很直观(使用频率高):

    这里写图片描述

    DDMS-Heap内存监测工具窗口如下,详细的说明这里就不解释了,很简单(使用频率不高):

    这里写图片描述

    dumpsys meminfo命令如下(使用频率非常高,非常高效,我的最爱之一,平时一般关注几个重要的Object个数即可判断一般的泄露;当然了,adb shell dumpsys meminfo不跟参数直接展示系统所有内存状态):

    这里写图片描述

    leakcanary神器使用这里先不说,下文会专题介绍,你会震撼的一B。有了这些工具的定位我们就能很方便的察觉我们App的内存泄露问题,察觉到以后该怎么定位分析呢,继续往下看。

    3-2-3 Android应用内存泄露leakcanary工具定位分析

    leakcanary是一个开源项目,一个内存泄露自动检测工具,是著名的GitHub开源组织Square贡献的,它的主要优势就在于自动化过早的发觉内存泄露、配置简单、抓取贴心,缺点在于还存在一些bug,不过正常使用百分之九十情况是OK的,其核心原理与MAT工具类似。

    关于leakcanary工具的配置使用方式这里不再详细介绍,因为真的很简单,详情点我参考官方教程学习使用即可

    PS:之前在优化性能时发现我们有一个应用有两个界面退出后Activity没有被回收(dumpsys meminfo发现一直在加),所以就怀疑可能存在内存泄露。但是问题来了,这两个Activity的逻辑十分复杂,代码也不是我写的,相关联的代码量也十分庞大,更加郁闷的是很难判断是哪个版本修改导致的,这时候只知道有泄露,却无法定位具体原因,使用MAT分析解决掉了一个可疑泄露后发现泄露又变成了概率性的。可以发现,对于这种概率性的泄露用MAT去主动抓取肯定是很耗时耗力的,所以决定直接引入leakcanary神器来检测项目,后来很快就彻底解决了项目中所有必现的、偶现的内存泄露。

    总之一点,工具再强大也只是帮我们定位可能的泄露点,而最核心的GC ROOT泄露信息推导出泄露问题及如何解决还是需要你把住代码逻辑及泄露核心概念去推理解决。

    3-2-4 Android应用内存泄露MAT工具定位分析

    Eclipse Memory Analysis Tools(点我下载)是一个专门分析Java堆数据内存引用的工具,我们可以使用它方便的定位内存泄露原因,核心任务就是找到GC ROOT位置即可,哎呀,关于这个工具的使用我是真的不想说了,自己搜索吧,实在简单、传统的不行了。

    PS:这是开发中使用频率非常高的一个工具之一,麻烦务必掌握其核心使用技巧,虽然Android Studio已经实现了部分功能,但是真的很难用,遇到问题目前还是使用Eclipse Memory Analysis Tools吧。

    原谅我该小节的放荡不羁!!!!(其实我是困了,呜呜!)

    3-2-5 Android应用开发规避内存泄露建议

    有了上面的原理及案例处理其实还不够,因为上面这些处理办法是补救的措施,我们正确的做法应该是在开发过程中就养成良好的习惯和敏锐的嗅觉才对,所以下面给出一些应用开发中常见的规避内存泄露建议:

    • Context使用不当造成内存泄露;不要对一个Activity Context保持长生命周期的引用(譬如上面概念部分给出的示例)。尽量在一切可以使用应用ApplicationContext代替Context的地方进行替换(原理我前面有一篇关于Context的文章有解释)。

    • 非静态内部类的静态实例容易造成内存泄漏;即一个类中如果你不能够控制它其中内部类的生命周期(譬如Activity中的一些特殊Handler等),则尽量使用静态类和弱引用来处理(譬如ViewRoot的实现)。

    • 警惕线程未终止造成的内存泄露;譬如在Activity中关联了一个生命周期超过Activity的Thread,在退出Activity时切记结束线程。一个典型的例子就是HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了Activity生命周期,我们必须手动在Activity的销毁方法中中调运thread.getLooper().quit();才不会泄露。

    • 对象的注册与反注册没有成对出现造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。

    • 创建与关闭没有成对出现造成的泄露;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。

    • 不要在执行频率很高的方法或者循环中创建对象,可以使用HashTable等创建一组对象容器从容器中取那些对象,而不用每次new与释放。

    • 避免代码设计模式的错误造成内存泄露。

    关于规避内存泄露上面我只是列出了我在项目中经常遇见的一些情况而已,肯定不全面,欢迎拍砖!当然了,只有我们做到好的规避加上强有力的判断嗅觉泄露才能让我们的应用驾驭好自己的一亩三分地。

    3-3 Android内存溢出OOM性能分析

    上面谈论了Android应用开发的内存泄露,下面谈谈内存溢出(OOM);其实可以认为内存溢出与内存泄露是交集关系,具体如下图:

    这里写图片描述

    下面我们就来看看内存溢出(OOM)相关的东东吧。

    3-3-1 Android应用内存溢出OOM概念

    上面我们探讨了Android内存管理和应用开发中的内存泄露问题,可以知道内存泄露一般影响就是导致应用卡顿,但是极端的影响是使应用挂掉。前面也提到过应用的内存分配是有一个阈值的,超过阈值就会出问题,这里我们就来看看这个问题—–内存溢出(OOM–OutOfMemoryError)。

    内存溢出的主要导致原因有如下几类:

    • 应用代码存在内存泄露,长时间积累无法释放导致OOM;

    • 应用的某些逻辑操作疯狂的消耗掉大量内存(譬如加载一张不经过处理的超大超高清图片等)导致超过阈值OOM;

    可以发现,无论哪种类型,导致内存溢出(OutOfMemoryError)的核心原因就是应用的内存超过阈值了。

    3-3-2 Android应用内存溢出OOM性能分析

    通过上面的OOM概念和那幅交集图可以发现,要想分析OOM原因和避免OOM需要分两种情况考虑,泄露导致的OOM,申请过大导致的OOM。

    内存泄露导致的OOM分析:

    这种OOM一旦发生后会在logcat中打印相关OutOfMemoryError的异常栈信息,不过你别高兴太早,这种情况下导致的OOM打印异常信息是没有太大作用,因为这种OOM的导致一般都如下图情况(图示为了说明问题数据和场景有夸张,请忽略):

    这里写图片描述

    从图片可以看见,这种OOM我们有时也遇到,第一反应是去分析OOM异常打印栈,可是后来发现打印栈打印的地方没有啥问题,没有可优化的余地了,于是就郁闷了。其实这时候你留心观察几个现象即可,如下:

    • 留意你执行触发OOM操作前的界面是否有卡顿或者比较密集的GC打印;
    • 使用命令查看下当前应用占用内存情况;

    确认了以上这些现象你基本可以断定该OOM的log真的没用,真正导致问题的原因是内存泄露,所以我们应该按照上节介绍的方式去着手排查内存泄露问题,解决掉内存泄露后红色空间都能得到释放,再去显示一张0.8M的优化图片就不会再报OOM异常了。

    不珍惜内存导致的OOM分析:

    上面说了内存泄露导致的OOM异常,下面我们再来看一幅图(数据和场景描述有夸张,请忽略),如下:

    这里写图片描述

    可见,这种类型的OOM就很好定位原因了,一般都可以从OOM后的log中得出分析定位。

    如下例子,我们在Activity中的ImageView放置一张未优化的特大的(30多M)高清图片,运行直接崩溃如下:

    //抛出OOM异常
    10-10 09:01:04.873 11703-11703/? E/art: Throwing OutOfMemoryError "Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM"
    10-10 09:01:04.940 11703-11703/? E/art: Throwing OutOfMemoryError "Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM"
    //堆栈打印
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: FATAL EXCEPTION: main
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: Process: com.example.application, PID: 11703
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.application/com.example.myapplication.MainActivity}: android.view.InflateException: Binary XML file line #21: Error inflating class <unknown>
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2610)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2684)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread.access$800(ActivityThread.java:177)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1542)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:111)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:194)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5743)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:372)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:988)
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
    //出错地点,原因是21行的ImageView设置的src是一张未优化的31M的高清图片
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:  Caused by: android.view.InflateException: Binary XML file line #21: Error inflating class <unknown>
    10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.view.LayoutInflater.createView(LayoutInflater.java:633)

    通过上面的log可以很方便的看出来问题原因所在地,那接下来的做法就是优化呗,降低图片的相关规格即可(譬如使用BitmapFactory的Option类操作等)。

    PS:提醒一句的是记得应用所属的内存是区分Java堆和native堆的!

    3-3-3 Android应用规避内存溢出OOM建议

    还是那句话,等待OOM发生是为时已晚的事,我们应该将其扼杀于萌芽之中,至于如何在开发中规避OOM,如下给出一些我们应用开发中的常用的策略建议:

    • 时刻记得不要加载过大的Bitmap对象;譬如对于类似图片加载我们要通过BitmapFactory.Options设置图片的一些采样比率和复用等,具体做法点我参考官方文档,不过过我们一般都用fresco或Glide开源库进行加载。

    • 优化界面交互过程中频繁的内存使用;譬如在列表等操作中只加载可见区域的Bitmap、滑动时不加载、停止滑动后再开始加载。

    • 有些地方避免使用强引用,替换为弱引用等操作。

    • 避免各种内存泄露的存在导致OOM。

    • 对批量加载等操作进行缓存设计,譬如列表图片显示,Adapter的convertView缓存等。

    • 尽可能的复用资源;譬如系统本身有很多字符串、颜色、图片、动画、样式以及简单布局等资源可供我们直接使用,我们自己也要尽量复用style等资源达到节约内存。

    • 对于有缓存等存在的应用尽量实现onLowMemory()和onTrimMemory()方法。

    • 尽量使用线程池替代多线程操作,这样可以节约内存及CPU占用率。

    • 尽量管理好自己的Service、Thread等后台的生命周期,不要浪费内存占用。

    • 尽可能的不要使用依赖注入,中看不中用。

    • 尽量在做一些大内存分配等可疑内存操作时进行try catch操作,避免不必要的应用闪退。

    • 尽量的优化自己的代码,减少冗余,进行编译打包等优化对齐处理,避免类加载时浪费内存。

    可以发现,上面只是列出了我们开发中常见的导致OOM异常的一些规避原则,还有很多相信还没有列出来,大家可以自行追加参考即可。

    3-4 Android内存性能优化总结

    无论是什么电子设备的开发,内存问题永远都是一个很深奥、无底洞的话题,上面的这些内存分析建议也单单只是Android应用开发中一些常见的场景而已,真正的达到合理的优化还是需要很多知识和功底的。

    合理的应用架构设计、设计风格选择、开源Lib选择、代码逻辑规范等都会决定到应用的内存性能,我们必须时刻头脑清醒的意识到这些问题潜在的风险与优劣,因为内存优化必须要有一个度,不能一味的优化,亦不能置之不理。

    【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

    4 Android应用API使用及代码逻辑性能分析

    在我们开发中除过常规的那些经典UI、内存性能问题外其实还存在很多潜在的性能优化、这种优化不是十分明显,但是在某些场景下却是非常有必要的,所以我们简单列举一些常见的其他潜在性能优化技巧,具体如下探讨。

    4-1 Android应用String/StringBuilder/StringBuffer优化建议

    字符串操作在Android应用开发中是十分常见的操作,也就是这个最简单的字符串操作却也暗藏很多潜在的性能问题,下面我们实例来说说。

    先看下面这个关于String和StringBuffer的对比例子:

    //性能差的实现
    String str1 = "Name:";
    String str2 = "GJRS";
    String Str = str1 + str2;
    //性能好的实现
    String str1 = "Name:";
    String str2 = "GJRS";
    StringBuffer str = new StringBuilder().append(str1).append(str2);

    通过这个例子可以看出来,String对象(记得是对象,不是常量)和StringBuffer对象的主要性能区别在于String对象是不可变的,所以每次对String对象做改变操作(譬如“+”操作)时其实都生成了新的String对象实例,所以会导致内存消耗性能问题;而StringBuffer对象做改变操作每次都会对自己进行操作,所以不需要消耗额外的内存空间。

    我们再看一个关于String和StringBuffer的对比例子:

    //性能差的实现
    StringBuffer str = new StringBuilder().append("Name:").append("GJRS");
    //性能好的实现
    String Str = "Name:" + "GJRS";

    在这种情况下你会发现StringBuffer的性能反而没有String的好,原因是在JVM解释时认为
    String Str = "Name:" + "GJRS";就是String Str = "Name:GJRS";,所以自然比StringBuffer快了。

    可以发现,如果我们拼接的是字符串常量则String效率比StringBuffer高,如果拼接的是字符串对象,则StringBuffer比String效率高,我们在开发中要酌情选择。当然,除过注意StringBuffer和String的效率问题,我们还应该注意另一个问题,那就是StringBuffer和StringBuilder的区别,其实StringBuffer和StringBuilder都继承自同一个父类,只是StringBuffer是线程安全的,也就是说在不考虑多线程情况下StringBuilder的性能又比StringBuffer高。

    PS:如果想追究清楚他们之间具体细节差异,麻烦自己查看实现源码即可。

    4-2 Android应用OnTrimMemory()实现性能建议

    OnTrimMemory是Android 4.0之后加入的一个回调方法,作用是通知应用在不同的情况下进行自身的内存释放,以避免被系统直接杀掉,提高应用程序的用户体验(冷启动速度是热启动的2~3倍)。系统会根据当前不同等级的内存使用情况调用这个方法,并且传入当前内存等级,这个等级有很多种,我们可以依据情况实现不同的等级,这里不详细介绍,但是要说的是我们应用应该至少实现如下等级:

    • TRIM_MEMORY_BACKGROUND
      内存已经很低了,系统准备开始根据LRU缓存来清理进程。这时候如果我们手动释放一些不重要的缓存资源,则当用户返回我们应用时会感觉到很顺畅,而不是重新启动应用。

    可以实现OnTrimMemory方法的系统组件有Application、Activity、Fragement、
    Service、ContentProvider;关于OnTrimMemory释放哪些内存其实在架构阶段就要考虑清楚哪些对象是要常驻内存的,哪些是伴随组件周期存在的,一般需要释放的都是缓存。
    如下给出一个我们项目中常用的例子:

    @Override
    public void onTrimMemory(int level) {
       if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
           clearCache();
       }
    }

    通常在我们代码实现了onTrimMemory后很难复显这种内存消耗场景,但是你又怕引入新Bug,想想办法测试。好在我们有一个快捷的方式来模拟触发该水平内存释放,如下命令:

    adb shell dumpsys gfxinfo packagename -cmd trim value

    packagename为包名或者进程id,value为ComponentCallbacks2.java里面定义的值,可以为80、60、40、20、5等,我们模拟触发其中的等级即可。

    4-3 Android应用HashMap与ArrayMap及SparseArray优化建议

    在Android开发中涉及到数据逻辑部分大部分用的都是Java的API(譬如HashMap),但是对于Android设备来说有些Java的API并不适合,可能会导致系统性能下降,好在Google团队已经意识到这些问题,所以他们针对Android设备对Java的一些API进行了优化,优化最多就是使用了ArrayMap及SparseArray替代HashMap来获得性能提升。

    HashMap:

    HashMap内部使用一个默认容量为16的数组来存储数据,数组中每一个元素存放一个链表的头结点,其实整个HashMap内部结构就是一个哈希表的拉链结构。HashMap默认实现的扩容是以2倍增加,且获取一个节点采用了遍历法,所以相对来说无论从内存消耗还是节点查找上都是十分昂贵的。

    SparseArray:

    SparseArray比HashMap省内存是因为它避免了对Key进行自动装箱(int转Integer),它内部是用两个数组来进行数据存储的(一个存Key,一个存Value),它内部对数据采用了压缩方式来表示稀疏数组数据,从而节约内存空间,而且其查找节点的实现采用了二分法,很明显可以看见性能的提升。

    ArrayMap:

    ArrayMap内部使用两个数组进行数据存储,一个记录Key的Hash值,一个记录Value值,它和SparseArray类似,也会在查找时对Key采用二分法。

    有了上面的基本了解我们可以得出结论供开发时参考,当数据量不大(千位级内)且Key为int类型时使用SparseArray替换HashMap效率高;当数据量不大(千位级内)且数据类型为Map类型时使用ArrayMap替换HashMap效率高;其他情况下HashMap效率相对高于二者。

    4-4 Android应用ContentProviderOperation优化建议

    ContentProvider是Android应用开发的核心组件之一,有时候在开发中需要使用ContentProvider对多行数据进行操作,我们的做法一般是多次调运相关操作方法,殊不知这种实现方式是非常低性能的,取而代之的做法应该是使用批量操作,具体为了使批量更新、插入、删除数据操作更加方便官方提供了ContentProviderOperation工具类。所以在我们开发中遇到类似情景时请务必使用批量操作,具体的优势如下:

    • 所有的操作都在一个事务中执行,可以保证数据的完整性。

    • 批量操作在一个事务中执行,所以只用打开、关闭一个事务。

    • 减轻应用程序与ContentProvider间的多次频繁交互,提升性能。

    可以看见,这对于数据库操作来说是一个非常有用的优化措施,烦请务必重视(我们项目优化过,的确有很大提升)。

    4-5 Android应用其他逻辑优化建议

    关于API及逻辑性能优化其实有多知识点的,这里无法一一列出,只能给出一些重要的知识点,下面再给出一些常见的优化建议:

    • 避免在Android中使用Java的枚举类型,因为编译后不但占空间,加载也费时,完全没有static final的变量好用、高效。

    • Handler发送消息时尽量使用obtain去获取已经存在的Message对象进行复用,而不是新new Message对象,这样可以减轻内存压力。

    • 在使用后台Service时尽量将能够替换为IntentService的地方替换为此,这样可以减轻系统压力、省电、省内存、省CPU占用率。

    • 在当前类内部尽量不要通过自己的getXXX、setXXX对自己内部成员进行操作,而是直接使用,这样可以提高代码执行效率。

    • 不要一味的为了设计模式而过分的抽象代码,因为代码抽象系数与代码加载执行时间成正比。

    • 尽量减少锁个数、减小锁范围,避免造成性能问题。

    • 合理的选择使用for循环与增强型for循环,譬如不要在ArrayList上使用增强型for循环等。

    哎呀,类似的小优化技巧有很多,这里不一一列举了,自行发挥留意即可。

    【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

    5 Android应用移动设备电池耗电性能分析

    有了UI性能优化、内存性能优化、代码编写优化之后我们在来说说应用开发中很重要的一个优化模块—–电量优化。

    5-1 Android应用耗电量概念

    在盒子等开发时可能电量优化不是特别重视(视盒子待机真假待机模式而定),但是在移动设备开发中耗电量是一个非常重要的指标,如果用户一旦发现我们的应用非常耗电,不好意思,他们大多会选择卸载来解决此类问题,所以耗电量是一个十分重要的问题。

    关于我们应用的耗电量情况我们可以进行定长时间测试,至于具体的耗电量统计等请参考此文,同时我们还可以直接通过Battery Historian Tool来查看详细的应用电量消耗情况。最简单常用办法是通过命令直接查看,如下:

    adb shell dumpsys batterystats

    其实我们一款应用耗电量最大的部分不是UI绘制显示等,常见耗电量最大原因基本都是因为网络数据交互、GPS定位、大量内存性能问题、冗余的后台线程和Service等造成。

    5-2 Android应用耗电量优化建议

    优化电量使用情况我们不仅可以使用系统提供的一些API去处理,还可以在平时编写代码时就养成好的习惯。具体的一些建议如下:

    • 在需要网络的应用中,执行某些操作前尽量先进行网络状态判断。

    • 在网络应用传输中使用高效率的数据格式和解析方法,譬如JSON等。

    • 在传输用户反馈或者下载OTA升级包等不是十分紧急的操作时尽量采用压缩数据进行传输且延迟到设备充电和WIFI状态时进行。

    • 在有必要的情况下尽量通过PowerManager.WakeLock和JobScheduler来控制一些逻辑操作达到省电优化。

    • 对定位要求不太高的场景尽量使用网络定位,而不是GPS定位。

    • 对于定时任务尽量使用AlarmManager,而不是sleep或者Timer进行管理。

    • 尽可能的减少网络请求次数和减小网络请求时间间隔。

    • 后台任务要尽可能少的唤醒CPU,譬如IM通信的长连接心跳时间间隔、一些应用的后台定时唤醒时间间隔等要设计合理。

    • 特殊耗电业务情况可以进行弹窗等友好的交互设计提醒用户该操作会耗用过多电量。

    可以看见,上面只是一些常见的电量消耗优化建议。总之,作为应用开发者的我们要意识到电量损耗对于用户来说是非常敏感的,只有我们做到合理的电量优化才能赢得用户的芳心。

    【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

    6 Android应用开发性能优化总结

    性能优化是一个很大的话题,上面我们谈到的只是应用开发中常见的性能问题,也是应用开发中性能问题的冰山一角,更多的性能优化技巧和能力不是靠看出来,而是靠经验和实战结果总结出来的,所以说性能优化是一个涉及面非常广的话题,如果你想对你的应用进行性能你必须对你应用的整个框架有一个非常清晰的认识。

    当然了,如果在我们开发中只是一味的追求各种极致的优化也是不对的。因为优化本来就是存在风险的,甚至有些过度的优化会直接导致项目的臃肿,所以不要因为极致的性能优化而破坏掉了你项目的合理架构。

    总之一句话,性能优化适可而止,请酌情优化。

    PS:附上Google关于Android开发的一些专题建议视频链接,不过在天朝需要自备梯子哦。

    【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

    这里写图片描述

    展开全文
  • 3.9_使用剪辑区域优化|离屏Canvas图像处理|Canvas图形、动画、游戏开发从入门到精通全系列课程
  • 2017_2018学年高中地理第四单元区域综合开发与可持续发展第二节农业与区域可持续发展__以东北地区为例优化练习鲁教版必修320180803319
  • 2017_2018学年高中地理第四单元区域综合开发与可持续发展第一节流域综合开发与可持续发展__以长江流域为例优化练习鲁教版必修320180803321
  • 优化指导】2013高考地理总复习 区域可持续发展 2-3 流域综合治理与开发 以田纳西河流域为例 湘教版
  • 区域GPS工程定位优化仿真分析.pdf
  • 区域GPS短距离定位优化仿真.pdf
  • 优化指导】2013高考地理总复习 区域可持续发展 2-2 湿地资源的开发与保护 以洞庭湖区为例 湘教版
  • Android 性能优化 之谈谈Java内存区域

    千次阅读 2018-03-28 14:10:59
    Android 性能优化 之谈谈Java内存区域 &gt; 基础的扎实程度直接决定高度。 最近一年副业主要在学习投资和技能学习,把以前学习内存分析的一些笔记总结发出来,写了很多笔记总结都没有写完就又忙着了,最近...

    Android 性能优化 之谈谈Java内存区域

    >

    基础的扎实程度直接决定高度。

    最近一年副业主要在学习投资和技能学习,把以前学习内存分析的一些笔记总结发出来,写了很多笔记总结都没有写完就又忙着了,最近再次总结复习学习一遍,还有提醒各位同学一定要学会投资。。

    了解Android 内存管理,就有必要了解Java GC机制,必须要理解JVM 内存区域,这里我们主要从概念上介绍Java虚拟机内存的各个区域。

    Android 应用运行时大部分时间都用于处理内存数据,虽然很多开发者都意识到优化内存,但并不是所有人都知道内存使用对性能的影响,网上也有很多关于内存优化技术的文章,但大多都不够系统,没有贯通全局。在《Android 性能优化之String篇》文中我们理解了一个对象占用空间的大小以及String 字符串对我们性能的影响,而在本文中我们将学习一些Android的内存,主要是基础的java 的内存管理,Java程序在运行的过程中管理的内存的数据区,为后面的Android内存分析做为铺垫。
    在阅读过程中有任何问题,请及时联系。如需转载请注明 fuchenxuan blog
    本章系《Android 之美 从0到1 – 高手之路》Android 性能优化 之深入理解Java内存结构。

    Java 内存区域

    Java虚拟机在执行程序过程中会把管理的内存划分为不同的数据区域,包括方法区、堆、虚拟机栈、本地方法栈、程序计数器这5个部分的运行时数据区。如下图:

    程序计数器

    在计算机组成原理里,CPU内部的寄存器中就包含一个程序计数器,存放程序执行的下一条指令地址。
    以下引自wiki的一段介绍。

    依照特定机器的细节而不同,他可能是保存着正在被运行的指令,也可能是下一个要运行指令的地址。程序计数器在每个指令周期会自动地增加,所以指令会正常地从寄存器中连续地被取出。某些指令,像是跳跃和子程序调用,会中断程序执行的序列,将新的数值内容存放到程序计数器中。
    在大部分的处理器,指令指针都是在提取程序指令后就被立即增加;也就是说跳跃指令的目的地址,是由跳跃指令的运算对象加上跳跃指令之后下一个指令的地址(比特或字节,视电脑形态而定)来获得目的地。

    而我们的JVM内存中的程序计数器也是这样的作用,它储存JVM当前执行bytecode的地址。

    Java虚拟机中的程序计数器是Java运行时数据区中的一小块内存区域,但是它的功能和通常的程序计数器是类似的,它指向虚拟机正在执行字节码指令的地址。具体点儿说,当虚拟机执行的方法不是native的时,程序计数器指向虚拟机正在执行字节码指令的地址;当虚拟机执行的方法是native的时,程序计数器中的值是未定义的。另外,程序计数器是线程私有的,也就是说,每一个线程都拥有仅属于自己的程序计数器

    此内存区域是唯一一个在 Java 虚拟机规范
    中没有规定任何 OutOfMemoryError 情况的区域。

    虚拟机栈(JVM Stack)

    与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与 线程相同。虚拟机栈描述的是 Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,
    就对应着一个栈帧在 虚拟机栈中从入栈到出栈的过程。

    局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、 double)、对象引用(reference 类型),它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向 4 / 31
    对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和 returnAddress
    类型(指向了一条字节码指令的地址)。
    其中 64 位长度的 long 和 double 类型的数据会占用 2 个局部变量空间(Slot),其余的数据类型只占 用 1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大 的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。 在 Java 虚拟机规范中,对这个 区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异
    常;如果虚拟机栈可以动态扩展(当前大部分的 Java 虚拟机都可动态扩展,只不过 Java 虚拟机规范中也允许
    固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。

    本地方法栈(Native Method Statck)

    本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为
    虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。虚拟机 规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实 现它。甚至有的虚拟机(譬如 Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一
    样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

    堆区(Heap)

    对于大多数应用来说,Java 堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所 有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存 放对象实例 ,几乎所有的对象 实例都在这里分配内存。这一点在 Java 虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但 是随着 JIT 编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发 生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。
    Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”(Garbage Collected Heap,幸
    好国内没翻译成“垃圾堆”)。如果从内存回收的角度看,由于现在收集器基本都是采用的分 代 收 集 算法,所以 Java 堆中还可以细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等。如果从内存分配的角度看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区(Thread
    Local Allocation Buffer,TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然 是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。在本章中,我们仅仅针对内存区
    域的作用进行讨论,Java 堆中的上述各个区域的分配和回收等细节将会是下一章的主题。
    根据 Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可, 就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都
    是按照可扩展来实现的(通过-Xmx 和-Xms 控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展 时,将会抛出 OutOfMemoryError 异常。

    堆内存唯一目的就是存放创建的对象实例,所有对象实例和数组都要在这里分配内存,因此也是垃圾回收的主要区域。根据虚拟机规范,java堆可以在物理空间上不连续,只要逻辑上连续即可,当堆内存无法继续分配时会抛出OutOfMemery异常。

    方法区(Method Area)

    方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类
    信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑 部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。

    对于习惯在 HotSpot 虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久 代”Permanent Generation),本质上两者并不等价,仅仅是因为 HotSpot 虚拟机的设计团队选择把 GC 分
    代收集扩展至方法区,或者说使用永久代来实现方法区而已。对于其他虚拟机(如 BEA JRockit、IBM J9 等) 来说是不存在永久代的概念的。即使是 HotSpot 虚拟机本身,根据官方发布的路线图信息,现在也有放弃永久 代并“搬家”至 Native Memory 来实现方法区的规划了。
    Java 虚拟机规范对这个区域的限制非常宽松,除了和 Java 堆一样不需要连续的内存和可以选择固定大小
    或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据
    进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标 主要是针对常量池的回收和对类型 的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区
    域的回收确实是有必要的。在 Sun 公司的 BUG 列表中, 曾出现过的若干个严重的 BUG 就是由于低版本的
    HotSpot 虚拟机对此区域未完全回收而导致内存泄漏。 根据 Java 虚拟机规范的规定,当方法区无法满足内存 分配需求时,将抛出 OutOfMemoryError 异常。

    用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。Java虚拟机规范把方法区描述为堆的一个逻辑部分。
    运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项就是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

    运行时常量池

    运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、 方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字 面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每 一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求, 这样才会被虚拟机认可、装载和执行。但对于运行时常量池,Java 虚拟机规范没有做任何细节的要求,不同的 提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了保存 Class 文件中描述 的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。 运行时常量池相对于 Class 文件常量池
    的另外一个重要特征是具备动态性,Java 语言并不要求常量一定只能在编译期产生,也就是并非预置入 Class 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员
    利用得比较多的便是 String 类的 intern()方法。 既然运行时常量池是方法区的一部分,自然会受到方法区内 存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。

    运行时常量池与Class文件常量池区别

    • JVM对Class文件中每一部分的格式都有严格的要求,每一个字节用于存储那种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行;但运行时常量池没有这些限制,除了保存Class文件中描述的符号引用,还会把翻译出来的直接引用也存储在运行时常量区

    • 相较于Class文件常量池,运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入。最主要的运用便是String类的intern()方法

    对象的创建

    以上我们介绍了5个数据区域,三个是线程隔离的,它们有:Java虚拟机栈,本地方法栈,程序计数器。另外还有两个数据区域是线程共享的,这两个区域是垃圾回收发生的地方,它们分别是:堆,方法区。

    我们最常见的也是最简单的创建对象有以下几种方式:

    通过这种方式我们可以调用任意的构造函数(无参的和有参的)去创建对象。

    Object object = new Object();

    使用Class类的newInstance方法(反射机制)
     
    Student stu = Student.class.newInstance();

    new 指令时主要涉及一些一个过程:

    • 检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过
    • 类加载检查通过
    • 虚拟机java堆为新主对象分配内存,对象所需内存的大小在类加载完成后确定
    • 虚拟机将分配到的内存空间都初始化为零值
    • 设置对象头,对象头中存储了该对象是拿了类的实例等信息
    • 执行 init 方法,把对象初始化

    这里写图片描述

    对象的访问

    介绍完 Java 虚拟机的运行时数据区之后,我们就可以来探讨一个问题:在 Java 语言中,对象访问是如何 进行的?对象访问在 Java 语言中无处不在,是最普通的程序行为,但即使是最简单的访问,也会却涉及 Java
    栈、Java 堆、方法区这三个最重要内存区域之间的关联关系,如下面的这句代码: Object obj = new Object();
    假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到 Java 栈的本地变量表中,作为一个 reference 类型数据出现。而“new Object()”这部分的语义将会反映到 Java 堆中,形成一块存储了 Object 类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机 实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在 Java 堆中
    还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则 存储在方法区中。

    句柄方式

    如果使用句柄访问方式,Java 堆中将会划分出一块内存 来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息

    这里写图片描述

    直接指针方式

    如果使用的是直接指针访问方式,Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,
    reference 中直接存储的就是对象地址

    这里写图片描述

    这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是 reference 中存储的是稳定的句柄地 址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference
    本 身 不 需要被修改。 使用直接指针访问方式的最大好处就 是速度更快,它节省了一次指针定位 的时间开销,由 于对象的访问在 Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。就本书讨论的主
    要虚拟机 Sun HotSpot 而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言 和框架使用句柄来访问的情况也十分常见。

    总结

    上面说了那么说,下面来个简单的概括:

    • 方法区:方法区存放的是类信息、常量、静态变量,所有线程共享区域。

    • 虚拟机栈:它的生命周期与 线程相同,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用存储局部变量表、操作数栈、动态链接、方法出口等信息,线程私有区域。

    • 本地方法栈:与虚拟机栈类似,区别是虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用到的Native方法。

    • :线程共享;用来存放对象实例,几乎所有的对象实例都在堆上分配内存;此区域也是垃圾回收器(Garbage Collection)主要的作用区域,内存泄漏就发生在这个区域。

    • 程序计数器:用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。如果线程在执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址。

    通过上面的学习,我们了解了JVM的内存区域,和java 对象的基本创建和访问方式。为我们后面线上OOM 分析、Dalvik内存管理学习做铺垫,以及分析线上各种OOM 的原因以及如何去解决。

    后面几篇文章:

    1.Android 性能优化 之谈谈Java内存区域
    2.Android 性能优化 之了解Dalvik内存分配策略
    3.Android 性能优化 之内存泄漏分析
    4.Android 性能优化 之Dalvik内存管理、线上OOM分析总结

    更多Android 之美,请阅读《Android 之美 从0到1 – 高手之路》系列文章

    水平有限,若有错漏,欢迎指正,批评,如需转载,请注明出处–http://blog.csdn.net/vfush,谢谢!

    Android 之美 从0到1

    展开全文
  • PyTorch实现(Trust Region Policy Optimization,信任区域策略优化)算法
  • 基于人工蜂群优化法的多区域电力系统经济调度.pdf
  • MDCC 2016 中国移动开发者大会前夕,iOS 开发峰会演讲嘉宾——搜狗输入法 iOS 版负责人李腾杰接受 CSDN 专访,分享了其与团队在第三方输入法开发优化方面的经验,以及搜狗输入法 iOS 版开发团队在新技术与一线项目...

    输入法是一种对性能要求极高的产品,不同于普通应用开发,很多在普通应用开发看来不是问题的,在输入法看来却是比较关键。同时,由于 iOS 系统的某些限制,输入法不得不在产品功能和性能方面做出调整,以尽可能地优化用户体验。在MDCC 2016 中国移动开发者大会前夕,iOS 开发峰会演讲嘉宾——搜狗输入法 iOS 版负责人李腾杰接受 CSDN 专访,分享了其与团队在第三方输入法开发与优化方面的经验,以及搜狗输入法 iOS 版开发团队在新技术与一线项目开发相结合的尝试与实践。(大会门票 8 折优惠正在进行时,欲购从速!票务详情链接

    搜狗输入法 iOS 版负责人 李腾杰

    请介绍一下您和目前的工作,以及关注/正在研究的技术。

    李腾杰: 我目前在搜狗公司从事 iOS 平台搜狗输入法的相关研发工作,重点关注输入法关键性能指标(如键盘调起速度、内存、CPU 等)和代码架构的优化。同时,也在研究 Swift 3,在合适的时间点安排项目适配 Swift 3。

    在 iOS 开发新的技术点方面,搜狗输入法 iOS 版开发团队有着怎样的尝试与实践?

    李腾杰: 在 iOS 动态更新方案上,我们跟进了对 JSPatchReact Native 的调研,最后根据输入法的项目实际情况,择选 JSPatch 应用于输入法,完成了输入法热更新的工作。JSPatch 集成较为简单,是一种应用较广泛的热修复解决方案,它可以通过服务器后台下发 JavaScript 脚本,实时修改 Objective-C 方法的实现,达到修复 Bug 和动态运营的目的。

    由于 JSPatch 存在性能方面的限制,实际项目中仅用于做一些轻量级代码的修复,并没有频繁的使用。考虑到安全性,输入法自行部署了服务器后台来分版本管理下发的脚本,针对键盘扩展和其容器 App 两种类型的下发脚本。同时,由于输入法键盘扩展的特殊性,受到键盘允许完全访问控制权限的影响,输入法尝试了通过键盘容器 App 来获取修复脚本,借由相同 Group 的共享目录来影响键盘。在输入法关键性能指标的优化上,我们也跟进了 FBRetainCycleDetector(Facebook开源的一个内存泄漏检测解决方案)、FastImageCache(Path 团队开发的一个开源库,用于提升图片的加载和渲染速度)的调研,并运用于项目实践,优化产出效果较好,确实发现了一些代码实现中的问题,并改进了输入法换肤的稳定性。

    在搜狗输入法 iOS 版研发与更新过程中,遇到过哪些比较棘手的问题,如何解决的?

    李腾杰: 最棘手的问题,往往是和 iOS 系统相关的,这里举一个输入法遇到的字符绘制产生的诡异崩溃。我们在分析输入法收集的崩溃日志时,曾经遇到一种特别诡异的崩溃,从崩溃栈来看是崩溃在候选绘制上,其占比也挺大,直接影响到输入法的稳定性。单纯分析崩溃栈对应的代码调用,并没有任何问题,也确认了并不是内存越界等造成的崩溃,我们尝试了各种自动化评测或人工操作方案,都没能复现该崩溃。经过初步分析,怀疑是和系统绘制某些特定字符异常导致的崩溃,于是就对崩溃日志收集进行了改进,增加了对崩溃异常时当前候选内容的收集,上线版本后通过收集的崩溃日志,发现对应的候选内容在绘制时并没有任何问题。

    鉴于这种情况,我们在原有自动化评测的基础上,单独抽调一台 iMac 机器,通过 Xcode 真机调试的方式来分多个机型和系统进行更细致的评测,最终在其中一台测试机器上发现了类似崩溃。且发生崩溃时,在 Xcode 上看到的当前绘制候选字符是一个表示“哭”的颜文字表情。一般情况下绘制该表情并不会有什么问题,我们在该机型上又持续进行了三次长时间的模拟输入评测,有一次崩溃还是与该表情相关。根据这个情况,我们在后续的上线版本中,从词库中剔除了该表情,根据后台监测到的崩溃情况,这个困扰许久的崩溃问题终于得到解决。最终分析结论是 iOS 系统在某些条件下绘制该复杂的表情可能会产生崩溃,而为何会如此,一直也没弄清楚,虽然这个崩溃确实是修复了。

    中文字体文件大,以 Webfonts 的方式加载困难加载比较困难,搜狗输入法是怎么优化的?

    李腾杰: 由于中文字体比较大,且从 iOS 8 开始被系统一旦加载,并不能立即释放,而是由系统决定在什么时机释放,且不能完全释放。针对这种情况,搜狗输入法的候选词绘制并没有开放让用户自定义,而是根据不同的 iOS 系统,使用其默认的系统字体;输入法在皮肤个性化里,允许皮肤作者自定义键盘区域的文本显示字体,并建议皮肤作者将该皮肤对应的字体文件包含的字符集控制在最小够用,避免字体太大导致皮肤下载流量和内存占用的增加。如果输入法被启用的皮肤没有指定字体,则输入法使用当前系统的默认字体。上述方案是输入法认真权衡用户输入性能优先,并兼顾输入法皮肤功能多样性之后,得到的一个优化方案,也避免了中文字体过大对输入法性能带来的压力。

    在输入法框架方面,搜狗输入法是如何实现不同平台代码复用的?

    李腾杰: 搜狗输入法会将某些不同平台共有的基础模块(如输入法内核)使用C/C++来实现,并用宏分隔开平台相关的某些具体 API 实现,封装成统一的接口来供平台调用。在更新某些模块的功能实现时,对平台调用始终透明,尽量保证接口的一致性。

    搜狗输入法的内核模块,划分了接口层、功能逻辑层和设备层三个层次,接口层对应平台代码调用,接口一般相对较为固定,除非大的功能调整才变化。功能逻辑层封装了具体的内核功能逻辑的实现代码,对各个平台是透明的,主要用于接口层的调用。设备层封装了与平台相关的某些API实现,不同平台会有不同的技术实现,如字符串操作、文件操作等,供功能逻辑层调用,一般很少变化。通过这三个层次的封装,内核实现某个新功能时,一般只需要调整功能逻辑层的实现,然后通过内核自动化测试工具来完成测试,就可以方便地提供给各个平台使用,平台调用也不用做什么接口层的更改。

    搜狗输入法的 Emoji、颜文字一直以来都颇受用户喜爱,使用率非常高,在这方面你们做了哪些工作?

    李腾杰: 在不同的 iOS 系统上,Emoji 表情的显示效果可能会有差异,且随着 iOS 系统的更新,Emoji 表情编码也会有新增或更改,这就要求输入法始终关注每个 iOS 系统的 Emoji 全集,及时更新到输入法中。搜狗输入法通过自动化评测的手段来分析每个 iOS 版本其系统表情键盘的 Emoji 表情全集,看是否有新的变化,并及时跟进更新。同时,Emoji、颜文字等表情字符的绘制又比较消耗资源,输入法尝试了提前将字符绘制成图片打包的方式来解决实时绘制表情字符造成的内存压力;并考虑到相同 Emoji 编码在不同 iOS 系统上的显示效果差异,又调研了在 iOS 9 及以上系统绘制 Emoji 表情不缓存的技术解决方案,将图片和绘制两种方式结合使用,保证了 Emoji 和颜文字表情功能的良好性能。除此之外,输入法提供了表情商城,用户可以在里面很方便地下载到颜文字表情包、图片表情等,大大丰富了用户的输入体验。

    Emoji、颜文字功能内存方面的优化实践,在后续的 MDCC 2016 移动开发者大会上会详细介绍。

    从 2009 年加入搜狗从事输入法的相关研发工作至今,从您的亲身实践来看,iOS 系统对于第三方输入法研发的支持有着怎样的改进与发展?就目前而言,在开发过程中,还有哪些特别想吐槽的限制?

    李腾杰: iOS 系统在 iOS 8 上才开始开放第三方输入法的研发,在这之前,只能通过越狱的方式来进行第三方输入法的研发,Hook 系统输入法相关的 API 来实现输入法的功能。越狱方式开发输入法不容易,每每 iOS 系统更新,第三方输入法都需要及时跟进,调研该 iOS 系统输入法相关 API 是否有变更,并在新的越狱工具出来之后,以最快的速度去跟进适配,保证用户率先用上最稳定的输入法。

    但越狱输入法由于 iOS 系统的限制,性能和稳定性方面难免存在一些避免不了的问题,很难将输入法体验做到完美。且越狱输入法的安装门槛也比较高,阻隔了很大一部分用户的第三方输入法需求。从 iOS 8 开始,iOS 系统对第三方输入法的研发提供了支持,接口也比较稳定,开发者在适配不同 iOS 系统上耗费的精力也少了很多,广大用户也能很便捷地通过 App Store 安装自己想要的输入法,皆大欢喜的场面。

    即便如此,iOS 系统至今仍有的某些限制,仍给第三方输入法造了不少麻烦事儿,主要有以下三个点:

    1. 首先,是系统第三方自定义键盘框架的稳定性不够,诸如升级输入法后旧版本的输入法进程仍存在、未能及时更新导致某些界面显示异常或崩溃;键盘无法调起;键盘需要重新添加等等,都给用户体验造成了较大影响,而第三方输入法又无能为力。
    2. 其次,是iOS系统对第三方键盘的诸多限制,如允许完全访问控制权限的限制,限制了用户需要开启入口隐蔽的权限开关,才能体验到输入法的某些特色功能;而且,由于iOS系统的限制,第三方键盘并没有如系统键盘一样被系统一视同仁,键盘调起体验始终无法像系统键盘那么流畅。
    3. 再者,是iOS系统对第三方键盘的资源使用限制比普通App更为严格,一旦第三方键盘使用的内存被它认为较多或系统内存紧张,就会直接kill键盘进程,切换成其它输入法,直接影响了输入法的稳定性,也对输入法开发的性能优化提出了相比越狱输入法更高的挑战。

    根据您的经验,当前在开发第三方 iOS 输入法方面,开发团队所面临的主要挑战是什么?在此过程中又容易陷入怎样的误区?

    李腾杰: 输入法不同于普通 App 的开发,对性能非常敏感,开发团队面临的主要挑战是兼顾功能效率的同时如何充分保障性能,并让每个开发组员都对性能足够敏感和关注。在实际项目过程中,针对某些涉及敏感性能的技术实现方案,可能会涉及到新技术的使用,需要避免“它性能应该没问题”这种猜测方式,通过评测来评估新技术的性能如何,这看起来有点麻烦,但确实省不得。

    输入法在最初适配不同键盘尺寸的按键布局时,使用了 Autolayout 的解决方案,Autolayout 方法简单、强大易用、可读性强,但是 updateConstraints 调用时机不一样,计算布局的耗时也不一样;尤其在适配较为复杂的界面依赖布局时,耗时会显著增加,性能会比直接setFrame差。下图所示是某技术同行对Autolayout的性能分析(详见:http://pilky.me/36/)。

    输入法最早就踩了 Autolayout 性能的这个坑,缺少对该技术方案的详细分析评测,影响了键盘调起速度,后续针对键盘调起速度的优化过程中,通过 Instruments 的 Time Profiler 才定位到该问题。

    在输入法某些基础关键性能指标(如键盘调起速度、内存等)的优化上,输入法同样踩过一些坑,这些坑在普通App看来可能并不是太大的问题,但在输入法上体现却相对明显,在 MDCC 上我会就此做详细分享。

    对于输入法的使用,用户比较关心信息安全的问题,搜狗输入法对于用户信息的保护有着怎样的经验?

    李腾杰: 首先,输入法本身不收集用户的任何输入信息,用户输入信息在本地进行运算,并给出对应的候选词结果,然后再调用系统 API 直接上屏到输入框之中,中间不做任何缓存。其次,输入法某些功能需要访问到用户隐私信息时,会在产品交互层面,明确说明访问哪些用户信息及其用途,只有用户允许了该访问,才会进行后续的功能逻辑。再者,iOS 系统为所有的第三方输入法都提供了“允许完全访问控制权限”的选项,用户一旦关闭该选项,那输入法便无法进行联网或访问系统某些权限要求比较高的功能模块。另外,输入法某些功能的联网请求,诸如云输入、词库同步功能,会使用加密方式与服务器进行通信,确保整个通讯过程是非明文可靠的,且整个请求不在服务器缓存任何数据。

    您在 MDCC 2016 上将分享《搜狗输入法性能优化实践》,希望此次演讲能够为听众带来哪些方面的启发?

    李腾杰: 这次分享的搜狗输入法在项目过程中某些关键性能指标的优化实践,希望通过实际案例来说明一些性能优化的方法和建议,与同行共同交流。

    对于您而言,您最期待在 MDCC 2016 上看到哪些内容?收获什么?

    李腾杰: 期待在今年的 MDCC 上看到 iOS、Android 相关新技术如何应用于实际项目,与同行交流性能优化方面踩过的坑,学习好的优化经验。

    更多精彩尽在 MDCC 2016,详情请查看大会官网:MDCC 2016 中国移动开发者大会

    展开全文
  • Android开发之ListView 优化之缓存优化

    千次阅读 2016-10-25 16:51:24
    通过平时对ListView的使用,目前我把ListView的优化分为以下几个方面: 1.缓存优化 2.数据优化 3.其他方面优化 0.未优化简单代码 public class MainActivity extends Activity { private ListView lv_demo; ...
  • 2017_2018学年高中地理第四单元区域综合开发与可持续发展第三节经济发达地区的可持续发展__以珠江三角洲地区为例优化练习鲁教版必修320180803320
  • Android开发性能优化简介

    万次阅读 2012-04-21 09:59:14
    以上理由,足以需要开发人员更加专心去实现和优化你的代码了。选择合适的算法和数据结构永远是开发人员最先应该考虑的事情。同时,我们应该时刻牢记,写出高效代码的两条基本的原则:(1)不要做不必要的
  • 英特尔有下列牛逼的开发工具与辅助套件,如何应用到实践中去,让自己的软件变得更加大,软件开发更加容易,软件拥有更多的性能呢英特尔® 图形性能分析器 3.0英特尔® Cloud Builder英特尔® 主动管理技术 API ...
  • 基于改进蚁群算法的区域伪卫星增强GPS星座优化设计.pdf
  • matlab开发-模拟退火优化的简单示例。为研究案例开发混合解决方案(模拟退火 模式搜索)的示例。
  • Android开发性能优化总结(二)

    千次阅读 2016-04-24 17:06:02
    接上一篇《Android开发性能优化总结(一)》 一、安卓UI性能检测与优化 UI是安卓应用程序与用户打交道的最直接途径,UI设计的好不好,直接影响到用户的体验,如果没有达到他们心目中的自然流畅细节,用户要是能够...
  • 基于水资源净效益最大化的水资源优化配置思想,以水资源承载力、用水部门生存条件、用水公平性及可持续发展约束等水资源开发利用约束为准则,建立了基于水资源净效益最大化的水资源优化配置模型.将模型应用于南水北...
  • Android WebView开发问题及优化汇总

    千次阅读 2014-11-18 15:36:19
    就我目前开发过程中遇到的问题以及最后得到的优化方案都将在这里列举出来。有些是老生常谈,有些则是个人摸索得出解决方法。下面就是整理得到的些干货。 1.加快HTML网页装载完成的速度 默认情况html代码下载到...
  • Linux环境下为了方便Shell的开发,对配置文件vimrc的参数需要有一些的优化。 参数如下: """"""""""""""""""""""""""""""""" " => 全局配置 """"""""""""""""""""""""""""""""" " 关闭兼容模式 set nocompatible " ...
  • 区域间在旅游资源开发利用、产品策划、市场开发等方面实现协作,有利于实现区域旅游发展效益的最大化和最优化.多数区域相邻的城市因资源雷同、产品相似、市场定位相似 等多重因素的影响,在旅游业发展过程中表现为低...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 162,617
精华内容 65,046
关键字:

优化开发区域