对于一些Android项目,影响性能瓶颈的主要是Android本身内存管理机制问题,目前手机厂商对RAM都比较吝啬,对于软件的流畅性来讲RAM对性能的影响十分敏感,除了 优化Dalvik虚拟机的堆内存分配外,咱们还能够强制定义本身软件的对内存大小,咱们使用Dalvik提供的 dalvik.system.VMRuntime类来设置最小堆内存为例:
java
//设置最小heap内存为6MB大小。固然对于内存吃紧来讲还能够经过手动干涉GC去处理
bitmap 设置图片尺寸,避免 内存溢出 OutOfMemoryError的优化方法
★android 中用bitmap 时很容易内存溢出,报以下错误:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget
● 主要是加上这段:
linux
● eg1:(经过Uri取图片)
android
以上代码能够优化内存溢出,但它只是改变图片大小,并不能完全解决内存溢出。
● eg2:(经过路径去图片)
程序员
★Android 还有一些性能优化的方法:
● 首先内存方面,能够参考 Android堆内存也可本身定义大小 和 优化Dalvik虚拟机的堆内存分配
● 基础类型上,由于Java没有实际的指针,在敏感运算方面仍是要借助NDK来完成。这点比较有意思的是Google 推出NDK多是帮助游戏开发人员,好比OpenGL ES的支持有明显的改观,本地代码操做图形界面是很必要的。
● 图形对象优化,这里要说的是Android上的Bitmap对象销毁,能够借助recycle()方法显示让GC回收一个Bitmap对象,一般对一个不用的Bitmap能够使用下面的方式,如
面试
● 目前系统对动画支持比较弱智对于常规应用的补间过渡效果能够,可是对于游戏而言通常的美工可能习惯了GIF方式的统一处理,目前Android系统仅能预览GIF的第一帧,能够借助J2ME中经过线程和本身写解析器的方式来读取GIF89格式的资源。
● 对于大多数Android手机没有过多的物理按键可能咱们须要想象下了作好手势识别 GestureDetector 和重力感应来实现操控。一般咱们还要考虑误操做问题的降噪处理。
Android堆内存也可本身定义大小
对于一些大型Android项目或游戏来讲在算法处理上没有问题外,影响性能瓶颈的主要是Android本身内存管理机制问题,目前手机厂商对RAM都比较吝啬,对于软件的流畅性来讲RAM对性能的影响十分敏感,除了上次Android开发网提到的优化Dalvik虚拟机的堆内存分配外,咱们还能够强制定义本身软件的对内存大小,咱们使用Dalvik提供的 dalvik.system.VMRuntime类来设置最小堆内存为例:
算法
//设置最小heap内存为6MB大小。固然对于内存吃紧来讲还能够经过手动干涉GC去处理,咱们将在下次提到具体应用。
优化Dalvik虚拟机的堆内存分配
对于Android平台来讲,其托管层使用的Dalvik JavaVM从目前的表现来看还有不少地方能够优化处理,好比咱们在开发一些大型游戏或耗资源的应用中可能考虑手动干涉GC处理,使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法能够加强程序堆内存的处理效率。固然具体原理咱们能够参考开源工程,这里咱们仅说下使用方法: private final static floatTARGET_HEAP_UTILIZATION = 0.75f; 在程序onCreate时就能够调用 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 便可。sql
层级观察器(Hierarchy Viewer):数据库
Android SDK tools目录下提供一个观察布局的工具,层级观察器(Hierarchy Viewer)。Hierarchy Viewer工具是一个很是好的布局优化工具,同时,你也能够经过它学习他人的布局。应该说是一个很是实用的工具。缓存
上面写有控件名称和id等信息,下方的圆形表示这个节点的渲染速度,从左至右分别为测量大小,布局和绘制。绿色最快,红色最慢。右下角的数字为子节点在父节点中的索引,若是没有子节点则为0。点击能够查看对应控件预览图、该节点的子节点数(为6则有5个子节点)以及具体渲染时间。双击能够打开控件图。右侧是树形结构的预览、控件属性和应用界面的结构预览。点击相应的树形图中的控件能够在右侧看到他在布局中的位置和属性。工具栏有一系列的工具,保存为png或者psd等工具。(http://www.linuxidc.com/Linux/2012-01/52263.htm )安全
layoutopt使用:
建立好看的Android布局是个不小的挑战,当你花了数小时调整好它们适应多种设备后,你一般不想再从新调整,但笨重的嵌套布局效率每每很是低下,幸运的是,在Android SDK中有一个工具能够帮助你优化布局,以减小内存消耗,提升应用程序运行性能。
layoutoptimization
优化是须要必定技巧的,性能良好的代码当然重要,但写出优秀代码的成本每每也很高,你可能不会过早地贸然为那些只运行一次或临时功能代码实施优化,若是你的应用程序反应迟钝,而且卖得很贵,或使系统中的其它应用程序变慢,用户必定会有所响应,你的应用程序下载量将极可能受到影响。
在开发期间尽早优化你的布局是节省成本,提升性能的简单方法,Android SDK带来了一个工具,它能够自动分析你的布局,发现可能并不须要的布局元素,以下降布局复杂度。
第一步:准备工做
若是想使用Android SDK中提供的优化工具,你须要在开发系统的命令行中工做,若是你不熟悉使用命令行工具,那么你得多下功夫学习了。
咱们强烈建议你将Android工具所在的路径添加到操做系统的环境变量中,这样就能够直接敲名字运行相关的工具了,不然每次都要在命令提示符后面输入完整的文件路径,如今在Android SDK中有两个工具目录:/tools和/platform-tools,本文主要使用位于/tools目录中的layoutopt工具,另外我想说的是,ADB工具位于/platform-tools目录下。
运行layoutopt
运行layoutopt工具是至关简单的,只须要跟上一个布局文件或布局文件所在目录做为参数,须要注意的是,这里你必须包括布局文件或目录的完整路径,即便你当前就位于这个目录。咱们来看一个简单的例子:
D:\d\tools\eclipse\article_ws\Nothing\res\layout>layoutopt
D:\d\tools\eclipse\article_ws\Nothing\res\layout\main.xml
D:\d\tools\eclipse\article_ws\Nothing\res\layout\main.xml
D:\d\tools\eclipse\article_ws\Nothing\res\layout>
注意,在上面的示例中,包含了文件的完整路径,若是不指定完整路径,不会输出任何内容,例如:
D:\d\tools\eclipse\article_ws\Nothing\res\layout>layoutopt main.xml D:\d\tools\eclipse\article_ws\Nothing\res\layout>
所以,若是你看不任何东西,则极可能是文件未被解析,也就是说文件可能未被找到。
使用layoutopt输出
Layoutopt的输出结果只是建议,你能够有选择地在你的应用程序中采纳这些建议,下面来看几个使用layoutopt输出建议的例子。
无用的布局
在布局设计期间,咱们会频繁地移动各类组件,有些组件最终可能会再也不使用,如:
工具将会很快告诉咱们LinearLayout内的LinearLayout是多余的:
11:17 This LinearLayout layout or its LinearLayout parent is useless
输出结果每一行最前面的两个数字表示建议的行号。
根能够替换
Layoutopt的输出有时是矛盾的,例如:
这个布局将返回下面的输出:
5:22 The root-level <FrameLayout/> can be replaced with <merge/> 10:21 This LinearLayout layout or its FrameLayout parent is useless
第一行的建议虽然可行,但不是必需的,咱们但愿两个TextView垂直放置,所以LinearLayout应该保留,而第二行的建议则能够采纳,能够删除无用的FrameLayout。
有趣的是,这个工具不是全能的,例如,在上面的例子中,若是咱们给FrameLayout添加一个背景属性,而后再运行这个工具,第一个建议固然会消失,但第二个建议仍然会显示,工具知道咱们不能经过合并控制背景,但检查了LinearLayout后,它彷佛就忘了咱们还给FrameLayout添加了一个LinearLayout不能提供的属性。
太多的视图
每一个视图都会消耗内存,在一个布局中布置太多的视图,布局会占用过多的内存,假设一个布局包含超过80个视图,layoutopt可能会给出下面这样的建议:
-1:-1 This layout has too many views: 83 views, it should have <= 80! -1:-1 This layout has too many views: 82 views, it should have <= 80! -1:-1 This layout has too many views: 81 views, it should have <= 80!
上面给出的建议是视图数量不能超过80,固然最新的设备有可能可以支持这么多视图,但若是真的出现性能不佳的状况,最好采纳这个建议。
嵌套太多
布局不该该有太多的嵌套,layoutopt(和Android团队)建议布局保持在10级之内,即便是最大的平板电脑屏幕,布局也不该该超过10级,RelativeLayout多是一个解决办法,但它的用法更复杂,好在Eclipse中的Graphical Layout资源工具更新后,使得这一切变得更简单。
下面是布局嵌套太多时,layoutopt的输出内容:
-1:-1 This layout has too many nested layouts: 12 levels, it should have <= 10! 305:318 This LinearLayout layout or its RelativeLayout parent is possibly useless 307:314 This LinearLayout layout or its FrameLayout parent is possibly useless 310:312 This LinearLayout layout or its LinearLayout parent is possibly useless
嵌套布局警告一般伴随有一些无用布局的警告,有助于找出哪些布局能够移除,避免屏幕布局所有从新设计。
小结
Layoutopt是一个快速易用的布局分析工具,找出低效和无用的布局,你要作的是判断是否采纳layoutopt给出的优化建议,虽然采纳建议做出修改不会当即大幅改善性能,但没有理由须要复杂的布局拖慢整个应用程序的速度,而且后期的维护难度也很大。简单布局不只简化了开发周期,还能够减小测试和维护工做量,所以,在应用程序开发期间,应尽早优化你的布局,不要等到最后用户反馈回来再作修改。
一、索引
简单的说,索引就像书本的目录,目录能够快速找到所在页数,数据库中索引能够帮助快速找到数据,而不用全表扫描,合适的索引能够大大提升数据库查询的效率。
(1). 优势
大大加快了数据库检索的速度,包括对单表查询、连表查询、分组查询、排序查询。常常是一到两个数量级的性能提高,且随着数据数量级增加。
(2). 缺点
索引的建立和维护存在消耗,索引会占用物理空间,且随着数据量的增长而增长。
在对数据库进行增删改时须要维护索引,因此会对增删改的性能存在影响。
(3). 分类
a. 直接建立索引和间接建立索引
直接建立: 使用sql语句建立,Android中能够在SQLiteOpenHelper的onCreate或是onUpgrade中直接excuSql建立语句,语句如
间接建立: 定义主键约束或者惟一性键约束,能够间接建立索引,主键默认为惟一索引。
b. 普通索引和惟一性索引
普通索引:
惟一性索引:保证在索引列中的所有数据是惟一的,对聚簇索引和非聚簇索引均可以使用,语句为
c. 单个索引和复合索引
单个索引:索引创建语句中仅包含单个字段,如上面的普通索引和惟一性索引建立示例。
复合索引:又叫组合索引,在索引创建语句中同时包含多个字段,语句如:
CREATE INDEX name_index ON username(firstname, lastname)
其中firstname为前导列。
d. 聚簇索引和非聚簇索引(汇集索引,群集索引)
聚簇索引:物理索引,与基表的物理顺序相同,数据值的顺序老是按照顺序排列,语句为:
其中WITH ALLOW_DUP_ROW表示容许有重复记录的聚簇索引
非聚簇索引:
索引默认为非聚簇索引
(4). 使用场景
在上面讲到了优缺点,那么确定会对什么时候使用索引既有点明白又有点糊涂吧,那么下面总结下:
a. 当某字段数据更新频率较低,查询频率较高,常常有范围查询(>, <, =, >=, <=)或order by、group by发生时建议使用索引。而且选择度越大,建索引越有优点,这里选择度指一个字段中惟一值的数量/总的数量。
b. 常常同时存取多列,且每列都含有重复值可考虑创建复合索引
(5). 索引使用规则
a. 对于复合索引,把使用最频繁的列作为前导列(索引中第一个字段)。若是查询时前导列不在查询条件中则该复合索引不会被使用。
如
b. 避免对索引列进行计算,对where子句列的任何计算若是不能被编译优化,都会致使查询时索引失效
c. 比较值避免使用NULL
d. 多表查询时要注意是选择合适的表作为内表。链接条件要充份考虑带有索引的表、行数多的表,内外表的选择可由公式:外层表中的匹配行数*内层表中每一次查找的次数肯定,乘积最小为最佳方案。实际多表操做在被实际执行前,查询优化器会根据链接条件,列出几组可能的链接方案并从中找出系统开销最小的最佳方案。
e. 查询列与索引列次序一致
f. 用多表链接代替EXISTS子句
g. 把过滤记录数最多的条件放在最前面
h. 善于使用存储过程,它使sql变得更加灵活和高效(Sqlite不支持存储过程::>_<:: )
(6)索引检验
创建了索引,对于某条sql语句是否使用到了索引能够经过执行计划查看是否用到了索引。
二、使用事务
使用事务的两大好处是原子提交和更优性能。
(1) 原子提交
原则提交意味着同一事务内的全部修改要么都完成要么都不作,若是某个修改失败,会自动回滚使得全部修改不生效。
(2) 更优性能
Sqlite默认会为每一个插入、更新操做建立一个事务,而且在每次插入、更新后当即提交。
这样若是连续插入100次数据实际是建立事务->执行语句->提交这个过程被重复执行了100次。若是咱们显示的建立事务->执行100条语句->提交会使得这个建立事务和提交这个过程只作一次,经过这种一次性事务能够使得性能大幅提高。尤为当数据库位于sd卡时,时间上能节省两个数量级左右。
Sqlte显示使用事务,示例代码以下:
其中sqliteOpenHelper.getWritableDatabase()表示获得写表权限。
三、其余优化
(1) 语句的拼接使用StringBuilder代替String
这个就很少说了,简单的string相加会致使建立多个临时对象消耗性能。StringBuilder的空间预分配性能好得多。若是你对字符串的长度有大体了解,如100字符左右,能够直接new StringBuilder(128)指定初始大小,减小空间不够时的再次分配。
(2) 读写表
在写表时调用sqliteOpenHelper..getWritableDatabase(),在读表时候调用sqliteOpenHelper..getReadableDatabase(),getReadableDatabase性能更优。
(3) 查询时返回更少的结果集及更少的字段。
查询时只取须要的字段和结果集,更多的结果集会消耗更多的时间及内存,更多的字段会致使更多的内存消耗。
(4) 少用cursor.getColumnIndex
根据性能调优过程当中的观察cursor.getColumnIndex的时间消耗跟cursor.getInt相差无几。能够在建表的时候用static变量记住某列的index,直接调用相应index而不是每次查询。
优化为
public static final String HTTP_RESPONSE_TABLE_ID = android.provider.BaseColumns._ID; public static final String HTTP_RESPONSE_TABLE_RESPONSE = "response"; public static final int HTTP_RESPONSE_TABLE_ID_INDEX = 0; public static final int HTTP_RESPONSE_TABLE_URL_INDEX = 1; public List<Object> getData() { …… cursor.getString(HTTP_RESPONSE_TABLE_RESPONSE_INDEX); …… }
四、异步线程
Sqlite是经常使用于嵌入式开发中的关系型数据库,彻底开源。
与Web经常使用的数据库Mysql、Oracle db、sql server不一样,Sqlite是一个内嵌式的数据库,数据库服务器就在你的程序中,无需网络配置和管理,数据库服务器端和客户端运行在同一进程内,减小了网络访问的消耗,简化了数据库管理。不过Sqlite在并发、数据库大小、网络方面存在局限性,而且为表级锁,因此也不必多线程操做。
Android中数据很少时表查询可能耗时很少,不会致使anr,不过大于100ms时一样会让用户感受到延时和卡顿,能够放在线程中运行,但sqlite在并发方面存在局限,多线程控制较麻烦,这时候可使用单线程池,在任务中执行db操做,经过handler返回结果和ui线程交互,既不会影响UI线程,同时也能防止并发带来的异常。实例代码以下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); singleThreadExecutor.execute(new Runnable() { @Override public void run() { // db operetions, u can use handler to send message after db.insert(yourTableName, null, value); handler.sendEmptyMessage(xx); } });
在Android应用开发过程当中,屏幕上控件的布局代码和程序的逻辑代码一般是分开的。界面的布局代码是放在一个独立的xml文件中的,这个文件里面是树型组织的,控制着页面的布局。一般,在这个页面中会用到不少控件,控件会用到不少的资源。
下面从几个方面来介绍如何利用系统资源。
1)利用系统定义的id
好比咱们有一个定义ListView的xml文件,通常的,咱们会写相似下面的代码片断。
这里咱们定义了一个ListView,定义它的id是"@+id/mylist"。实际上,若是没有特别的需求,就能够利用系统定义的id,相似下面的样子。
在xml文件中引用系统的id,只须要加上“@android:”前缀便可。若是是在Java代码中使用系统资源,和使用本身的资源基本上是同样的。不一样的是,须要使用android.R类来使用系统的资源,而不是使用应用程序指定的R类。这里若是要获取ListView能够使用android.R.id.list来获取。
2)利用系统的图片资源
假设咱们在应用程序中定义了一个menu,xml文件以下。
其中代码片断android:icon="@android:drawable/ic_menu_attachment"原本是想引用系统中已有的Menu里的“附件”的图标。可是在Build工程之后,发现出现了错误。提示信息以下:
从错误的提示信息大概能够看出,因为该资源没有被公开,因此没法在咱们的应用中直接引用。既然这样的话,咱们就能够在Android SDK中找到相应的图片资源,直接拷贝到咱们的工程目录中,而后使用相似android:icon="@drawable/ic_menu_attachment"的代码片断进行引用。
这样作的好处,一个是美工不须要重复的作一份已有的图片了,能够节约很多工时;另外一个是能保证咱们的应用程序的风格与系统一致。
经验分享: Android中没有公开的资源,在xml中直接引用会报错。除了去找到对应资源并拷贝到咱们本身的应用目录下使用之外,咱们还能够将引用“@android”改为“@*android”解决。好比上面引用的附件图标,能够修改为下面的代码。 android:icon="@*android:drawable/ic_menu_attachment" 修改后,再次Build工程,就不会报错了。 |
3)利用系统的字符串资源
假设咱们要实现一个Dialog,Dialog上面有“肯定”和“取消”按钮。就能够使用下面的代码直接使用Android系统自带的字符串。
若是使用系统的字符串,默认就已经支持多语言环境了。如上述代码,直接使用了@android:string/yes和@android:string/no,在简体中文环境下会显示“肯定”和“取消”,在英文环境下会显示“OK”和“Cancel”。
4)利用系统的Style
假设布局文件中有一个TextView,用来显示窗口的标题,使用中等大小字体。能够使用下面的代码片断来定义TextView的Style。
其中android:textAppearance="?android:attr/textAppearanceMedium"就是使用系统的style。须要注意的是,使用系统的style,须要在想要使用的资源前面加“?android:”做为前缀,而不是“@android:”。
5)利用系统的颜色定义
除了上述的各类系统资源之外,还能够使用系统定义好的颜色。在项目中最经常使用的,就是透明色的使用。代码片断以下。
经验分享: Android系统自己有不少资源在应用中均可以直接使用,具体的,能够进入android-sdk的相应文件夹中去查看。例如:能够进入$android-sdk$\platforms\android-8\data\res,里面的系统资源就尽收眼底了。 开发者须要花一些时间去熟悉这些资源,特别是图片资源和各类Style资源,这样在开发过程当中,可以想到有相关资源而且直接拿来使用。 |
在一个应用程序中,通常都会存在多个Activity,每一个Activity对应着一个UI布局文件。通常来讲,为了保持不一样窗口之间的风格统一,在这些UI布局文件中,几乎确定会用到不少相同的布局。若是咱们在每一个xml文件中都把相同的布局都重写一遍,一个是代码冗余,可读性不好;另外一个是修改起来比较麻烦,对后期的修改和维护很是不利。
在一个应用程序中,通常都会存在多个Activity,每一个Activity对应着一个UI布局文件。通常来讲,为了保持不一样窗口之间的风格统一,在这些UI布局文件中,几乎确定会用到不少相同的布局。若是咱们在每一个xml文件中都把相同的布局都重写一遍,一个是代码冗余,可读性不好;另外一个是修改起来比较麻烦,对后期的修改和维护很是不利。因此,通常状况下,咱们须要把相同布局的代码单独写成一个模块,而后在用到的时候,能够经过<include /> 标签来重用layout的代码。
常见的,有的应用在最上方会有一个标题栏。相似下图所示。
图 标题栏的示例
若是项目中大部分Activity的布局都包含这样的标题栏,就能够把标题栏的布局单独写成一个xml文件。
咱们将上面的xml文件命名为“navigator_bar.xml”,其它须要标题栏的Activity的xml布局文件就能够直接引用此文件了。
经验分享: 通常状况下,在项目的初期就可以大体肯定总体UI的风格。因此早期的时候就能够作一些规划,将通用的模块先写出来。 下面是可能能够抽出的共用的布局: 1)背景。有的应用在不一样的界面里会用到统一的背景。后期可能会常常修改默认背景,因此能够将背景作成一个通用模块。 2)头部的标题栏。若是应用有统一的头部标题栏,就能够抽取出来。 3)底部的导航栏。若是应用有导航栏,并且大部分的Activity的底部导航栏是相同的,就能够将导航栏写成一个通用模块。 4)ListView。大部分应用都会用到ListView展现多条数据。项目后期可能会常常调整ListView的风格,因此将ListView做为一个通用的模块比较好。 |
有时候,咱们的页面中可能会包含一些布局,这些布局默认是隐藏的,当用户触发了必定的操做以后,隐藏的布局才会显示出来。好比,咱们有一个Activity用来显示好友的列表,当用户点击Menu中的“导入”之后,在当前的Activity中才会显示出一个导入好友的布局界面。从需求的角度来讲,这个导入功能,通常状况下用户是不使用的。即大部分时候,导入好友的布局都不会显示出来。这个时候,就能够使用延迟加载的功能。
有时候,咱们的页面中可能会包含一些布局,这些布局默认是隐藏的,当用户触发了必定的操做以后,隐藏的布局才会显示出来。好比,咱们有一个Activity用来显示好友的列表,当用户点击Menu中的“导入”之后,在当前的Activity中才会显示出一个导入好友的布局界面。从需求的角度来讲,这个导入功能,通常状况下用户是不使用的。即大部分时候,导入好友的布局都不会显示出来。这个时候,就能够使用延迟加载的功能。
ViewStub是一个隐藏的,不占用内存空间的视图对象,它能够在运行时延迟加载布局资源文件。当ViewStub被设置为可见,或者调用inflate()函数时,才会真的去加载这个布局资源文件。该ViewStub在加载视图时会在父容器中替换它自己。所以,ViewStub会一直存在于视图中,直到调用setVisibility(int)或者inflate()为止。ViewStub的布局参数会随着加载的视图数一同被添加到ViewStub父容器。一样,也能够经过使用inflated Id属性来定义或重命名要加载的视图对象的Id值。
请参考下面的代码片断。
经过“stub_import”这个id能够找到被定义的ViewStub对象。加载布局资源文件“progress_overlay”后,ViewStub对象从其父容器中移除。能够经过“panel_import”这个id找到由布局资源“progress_overlay”建立的View。
执行加载布局资源文件的推荐方式以下:
当inflate()被调用, 这个ViewStub被加载的视图所替代,而且返回这个视图对象。这使得应用程序不须要额外执行findViewById()来获取加载视图的引用。
经验分享: 利用ViewStub能够与xml文件里面指定的布局资源文件关联起来,让布局资源文件在须要使用的时候再加载上去。何时用何时才加载,不用在开始启动的时候一次加载。这样作既能够加快应用的启动速度,又能够节省内存资源。 |
一般咱们写程序,都是在项目计划的压力下完成的,此时完成的代码能够完成具体业务逻辑,可是性能不必定是最优化的。通常来讲,优秀的程序员在写完代码以后都会不断的对代码进行重构。重构的好处有不少,其中一点,就是对代码进行优化,提升软件的性能。下面咱们就从几个方面来了解Android开发过程当中的代码优化。
一般咱们写程序,都是在项目计划的压力下完成的,此时完成的代码能够完成具体业务逻辑,可是性能不必定是最优化的。通常来讲,优秀的程序员在写完代码以后都会不断的对代码进行重构。重构的好处有不少,其中一点,就是对代码进行优化,提升软件的性能。下面咱们就从几个方面来了解Android开发过程当中的代码优化。
1)静态变量引发内存泄露
在代码优化的过程当中,咱们须要对代码中的静态变量特别留意。静态变量是类相关的变量,它的生命周期是从这个类被声明,到这个类完全被垃圾回收器回收才会被销毁。因此,通常状况下,静态变量从所在的类被使用开始就要一直占用着内存空间,直到程序退出。若是不注意,静态变量引用了占用大量内存的资源,形成垃圾回收器没法对内存进行回收,就可能形成内存的浪费。
先来看一段代码,这段代码定义了一个Activity。
这段代码中有一个静态的Resources对象。代码片断mResources = this.getResources()对Resources对象进行了初始化。这时Resources对象拥有了当前Activity对象的引用,Activity又引用了整个页面中全部的对象。
若是当前的Activity被从新建立(好比横竖屏切换,默认状况下整个Activity会被从新建立),因为Resources引用了第一次建立的Activity,就会致使第一次建立的Activity不能被垃圾回收器回收,从而致使第一次建立的Activity中的全部对象都不能被回收。这个时候,一部份内存就浪费掉了。
经验分享: 在实际项目中,咱们常常会把一些对象的引用加入到集合中,若是这个集合是静态的话,就须要特别注意了。当不须要某对象时,务必及时把它的引用从集合中清理掉。或者能够为集合提供一种更新策略,及时更新整个集合,这样能够保证集合的大小不超过某值,避免内存空间的浪费。 |
2)使用Application的Context
在Android中,Application Context的生命周期和应用的生命周期同样长,而不是取决于某个Activity的生命周期。若是想保持一个长期生命的对象,而且这个对象须要一个Context,就能够使用Application对象。能够经过调用Context.getApplicationContext()方法或者Activity.getApplication()方法来得到Application对象。
依然拿上面的代码做为例子。能够将代码修改为下面的样子。
在这里将this.getResources()修改成this.getApplication().getResources()。修改之后,Resources对象拥有的是Application对象的引用。若是Activity被从新建立,第一次建立的Activity就能够被回收了。
3)及时关闭资源
Cursor是Android查询数据后获得的一个管理数据集合的类。正常状况下,若是咱们没有关闭它,系统会在回收它时进行关闭,可是这样的效率特别低。若是查询获得的数据量较小时还好,若是Cursor的数据量很是大,特别是若是里面有Blob信息时,就可能出现内存问题。因此必定要及时关闭Cursor。
下面给出一个通用的使用Cursor的代码片断。
即对异常进行捕获,而且在finally中将cursor关闭。
一样的,在使用文件的时候,也要及时关闭。
4)使用Bitmap及时调用recycle()
前面的章节讲过,在不使用Bitmap对象时,须要调用recycle()释放内存,而后将它设置为null。虽然调用recycle()并不能保证当即释放占用的内存,可是能够加速Bitmap的内存的释放。
在代码优化的过程当中,若是发现某个Activity用到了Bitmap对象,却没有显式的调用recycle()释放内存,则须要分析代码逻辑,增长相关代码,在再也不使用Bitmap之后调用recycle()释放内存。
5)对Adapter进行优化
下面以构造ListView的BaseAdapter为例说明如何对Adapter进行优化。
在BaseAdapter类中提供了以下方法:
当ListView列表里的每一项显示时,都会调用Adapter的getView方法返回一个View,
来向ListView提供所须要的View对象。
下面是一个完整的getView()方法的代码示例。
当向上滚动ListView时,getView()方法会被反复调用。getView()的第二个参数convertView是被缓存起来的List条目中的View对象。当ListView滑动的时候,getView可能会直接返回旧的convertView。这里使用了convertView和ViewHolder,能够充分利用缓存,避免反复建立View对象和TextView对象。
若是ListView的条目只有几个,这种技巧并不能带来多少性能的提高。可是若是条目有几百甚至几千个,使用这种技巧只会建立几个convertView和ViewHolder(取决于当前界面可以显示的条目数),性能的差异就很是很是大了。
6)代码“微优化”
当今时代已经进入了“微时代”。这里的“微优化”指的是代码层面的细节优化,即不改动代码总体结构,不改变程序原有的逻辑。尽管Android使用的是Dalvik虚拟机,可是传统的Java方面的代码优化技巧在Android开发中也都是适用的。
下面简要列举一部分。由于通常Java开发者都可以理解,就再也不作具体的代码说明。
建立新的对象都须要额外的内存空间,要尽可能减小建立新的对象。
将类、变量、方法等等的可见性修改成最小。
针对字符串的拼接,使用StringBuffer替代String。
不要在循环当中声明临时变量,不要在循环中捕获异常。
若是对于线程安全没有要求,尽可能使用线程不安全的集合对象。
使用集合对象,若是事先知道其大小,则能够在构造方法中设置初始大小。
文件读取操做须要使用缓存类,及时关闭文件。
慎用异常,使用异常会致使性能下降。
若是程序会频繁建立线程,则能够考虑使用线程池。
经验分享: 代码的微优化有不少不少东西能够讲,小到一个变量的声明,大到一段算法。尤为在代码Review的过程当中,可能会反复审查代码是否能够优化。不过我认为,代码的微优化是很是耗费时间的,没有必要从头至尾将全部代码都优化一遍。开发者应该根据具体的业务逻辑去专门针对某部分代码作优化。好比应用中可能有一些方法会被反复调用,那么这部分代码就值得专门作优化。其它的代码,须要开发者在写代码过程当中去注意。 |
利用ViewHolder来优化ListView数据加载,仅仅就此一条吗?其实不是的,首先,想要优化ListView就得先了解ListView加载数据原理,这是前提,可是小马在这个地方先作一些简单的补充,你们必定仔细看下,保证会有收获的。
在整理前几篇文章的时候有朋友提出写一下ListView的性能优化方面的东西,这个问题也是小马在面试过程当中被别人问到的…..今天小马就借此机会来整理下,网上相似的资料蛮多的,倒不如本身写一篇,记录在这个地方,供本身之后使用,不用再翻来翻去的找了,用本身写的…呵呵,很少讲其它了,提及优化我想你们第一反应跟小马同样吧?想到利用ViewHolder来优化ListView数据加载,仅仅就此一条吗?其实不是的,首先,想要优化ListView就得先了解ListView加载数据原理,这是前提,可是小马在这个地方先作一些简单的补充,你们必定仔细看下,保证会有收获的:
ListVeiw: 用来展现列表的View。
适配器 : 用来把数据映射到ListView上
数据: 具体的将被映射的字符串,图片,或者基本组件。
根据列表的适配器类型,列表分为三种,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter,这三种适配器的使用你们可学习下官网上面的使用或者自行百度谷歌,一堆DEMO!!!其中以ArrayAdapter最为简单,只能展现一行字。SimpleAdapter有最好的扩充性,能够自定义出各类效果。SimpleCursorAdapter能够认为是SimpleAdapter对数据库的简单结合,能够方便的把数据库的内容以列表的形式展现出来。
系统要绘制ListView了,他首先用getCount()函数获得要绘制的这个列表的长度,而后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先得到一个View(这个看实际状况,若是是一个简单的显示则是View,若是是一个自定义的里面包含不少控件的时候它实际上是一个ViewGroup),而后再实例化并设置各个组件及其数据内容并显示它。好了,绘制完这一行了。那 再绘制下一行,直到绘完为止,前面这些东西作下铺垫,继续…….
如今咱们再来了解ListView加载数据的原理,有了这方面的了解后再说优化才行,下面先跟你们一块儿来看下ListView加载数据的基本原理小马就直接写了:
ListView 针对每一个item,要求 adapter “返回一个视图” (getView),也就是说ListView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值获得ListView的长度,而后根据这个长度,调用getView()一行一行的绘制ListView的每一项。若是你的getCount()返回值是0的话,列表一行都不会显示,若是返回1,就只显示一行。返回几则显示几行。若是咱们有几千几万甚至更多的item要显示怎么办?为每一个Item建立一个新的View?不可能!!!实际上Android早已经缓存了这些视图,你们能够看下下面这个截图来理解下,这个图是解释ListView工做原理的最经典的图了你们能够收藏下,不懂的时候拿来看看,加深理解,其实Android中有个叫作Recycler的构件,顺带列举下与Recycler相关的已经由Google作过N多优化过的东东好比:AbsListView.RecyclerListener、ViewDebug.RecyclerTraceType等等,要了解的朋友本身查下,不难理解,下图是ListView加载数据的工做原理(原理图看不清楚的点击后看大图):
执行程序,查看日志:
getView 被调用 9 次 ,convertView 对于全部的可见项目是空值(以下):
而后稍微向下滚动List,直到item10出现:
convertView仍然是空值,由于recycler中没有视图(item1的边缘仍然可见,在顶端)再滚动列表,继续滚动:
convertView不是空值了!item1离开屏幕到Recycler中去了,而后item11被建立,再滚动下:
此时的convertView非空了,在item11离开屏幕以后,它的视图(…0f8)做为convertView容纳item12了,好啦,结合以上原理,下面来看看今天最主要的话题,主角ListView的优化:
首先,这个地方先记两个ListView优化的一个小点:
1. ExpandableListView 与 ListActivity 由官方提供的,里面要使用到的ListView是已经通过优化的ListView,若是你们的需求能够用Google自带的ListView知足的的话尽可能用官方的,绝对没错!
2.其次,像小马前面讲的,说ListView优化,其实并非指其它的优化,就是内存是的优化,提到内存…(想到OOM,折腾了我很多时间),不少不少,先来写下,若是咱们的ListView中的选项仅仅是一些简单的TextView的话,就好办啦,消耗不了多少的,但若是你的Item是自定义的Item的话,例如你的自定义Item布局ViewGroup中包含:按钮、图片、flash、CheckBox、RadioButton等一系列你能想到的控件的话, 你要在getView中单单使用文章开头提到的ViewHolder是远远不够的,若是数据过多,加载的图片过多过大,你BitmapFactory.decode的猛多的话,OOM搞死你,这个地方再警告下你们,是警告……….也提醒下本身:
小马碰到的问题你们应该也都碰到过的,自定义的ListView项乱序问题,我很天真的在getView()中强制清除了下ListView的缓存数据convertView,也就是convertView = null了,虽然当时是解决了这个问题让其它每次重绘,可是犯了大错了,若是数据太多的话,出现最最恶心的错,手机卡死或强制关机,关机啊哥哥们……O_O,客户杀了我都有可能,但你们之后别犯这样的错了,单单使用清除缓存convertView是解决不了实际问题的,继续……
下面是小记:图片用完了正确的释放…
好啦,ListVIew的优化问题,小马就暂时先理解记录这么多了,若是朋友们有什么更好的优化建议什么的,留言指点下小马,必定会及时添加到进来的,先谢谢啦,其实在ListView适配器的getView()方法中能够作不少的优化,我记得还有能够优化findViewById()这个方法来寻址资源信息效率的方法,资料太多了,小马发现了会及时更新的哦,天太晚了,先休息了,吼吼,你们加油,一块儿努力学习!!!O_O
在Android应用里,最耗费内存的就是图片资源。并且在Android系统中,读取位图Bitmap时,分给虚拟机中的图片的堆栈大小只有8M,若是超出了,就会出现OutOfMemory异常。因此,对于图片的内存优化,是Android应用开发中比较重要的内容。
在Android应用里,最耗费内存的就是图片资源。并且在Android系统中,读取位图Bitmap时,分给虚拟机中的图片的堆栈大小只有8M,若是超出了,就会出现OutOfMemory异常。因此,对于图片的内存优化,是Android应用开发中比较重要的内容。
1) 要及时回收Bitmap的内存
Bitmap类有一个方法recycle(),从方法名能够看出意思是回收。这里就有疑问了,Android系统有本身的垃圾回收机制,能够不按期的回收掉不使用的内存空间,固然也包括Bitmap的空间。那为何还须要这个方法呢?
Bitmap类的构造方法都是私有的,因此开发者不能直接new出一个Bitmap对象,只能经过BitmapFactory类的各类静态方法来实例化一个Bitmap。仔细查看BitmapFactory的源代码能够看到,生成Bitmap对象最终都是经过JNI调用方式实现的。因此,加载Bitmap到内存里之后,是包含两部份内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了,可是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。因此须要调用recycle()方法来释放C部分的内存。从Bitmap类的源代码也能够看到,recycle()方法里也的确是调用了JNI方法了的。
那若是不调用recycle(),是否就必定存在内存泄露呢?也不是的。Android的每一个应用都运行在独立的进程里,有着独立的内存,若是整个进程被应用自己或者系统杀死了,内存也就都被释放掉了,固然也包括C部分的内存。
Android对于进程的管理是很是复杂的。简单的说,Android系统的进程分为几个级别,系统会在内存不足的状况下杀死一些低优先级的进程,以提供给其它进程充足的内存空间。在实际项目开发过程当中,有的开发者会在退出程序的时候使用Process.killProcess(Process.myPid())的方式将本身的进程杀死,可是有的应用仅仅会使用调用Activity.finish()方法的方式关闭掉全部的Activity。
经验分享: Android手机的用户,根据习惯不一样,可能会有两种方式退出整个应用程序:一种是按Home键直接退到桌面;另外一种是从应用程序的退出按钮或者按Back键退出程序。那么从系统的角度来讲,这两种方式有什么区别呢?按Home键,应用程序并无被关闭,而是成为了后台应用程序。按Back键,通常来讲,应用程序关闭了,可是进程并无被杀死,而是成为了空进程(程序自己对退出作了特殊处理的不考虑在内)。 Android系统已经作了大量进程管理的工做,这些已经能够知足用户的需求。我的建议,应用程序在退出应用的时候不须要手动杀死本身所在的进程。对于应用程序自己的进程管理,交给Android系统来处理就能够了。应用程序须要作的,是尽可能作好程序自己的内存管理工做。 |
通常来讲,若是可以得到Bitmap对象的引用,就须要及时的调用Bitmap的recycle()方法来释放Bitmap占用的内存空间,而不要等Android系统来进行释放。
下面是释放Bitmap的示例代码片断。
从上面的代码能够看到,bitmap.recycle()方法用于回收该Bitmap所占用的内存,接着将bitmap置空,最后使用System.gc()调用一下系统的垃圾回收器进行回收,能够通知垃圾回收器尽快进行回收。这里须要注意的是,调用System.gc()并不能保证当即开始进行回收过程,而只是为了加快回收的到来。
如何调用recycle()方法进行回收已经了解了,那何时释放Bitmap的内存比较合适呢?通常来讲,若是代码已经再也不须要使用Bitmap对象了,就能够释放了。释放内存之后,就不能再使用该Bitmap对象了,若是再次使用,就会抛出异常。因此必定要保证再也不使用的时候释放。好比,若是是在某个Activity中使用Bitmap,就能够在Activity的onStop()或者onDestroy()方法中进行回收。
2) 捕获异常
由于Bitmap是吃内存大户,为了不应用在分配Bitmap内存的时候出现OutOfMemory异常之后Crash掉,须要特别注意实例化Bitmap部分的代码。一般,在实例化Bitmap的代码中,必定要对OutOfMemory异常进行捕获。
如下是代码示例。
这里对初始化Bitmap对象过程当中可能发生的OutOfMemory异常进行了捕获。若是发生了OutOfMemory异常,应用不会崩溃,而是获得了一个默认的Bitmap图。
经验分享: 不少开发者会习惯性的在代码中直接捕获Exception。可是对于OutOfMemoryError来讲,这样作是捕获不到的。由于OutOfMemoryError是一种Error,而不是Exception。在此仅仅作一下提醒,避免写错代码而捕获不到OutOfMemoryError。 |
3) 缓存通用的Bitmap对象
有时候,可能须要在一个Activity里屡次用到同一张图片。好比一个Activity会展现一些用户的头像列表,而若是用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序自己的资源文件中的。
若是有相似上面的场景,就能够对同一Bitmap进行缓存。若是不进行缓存,尽管看到的是同一张图片文件,可是使用BitmapFactory类的方法来实例化出来的Bitmap,是不一样的Bitmap对象。缓存能够避免新建多个Bitmap对象,避免内存的浪费。
经验分享: Web开发者对于缓存技术是很熟悉的。其实在Android应用开发过程当中,也会常用缓存的技术。这里所说的缓存有两个级别,一个是硬盘缓存,一个是内存缓存。好比说,在开发网络应用过程当中,能够将一些从网络上获取的数据保存到SD卡中,下次直接从SD卡读取,而不从网络中读取,从而节省网络流量。这种方式就是硬盘缓存。再好比,应用程序常常会使用同一对象,也能够放到内存中缓存起来,须要的时候直接从内存中读取。这种方式就是内存缓存。 |
4) 压缩图片
若是图片像素过大,使用BitmapFactory类的方法实例化Bitmap的过程当中,须要大于8M的内存空间,就一定会发生OutOfMemory异常。这个时候该如何处理呢?若是有这种状况,则能够将图片缩小,以减小载入图片过程当中的内存的使用,避免异常发生。
使用BitmapFactory.Options设置inSampleSize就能够缩小图片。属性值inSampleSize表示缩略图大小为原始图片大小的几分之一。即若是这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4。
若是知道图片的像素过大,就能够对其进行缩小。那么如何才知道图片过大呢?
使用BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,即解码出来的Bitmap为null,可是可计算出原始图片的宽度和高度,即options.outWidth和options.outHeight。经过这两个值,就能够知道图片是否过大了。
在实际项目中,能够利用上面的代码,先获取图片真实的宽度和高度,而后判断是否须要跑缩小。若是不须要缩小,设置inSampleSize的值为1。若是须要缩小,则动态计算并设置inSampleSize的值,对图片进行缩小。须要注意的是,在下次使用BitmapFactory的decodeFile()等方法实例化Bitmap对象前,别忘记将opts.inJustDecodeBound设置回false。不然获取的bitmap对象仍是null。
经验分享: 若是程序的图片的来源都是程序包中的资源,或者是本身服务器上的图片,图片的大小是开发者能够调整的,那么通常来讲,就只须要注意使用的图片不要过大,而且注意代码的质量,及时回收Bitmap对象,就能避免OutOfMemory异常的发生。 若是程序的图片来自外界,这个时候就特别须要注意OutOfMemory的发生。一个是若是载入的图片比较大,就须要先缩小;另外一个是必定要捕获异常,避免程序Crash。 |
Java从JDK1.2版本开始,就把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
这里重点介绍一下软引用和弱引用。
若是一个对象只具备软引用,那么若是内存空间足够,垃圾回收器就不会回收它;若是内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就能够被程序使用。软引用可用来实现内存敏感的高速缓存。软引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
若是一个对象只具备弱引用,那么在垃圾回收器线程扫描的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。不过,因为垃圾回收器是一个优先级很低的线程,所以不必定会很快发现那些只具备弱引用的对象。弱引用也能够和一个引用队列(ReferenceQueue)联合使用,若是弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
弱引用与软引用的根本区别在于:只具备弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具备软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,一般不被回收。
在java.lang.ref包中提供了几个类:SoftReference类、WeakReference类和PhantomReference类,它们分别表明软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它能够和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对象的活动。
在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大并且声明周期较长的对象时候,能够尽可能应用软引用和弱引用技术。
下面以使用软引用为例来详细说明。弱引用的使用方式与软引用是相似的。
假设咱们的应用会用到大量的默认图片,好比应用中有默认的头像,默认游戏图标等等,这些图片不少地方会用到。若是每次都去读取图片,因为读取文件须要硬件操做,速度较慢,会致使性能较低。因此咱们考虑将图片缓存起来,须要的时候直接从内存中读取。可是,因为图片占用内存空间比较大,缓存不少图片须要不少的内存,就可能比较容易发生OutOfMemory异常。这时,咱们能够考虑使用软引用技术来避免这个问题发生。
首先定义一个HashMap,保存软引用对象。
再来定义一个方法,保存Bitmap的软引用到HashMap。
获取的时候,能够经过SoftReference的get()方法获得Bitmap对象。
使用软引用之后,在OutOfMemory异常发生以前,这些缓存的图片资源的内存空间能够被释放掉的,从而避免内存达到上限,避免Crash发生。
须要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象以后,get方法将返回null。因此在获取软引用对象的代码中,必定要判断是否为null,以避免出现NullPointerException异常致使应用崩溃。
经验分享: 到底何时使用软引用,何时使用弱引用呢? 我的认为,若是只是想避免OutOfMemory异常的发生,则能够使用软引用。若是对于应用的性能更在乎,想尽快回收一些占用内存比较大的对象,则能够使用弱引用。 还有就是能够根据对象是否常用来判断。若是该对象可能会常用的,就尽可能用软引用。若是该对象不被使用的可能性更大些,就能够用弱引用。 另外,和弱引用功能相似的是WeakHashMap。WeakHashMap对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,回收之后,其条目从映射中有效地移除。WeakHashMap使用ReferenceQueue实现的这种机制。 |