Android优化策略

本篇中,将带大家了解一下Android的一系列优化问题


   应用App内存的使用,是评价一个应用性能高低的重要指标,虽然智能手机的内存很大,但是一个好的应用应当将效率发挥到极致,做到精益求精,而现在很多应用,为了达到自己的利益,使用一些非常影响系统效率的方法,不仅破坏的Android的口碑,更影响了系统稳定性,很多应用在启动时启一个子线程,监听用户卸载应用,并且还不能被kill掉,导致内存的消耗不断的增高,影响了低端机的体验,因此我借鉴了很多网站和资料以及大牛的博客,把内存和用户体验放在首位,而不是满足自己的利益。


一:什么是内存?

       由于Android应用的沙箱机制,每个应用所分配的内存大小都是有限度的,内存太低就会触发LMK机制,一般包括以下几部分。

      1、寄存器(速度最快的存储场所,因为寄存器位于处理器内部,在程序中无法控制)

      2、栈(存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而在堆中)

      3、堆(用来存放new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理)

      4、静态存储区域(指在固定的位置存放应用程序运行时一直存在的数据,java在内存中划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量)

      5、常量池(JVM虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集合,包括直接常量-基本类型和其他类型、字段和方法的符号引用。....)

 

   基本的内存的介绍已经完成,谈一下如何优化吧~

二:优化模块--Bitmap 图片优化

1、图片 缩略
BitmapFactory.Options options = new BitmapFactory.Options()
options.inSampleSize = 2
Bitmap img = BitmapFactory.decodeFile("/sdcard/1.png", options)
该段代码便是读取1.png的缩略图,长度、宽度都只有原图片的1/2。图片大小削减,占用的内存天然也变小了。
使用这个方法,首先你要将BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片。然后将BitmapFactory.Options连同期望的宽度和高度一起传递到到calculateInSampleSize方法中,就可以得到合适的inSampleSize值了。之后再解析一次图片,使用新获取到的inSampleSize值,并把inJustDecodeBounds设置为false,就可以得到压缩后的图片了。

Bitmap的内存就是分配在dalvik堆中,即JAVA堆中的,调用recycle()并不能立即释放Native内存。但是调用recycle()也是一个良好的习惯。
    
2、使用框架  链接发给大家,可以学习一下
  1)      Picasso
  2)     Glide
  3)     Fresco
4)    ImageLoader

    

三:优化模块--资源、代码

1:注册没取消造成的内存泄露
  这种Android的内存泄露比纯java的内存泄露还要严重,因为其他一些Android程序可能引用我们的Anroid程序的对象(比如注册机制)。即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收。
比如示例:
   

假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。

  但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process进程挂掉。

  虽然有些系统程序,它本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在我们的程序中明确的取消注册,程序结束时应该把所有的注册都取消掉。

集合中对象没清理造成的内存泄露
  我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

2、资源对象没关闭造成的内存泄露
  资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。

  程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

              3、一些不良代码成内存压力
  有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,造成不必要的内存开支。

3.1,Bitmap没调用recycle()
  Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后才它设置为null.虽然recycle()从源码上看,调用它应该能立即释放Bitmap的主要内存,但是测试结果显示它并没能立即释放内存。但是我它应该还是能大大的加速Bitmap的主要内存的释放。

3.2,构造Adapter时,没有使用缓存的 convertView
  以构造ListView的BaseAdapter为例,在BaseAdapter中提共了方法:
public View getView(int position, View convertView, ViewGroup parent)来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。

  由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支。

----注:ViewHolder模式通过getView()方法返回的视图的标签(Tag)中存储一个数据结构,这个数据结构包含了指向我们要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById()。

四:优化模块--UI优化

      1:利用系统资源

说明:

Android系统本身有很多的资源,包括各种各样的字符串、图片、动画、样式和布局等等,这些都可以在应用程序中直接使用。这样做的好处很多,既可以减少内存的使用,又可以减少部分工作量,也可以缩减程序安装包的大小。

Android中没有公开的资源,在xml中直接引用会报错。除了去找到对应资源并拷贝到我们自己的应用目录下使用以外,我们还可以将引用“@android”改成“@*android”解决。

1:通用模块抽离

<include layout="@layout/title_bar" /> 
说明:
include引用布局相同布局,从而优化整个ui代码

五:优化模块--细节点优化

对常量使用static final修饰符

让我们来看看这两段在类前面的声明:

static int intVal = 42;
static String strVal = "Hello, world!";
编译器会生成一个叫做clinit的初始化类的方法,当类第一次被使用的时候这个方法会被执行。方法会将42赋给intVal,然后把一个指向类中常量表 的引用赋给strVal。当以后要用到这些值的时候,会在成员变量表中查找到他们。 下面我们做些改进,使用“final”关键字:

static final int intVal = 42;
static final String strVal = "Hello, world!";

现在,类不再需要clinit方法,因为在成员变量初始化的时候,会将常量直接保存到类文件中。用到intVal的代码被直接替换成42,而使用strVal的会指向一个字符串常量,而不是使用成员变量。

将一个方法或类声明为final不会带来性能的提升,但是会帮助编译器优化代码。举例说,如果编译器知道一个getter方法不会被重载,那么编译器会对其采用内联调用。

你也可以将本地变量声明为final,同样,这也不会带来性能的提升。使用“final”只能使本地变量看起来更清晰些(但是也有些时候这是必须的,比如在使用匿名内部类的时候)。

静态方法代替虚拟方法

如果不需要访问某对象的字段,将方法设置为静态,调用会加速15%到20%。这也是一种好的做法,因为你可以从方法声明中看出调用该方法不需要更新此对象的状态。


减少不必要的全局变量

尽量避免static成员变量引用资源耗费过多的实例,比如Context

因为Context的引用超过它本身的生命周期,会导致Context泄漏。所以尽量使用Application这种Context类型。 你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。 

避免创建不必要的对象

最常见的例子就是当你要频繁操作一个字符串时,使用StringBuffer代替String。

对于所有所有基本类型的组合:int数组比Integer数组好,这也概括了一个基本事实,两个平行的int数组比 (int,int)对象数组性能要好很多。

总体来说,就是避免创建短命的临时对象。减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。


避免内部Getters/Setters
在Android中,虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。

避免使用浮点数

通常的经验是,在Android设备中,浮点数会比整型慢两倍。


使用实体类比接口好

假设你有一个HashMap对象,你可以将它声明为HashMap或者Map:

Map map1 = new HashMap();
HashMap map2 = new HashMap();

哪个更好呢?

按照传统的观点Map会更好些,因为这样你可以改变他的具体实现类,只要这个类继承自Map接口。传统的观点对于传统的程序是正确的,但是它并不适合嵌入式系统。调用一个接口的引用会比调用实体类的引用多花费一倍的时间。如果HashMap完全适合你的程序,那么使用Map就没有什么价值。如果有些地方你不能确定,先避免使用Map,剩下的交给IDE提供的重构功能好了。(当然公共API是一个例外:一个好的API常常会牺牲一些性能)


避免使用枚举

枚举变量非常方便,但不幸的是它会牺牲执行的速度和并大幅增加文件体积。

使用枚举变量可以让你的API更出色,并能提供编译时的检查。所以在通常的时候你毫无疑问应该为公共API选择枚举变量。但是当性能方面有所限制的时候,你就应该避免这种做法了。

六:优化模块--哪些你所不知道的

        1、使用优化后的数据容器
请使用 Andorid 框架中优化过的数据容器,例如 SparseArray,SparseBooleanArray 和 LongSparseArray。类似于 HashMap 这一类的容器的效率不是很高,因为在每个 Map 中对于每一次的存放数据,他都需要独立一个单独的 Entry 对象进行传芳。而 SparseArray 由于禁止系统自动封装键值对,因此他更加有效率。并且你不需要担心丢失掉原有信息

2、小心内存花销

请对你正在使用的语言和依赖包拥有一定的了解,并且在你设计应用的整个阶段,都不要忽视它。通常大多数看起来无害的东西都可能让你花费大量的内存,比如说一下的几个:


1.枚举与静态常量相比,通常会消耗两倍的内存资源,因此你应该尽量避免在 Android 中使用枚举类型


2.Java 中的每一个类(包括匿名内部类),都会消耗大约500比特内存


3.每一个类对象都会消耗12-16比特内存


4.把单个 Entry 放入 HashMap 需要多消耗32比特的内存(原因请参看上一小节,使用优化后的数据容器)


虽然这里的消耗看起来比较少,但是他们累计起来就很大了,应用中设计那些重量级的类就很可能承受这些内存花销。这会使你的堆分析变得困难起来,你很难发现你的问题其实是因为很多小的对象正在占用你的内存。

3、小心抽象代码


通常,开发者都会将抽象作为一种好的编程习惯,因为抽象可以提升代码的灵活性和可维护性。但是,抽象方法可能带来很多的额外花费,例如当他们执行的时候,他们拥有大量的代码,并且他们会被多次映射到内存中占用更多的内存,因此如果抽象的效果不是很好,那么最好放弃他

对于序列化数据使用 Protobufs

Protocol buffers 是谷歌一种跨语言,跨平台的结构化序列数据,相比于 XML,他更小,更快,更简单。如果你决定让你的数据使用 Protobufs,你应用总是在你的客户端使用纳米级的Protobufs。规则的Protobufs会产生极其冗余的代码,这可能会导致应用产生各种问题:增加内存使用,APK包体增加,执行效率变慢,打破Dex的符号限制。

更多的信息,请参看protobuf readme

       4、避免依赖注入框架

使用类似于 Guice 和 RoboGuice 的依赖注射框架,或许会使你的代码变得更加漂亮,因为他们能够减少你需要写的代码,并且为测试或者在其他条件改变的情况下,提供一种自适应的环境。但是,这些框架在初始化的时候会因为注释而消耗大量的工作在扫描你的代码上,这会让你的代码在进行内存映射的时候花费更多的资源。虽然这些内存能够被 Android 进行回收,但是等待整个分页被释放需要很长一段时间。

小心使用外部依赖包

很多依赖包都不是专门为了移动环境或者移动客户端写的。如果你决定使用一个外部依赖包,你应该提前明白你需要为了将它移植到移动端而消耗花费大量的时间和工作量。请在使用外部依赖包得时候提前分析他的代码和内存占用

即使依赖包是为了 Android 而设计的,但是这也有潜伏的危险,因为每一个包都做着不同的工作。例如,有一个依赖包使用纳米级的 protobufs 但是别的包使用微米级的 protobufs.那么现在在你的应用中就有两套 protobuf 的标准了。这会在你记录数据,分析数据,加载图像,缓存,或者其他任何可能的情况下发生你不希望发生的事情。ProGuard 无法在这里帮助你,因为他们都是你所依赖包的底层实现,。当你使用从别的依赖包(他可能继承了很多的依赖包)里继承的 Activity 时,这个问题变得尤其严重,当你使用反射以及干别的事情的时候


请注意不要落入一个依赖包的陷阱,你不希望引入一大片你根本不会使用到的代码。如果你无法找到一种已经实现的逻辑来完全满足你的需求,那么你尽量创建一个自己的实现方式。

5、优化整体性能


很多关于优化应用的整体性能的信息被放到了 Best Practices for Performance,这里的很多文章介绍了如何优化 CPU 性能,但是很多小提示能够帮助你优化你的应用的内存使用,比如说通过减少你ui 的布局元素。

你应该读一下优化你的 ui,并使用布局调试工具来进行优化,另外可以通过 lint 工具来进行优化

使用 ProGuard 来剔除你不用的代码


ProGuard 工具能够通过移除不用的代码以及对类,方法和标量进行无意义的重命名来起到回收,优化和混淆代码的作用,使用 ProGuard 能够使你的代码变得更紧凑,而且减低内存消耗

使用 Zipalign 来优化你的 Apk

如果你对你生成的 APK 文件做了后期优化,那么你必须要使用 Zipalign 来让他对齐字节。不这样做可能会导致你的应用因为从 APK 里的资源不能被很好的映射到内存里而消耗更多的内存。

注意:现在 Google 市场不接受没有通过 Zipalign 处理过的 APK 文件

6、分析你的内存使用

一旦你已经拥有一个稳定版本的应用,那么就从他的整个生命周期开始分析你应用的使用内存。更多关于你应用的内存分析,请查看Investigating Your RAM Usage.


七:优化模块--多线程

1、Thread+Handler


Handler来根据接收的消息,处理UI更新。Thread线程发出Handler消息,通知更新UI。

      2、AsyncTask

Android的AsyncTask比Handler更轻量级一些,适用于简单的异步处理。

AsyncTask定义了三种泛型类型 Params,Progress和Result。


Params 启动任务执行的输入参数,比如HTTP请求的URL。
Progress 后台任务执行的百分比。
Result 后台执行任务最终返回的结果,比如String。


使用AsyncTask类,以下是几条必须遵守的准则:

Task的实例必须在UI thread中创建;
execute方法必须在UI thread中调用;
不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...),onProgressUpdate(Progress...)这几个方法;
该task只能被执行一次,否则多次调用时将会出现异常;


七:总结完成--结束语

到这里,基本就完成了,借鉴了网上大牛们的好多文章,还有什么不足希望大家可以提提建议,以便补充!参考文章:http://blog.csdn.net/hewence1/article/details/39004301


http://www.2cto.com/kf/201411/348847.html


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值