首页 > 代码库 > Android 性能优化
Android 性能优化
布局优化
Android性能优化之如何避免Overdraw(可参考)
(1)避免Overdraw
Overdraw,过度绘制会浪费很多的cpu,Gpu资源,例如系统默认会绘制Activity的背景,如果在给布局重新绘制了重叠的背景,那么默认的Activity的背景就属于无效的过度绘制。
过度绘制(Overdraw)也是很浪费CPU/GPU资源的,系统也提供了检测工具Debug GPU Overdraw来查看界面overdraw的情况。该工具会使用不同的颜色绘制屏幕,来指示overdraw发生在哪里以及程度如何,其中:
没有颜色: 意味着没有overdraw。像素只画了一次。
蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)。
绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。
浅红: 意味着overdraw 3倍。像素绘制了四次,小范围可以接受。
暗红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。
(2)优化布局层次
在Android中,系统对View的进行测量、布局和绘制时,都是通过对View树的遍历来进行操作的。如果一个View的高度太高,就会影响测量、布局和绘制的速度,因此优化布局的第一个方法就是降低View树的高度,Google在器API文档中也建议View树的高度不超过10层。
避免嵌套过多无用布局:
①使用<include>标签重用layout
在一个应用程序中界面中,为了风格上的统一,很多界面都会存在一些共通的UI,对于这些共通的UI,如果在每个界面中都来复制一段这样的代码,不仅不利于后期的维护,更增加了程序的冗余性。因此可以使用<include>标签来定义这样一个共通的UI。
我们先来定义一个简单的共通的UI:
代码中,将 android:layout_width="0dp" 和android:layout_height="0dp"都设置为0,这样迫使开发者在使用时对宽高进行赋值,否则无法看见这个界面。
<?xml version="1.0" encoding="utf-8"?></span><span style="font-family:微软雅黑;"> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="0dp" android:layout_height="0dp" android:textSize="30dp" android:gravity="center" android:text="this is a common ui" />
<include layout="@layout/common_ui" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="wrap_content"/>
如果需要在<include>标签中覆盖类似原布局中的android:layout_xxx的属性,就必须在标签中同时指定android:layout_width和android:layout_height属性。
②使用实现view的延迟加载
<ViewStub>是一个非常轻量级的组件,它不仅不可见,而且大小为0。
首先创建一个布局,这个布局在初始化加载时不需要显示,只在某些情况下才显示出来,例如查看用户信息的时,只有点击了某个按钮是,用户详细信息才显示出来。我们写一个简单的布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="not often use layout" android:textSize="30sp"/> </LinearLayout>
接下来与使用<include>标签类似,在主布局中的<ViewStub>中的layout属性来引用上面的布局。
<ViewStub android:id="@+id/not_often_use" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout="@layout/not_often_use"/>
如何重新加载显示的布局呢?
首先,通过普通的findViewById()方法找到<ViewStub>组件,这点与一般的组件基本相同:
mViewStub = (ViewStub)findViewById(R.id.not_often_use);
接下来,有两种方式重新显示这个View.
(1)VISIBLE
通过调用ViewStub的setVisibility()方法来显示这个View,代码如下所示:
mViewStub.setVisibility(View.VISIBLE);
( 2 ) inflate
通过调用ViewStub的inflate()方法来显示这个View,代码如下:
View inflateView = mViewStub.inflate();
这两种方式都可以让ViewStub重新展开,显示引用的布局,而唯一的区别就是inflate()方法可以返回引用的布局,从而可以在通过View.findViewById()方法来找到对应的控件,代码如下:
View inflateView = mViewStub.inflate();
TextView textview = (TextView) inflateView.findViewById(R.id.Tv);
textView.setText(“Hello“);
ViewStub和View.GONE有啥区别?
它们的共同点是初始时都不会显示,但是前者只会在显示时才去渲染整个布局,而后者在初始化布局树的时候就已经添加到布局树上了,相比之下前者的布局具有更高的效率。
③Hierarchy Viewer:查看视图树的工具
内存优化
(1)Bitmap优化
使用适当分辨率和大小的图片;
及时回收内存:从Android 3.0开始,Bitmap被放置到了堆中,其内存由GC管理,所以不用手动调用bitmap.recycle()方法进行释放了;
使用图片缓存:设计内存缓存和磁盘缓存可以更好地利用Bitmap。
(2)代码优化
使用静态方法,它比普通方法会提高15%左右的访问速度;
尽量不要使用枚举,少用迭代器;
对Cursor、Receiver、Sensor、File等对象,要非常注意对它们的创建、回收与注册、解注册;
使用SurfaceView来替代view进行大量的、频繁的绘图操作;
尽量使用视图缓存,而不是每次都执行inflate方法解析视图。
怎么加速启动Activity
Android性能优化之加快应用启动速度
Android性能优化之Splash页应该这样设计
启动方式
通常来说,在安卓中应用的启动方式分为两种:冷启动和热启动。
1、冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。
2、热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。
特点
1、冷启动:冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
2、热启动:热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。
应用启动的流程
在安卓系统上,应用在没有进程的情况下,应用的启动都是这样一个流程:当点击app的启动图标时,安卓系统会从Zygote(|za?g??t|受精卵)进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类、创建MainActivity类、加载主题样式Theme中的windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上,所以直到这里,应用的第一次启动才算完成,这时候我们看到的界面也就是所说的第一帧。
所以,总结一下,应用的启动流程如下:
Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。
OOM 问题如何处理
http://blog.csdn.net/wangqilin8888/article/details/7752528
图片OOM处理方法:Android高效加载大图、多图解决方案,有效避免程序OOM
a.对高分辨的图片进行压缩
b.使用图片缓存技术,最核心的类是LruCache
OOM主要原因有两个:
1.由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放。
2. 保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。
针对图片过大的OOM的解决方法:
1)高效加载大图片,利用图片压缩。
我们怎样才能对图片进行压缩呢?通过设置BitmapFactory.Options中inSampleSize的值就可以实现。比如我们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素。原本加载这张图片需要占用13M的内存,压缩后就只需要占用0.75M了(假设图片是ARGB_8888类型,即每个像素点占用4个字节)。
2)利用图片缓存技术
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
内存溢出问题如何处理
1.使用更加轻量的数据结构
例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。
2.避免在Android里面使用Enum
Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,所以请避免在Android里面使用到枚举。
3.减小Bitmap对象的内存占用
Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,,通常来说有以下2个措施:
inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异
4.Bitmap对象的复用
缩小Bitmap的同时,也需要提高BitMap对象的复用率,避免频繁创建BitMap对象,复用的方法有以下2个措施
LRUCache:“最近最少使用算法”在Android中有极其普遍的应用。ListView与GridView等显示大量图片的控件里,就是使用LRU的机制来缓存处理好的Bitmap,把近期最少使用的数据从缓存中移除,保留使用最频繁的数据,
inBitMap高级特性:利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小
5.使用更小的图片
在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。
6.StringBuilder
在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
7.避免在onDraw方法里面执行对象的创建
类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
8.避免对象的内存泄露
https://drakeet.me/android-leaks
Android性能优化之常见的内存泄漏
·类的静态变量持有大数据对象
静态变量长期维持到大数据对象的引用,阻止垃圾回收。
·非静态内部类存在静态实例
非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉。
·资源对象未关闭
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们, 以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。 如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。
解决办法: 比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭), 如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。 因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null. 在我们的程序退出时一定要确保我们的资源性对象已经关闭。 程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小, 对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险,记得try catch后,在finally方法中关闭连接
Handler内存泄漏(?)
Handler作为内部类存在于Activity中,但是Handler生命周期与Activity生命周期往往并不是相同的,比如当Handler对象有Message在排队,则无法释放,进而导致本该释放的Acitivity也没有办法进行回收。
解决办法:
1.声明handler为static类,这样内部类就不再持有外部类的引用了,就不会阻塞Activity的释放
2.如果内部类实在需要用到外部类的对象,可在其内部声明一个弱引用引用外部类。
public class MainActivity extends Activity { private CustomHandler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler = new CustomHandler(this); } static class CustomHandlerextends Handler { // 内部声明一个弱引用,引用外部类 private WeakReference<MainActivity > activityWeakReference; public MyHandler(MyActivity activity) { activityWeakReference= new WeakReference<MainActivity >(activity); } // ... ... } }
3.在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable
@Override public void onDestroy() { // If null, all callbacks and messages will be removed. mHandler.removeCallbacksAndMessages(null); }
·一些不良代码习惯
有些代码并不造成内存泄露,但是他们的资源没有得到重用,频繁的申请内存和销毁内存,消耗CPU资源的同时,也引起内存抖动
解决方案
如果需要频繁的申请内存对象和和释放对象,可以考虑使用对象池来增加对象的复用。 例如ListView便是采用这种思想,通过复用converview来避免频繁的GC
Android 性能优化