Android app优化策略

来源:互联网 发布:js获取video播放时间 编辑:程序博客网 时间:2024/05/28 16:28

App优化主要在于UI,内存,代码等方面的优化。优化之后app运行会更加流畅,不会出现ANR或者OOM异常,能适用更多的android机型,从而使使用户体验更佳。现在介绍下UI、内存、代码方面的优化。

一、UI 优化

如果布局写得糟糕的话,那么程序加载UI的速度就会非常慢,从而造成不好的用户体验。由于Android去解析和展示一个布局是需要消耗时间的,布局如果嵌套的越多越深,那么解析起来就越耗时,性能也就越差,因此我们在编写布局文件时的宗旨是让嵌套的层数越少越好。

优化UI布局的方法:合理使用RelativeLayout代替LinearLayout布局、多使用抽象布局标签(include, viewstub, merge)、去除不必要的嵌套和View节点、减少不必要的inflate。

AndroidSDK有自带的查看UI布局的工具-hierarchy viewer

1、<include>标签,<merge>标签,<viewstub>标签介绍

(1) <include>标签

include标签常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,这在布局编写方便提供了大大的便利,能节约内存,缩减apk大小。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <ListView        android:id="@+id/simple_list_view"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_marginBottom="@dimen/dp_80" />    <include layout="@layout/foot" /></RelativeLayout>


其中include引入的foot.xml为公用的页面底部,代码如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <Button        android:id="@+id/button"        android:layout_width="match_parent"        android:layout_height="@dimen/dp_40"        android:layout_above="@+id/text"/>    <TextView        android:id="@+id/text"        android:layout_width="match_parent"        android:layout_height="@dimen/dp_40"        android:layout_alignParentBottom="true"        android:text="@string/app_name" /></RelativeLayout>


(2) <merge>标签

在使用了include后可能导致布局嵌套过多,多余不必要的layout节点,从而导致解析变慢。可通过hierarchy viewer工具来查看不必要的节点和嵌套

需谨记该标签必须在根节点使用。

merge标签可用于两种典型情况:

a.布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容试图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。

b.某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。

以(1) <include>标签的示例为例,用hierarchy viewer查看main.xml布局如下图:



可以发现多了一层没必要的RelativeLayout,将foot.xml中RelativeLayout改为merge,

<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <Button        android:id="@+id/button"        android:layout_width="match_parent"        android:layout_height="@dimen/dp_40"        android:layout_above="@+id/text"/>    <TextView        android:id="@+id/text"        android:layout_width="match_parent"        android:layout_height="@dimen/dp_40"        android:layout_alignParentBottom="true"        android:text="@string/app_name" /></merge>


运行后再次用hierarchy viewer查看main.xml布局如下图:


(3) <viewstub>标签  Android布局性能优化—从源码角度看ViewStub延迟加载技术

viewstub标签同include标签一样可以用来引入一个外部布局,不同的是,viewstub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省cpu和内存。但是每个布局都必须要指定layout_width和layout_height属性,否则运行就会报错。

viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。
ViewStub所加载的布局是不可以使用<merge>标签的,因此这有可能导致加载出来的布局存在着多余的嵌套结构,具体如何去取舍就要根据各自的实际情况来决定了,对于那些隐藏的布局文件结构相当复杂的情况,使用ViewStub还是一种相当不错的选择的,即使增加了一层无用的布局结构,仍然还是利大于弊。

下面以在一个布局main.xml中加入网络错误时的提示页面network_error.xml为例。main.mxl代码如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" > ……    <ViewStub        android:id="@+id/network_error_layout"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout="@layout/network_error" /></RelativeLayout>


其中network_error.xml为只有在网络错误时才需要显示的布局,默认不会被解析,示例代码如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <Button        android:id="@+id/network_setting"        android:layout_width="@dimen/dp_160"        android:layout_height="wrap_content"        android:layout_centerHorizontal="true"        android:text="@string/network_setting" />    <Button        android:id="@+id/network_refresh"        android:layout_width="@dimen/dp_160"        android:layout_height="wrap_content"        android:layout_below="@+id/network_setting"        android:layout_centerHorizontal="true"        android:layout_marginTop="@dimen/dp_10"        android:text="@string/network_refresh" /></RelativeLayout>

在java中通过(ViewStub)findViewById(id)找到ViewStub,通过stub.inflate()展开ViewStub,然后得到子View,如下:
private View networkErrorView;private void showNetError() { // not repeated infalte if (networkErrorView != null) {  networkErrorView.setVisibility(View.VISIBLE);  return; } ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout); networkErrorView = stub.inflate(); Button networkSetting = (Button)networkErrorView.findViewById(R.id.network_setting); Button refresh = (Button)findViewById(R.id.network_refresh);}private void showNormal() { if (networkErrorView != null) {  networkErrorView.setVisibility(View.GONE); }


2、去除不必要的嵌套和View节点

(1) 首次不需要使用的节点使用viewstub。

虽然可以设置为Gone,但是该view控件仍然会被创建对象,被实例化,还是会消耗内存等资源。

(2) 使用RelativeLayout代替LinearLayout

大约在Android4.0之前,新建工程的默认main.xml中顶节点是LinearLayout,而在之后已经改为RelativeLayout,因为RelativeLayout性能更优,且可以简单实现LinearLayout嵌套才能实现的布局。

3、减少不必要的infalte

(1) 对于inflate的布局可以直接缓存,用全局变量代替局部变量,避免下次需再次inflate

如上面ViewStub示例中的

<pre name="code" class="java">private View networkErrorView;private void showNetError() { // not repeated infalte if (networkErrorView != null) {  networkErrorView.setVisibility(View.VISIBLE);  return; } ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout); networkErrorView = stub.inflate(); Button networkSetting = (Button)networkErrorView.findViewById(R.id.network_setting); Button refresh = (Button)findViewById(R.id.network_refresh);}private void showNormal() { if (networkErrorView != null) {  networkErrorView.setVisibility(View.GONE); }}

(2) ListView提供了item缓存,adapter getView的标准写法,如下:

@Overridepublic View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) {  convertView = inflater.inflate(R.layout.list_item, null);  holder = new ViewHolder();  ……  convertView.setTag(holder); } else {  holder = (ViewHolder)convertView.getTag(); }}/** * ViewHolder *  * @author trinea@trinea.cn 2013-08-01 */private static class ViewHolder { ImageView appIcon; TextView appName; TextView appInfo;}


4、合理利用Android系统本身的资源

Android系统中有很多的资源,包括各种各样的字符串、图片、动画、样式和布局等等,这些都可以在应用程序中直接使用。这样做能减少内存,顺带缩减程序安装包的大小。

1)利用系统定义的id

比如我们有一个定义ListView的xml文件,一般的,我们会写类似下面的代码片段。
<ListView     android:id="@+id/mylist"     android:layout_width="fill_parent"     android:layout_height="fill_parent"/>

这里我们定义了一个ListView,定义它的id是"@+id/mylist"。实际上,如果没有特别的需求,就可以利用系统定义的id,类似下面的样子。
<ListView     android:id="@android:id/list"     android:layout_width="fill_parent"     android:layout_height="fill_parent"/> 

在xml文件中引用系统的id,只需要加上“@android:”前缀即可。如果是在Java代码中使用系统资源,和使用自己的资源基本上是一样的。不同的是,需要使用android.R类来使用系统的资源,而不是使用应用程序指定的R类。这里如果要获取ListView可以使用android.R.id.list来获取。
2)利用系统的图片资源,参考上面的例子。
3)利用系统的字符串资源,参考上面的例子。
4)利用系统的Style,参考上面的例子。
5)利用系统的颜色定义,参考上面的例子。

5、其他的方面

(1) 用SurfaceView或TextureView代替普通View
SurfaceView或TextureView可以通过将绘图操作移动到另一个单独线程上提高性能。
普通View的绘制过程都是在主线程(UI线程)中完成,如果某些绘图操作影响性能就不好优化了,这时我们可以考虑使用SurfaceView和TextureView,他们的绘图操作发生在UI线程之外的另一个线程上。
因为SurfaceView在常规视图系统之外,所以无法像常规试图一样移动、缩放或旋转一个SurfaceView。TextureView是Android4.0引入的,除了与SurfaceView一样在单独线程绘制外,还可以像常规视图一样被改变。
(2) 使用RenderJavascript
RenderScript是Adnroid3.0引进的用来在Android上写高性能代码的一种语言,语法给予C语言的C99标准,他的结构是独立的,所以不需要为不同的CPU或者GPU定制代码代码。
(3) 使用OpenGL绘图
Android支持使用OpenGL API的高性能绘图,这是Android可用的最高级的绘图机制,在游戏类对性能要求较高的应用中得到广泛使用。
Android 4.3最大的改变,就是支持OpenGL ES 3.0。相比2.0,3.0有更多的缓冲区对象、增加了新的着色语言、增加多纹理支持等等,将为Android游戏带来更出色的视觉体验。
(4) 尽量为所有分辨率创建资源
减少不必要的硬件缩放,这会降低UI的绘制速度,可借助Android asset studio

二、内存优化,避免出现OOM异常
虽说现在的手机内存都已经非常大了,但是我们大家都知道,系统是不可能将所有的内存都分配给我们的应用程序的,也就是说每个程序都会有可使用的内存上限,这被称为堆大小(Heap Size)。不同的手机,堆大小也不尽相同,随着现在硬件设备不断提高,堆大小也已经由Nexus One时的32MB,变成了Nexus 5时的192MB。如果大家想要知道自己手机的堆大小是多少,可以调用如下代码:
ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);  int heapSize = manager.getMemoryClass();

结果是以MB为单位进行返回的,我们在开发应用程序时所使用的内存不能超出这个限制,否则就会出现OutOfMemoryError。

系统内存回收-Android的GC操作,GC全称是Garbage Collection,也就是所谓的垃圾回收。Android系统会在适当的时机触发GC操作,一旦进行GC操作,就会将一些不再使用的对象进行回收。那么什么时候会触发GC操作呢?这个通常都是由系统去决定的,我们一般情况下都不需要主动通知系统应该去GC了,但是我们仍然可以去监听系统的GC过程,以此来分析我们应用程序当前的内存状态。那么怎样才能去监听系统的GC过程呢?其实非常简单,系统每进行一次GC操作时,都会在LogCat中打印一条日志,我们只要去分析这条日志就可以了,日志的基本格式如下所示:

D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <Pause_time>

通过日志的方式我们可以简单了解到系统的GC工作情况,但是如果我们想要更加清楚地实时知晓当前应用程序的内存使用情况,只通过日志就有些力不从心了,我们需要通过DDMS中提供的工具来实现。如果你发现反复操作某一功能会导致应用程序内存持续增高而不会下降的话,那么就说明这里很有可能发生内存泄漏了。出现了内存泄露,我们应该怎么定位到具体是哪里出的问题呢?这就需要借助一个内存分析工具了,叫做Eclipse Memory Analyzer(MAT)。点击打开链接

现在说下内存优化的注意事项:

1、bitmap处理方案

bitmap占用的内存大小不是图片的大小,而是其图片像素*4byte, 所以针对bitmap占内存的问题,合理使用裁剪,缓存,软引用或者弱引用,并及时的bitmap.recycle()。这些操作都能有效避免内存OutOfMemoryError。

2、使用软引用和弱引用

Java从JDK1.2版本开始,就把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。弱引用与软引用的根本区别在于:只具有弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具有软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,通常不被回收。

3、使用优化过的数据集合Android API当中提供了一些优化过后的数据集合工具类,如SparseArray,SparseBooleanArray,以及LongSparseArray等,使用这些API可以让我们的程序更加高效。

传统Java API中提供的HashMap工具类会相对比较低效,因为它需要为每一个键值对都提供一个对象入口,而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间。

a)使用枚举通常会比使用静态常量要消耗两倍以上的内存,在Android开发当中我们应当尽可能地不使用枚举

b)任何一个Java类,包括内部类、匿名类,都要占用大概500字节的内存空间。

c)任何一个类的实例要消耗12-16字节的内存开支,因此频繁创建实例也是会一定程序上影响内存的。

d)在使用HashMap时,即使你只设置了一个基本数据类型的键,比如说int,但是也会按照对象的大小来分配内存,大概是32字节,而不是4字节。因此最好的办法就是像上面所说的一样,使用优化过的数据集合。

4、对常量使用static final修饰符

因为所有的常量都会在dex文件的初始化器当中进行初始化。这种优化方式只对基本数据类型以及String类型的常量有效,对于其它数据类型的常量是无效的。不过,对于任何常量都是用static final的关键字来进行声明仍然是一种非常好的习惯。

5、使用增强型for循环语法
增强型for循环(也被称为for-each循环)可以用于去遍历实现Iterable接口的集合以及数组,这是jdk 1.5中新增的一种循环模式。当然除了这种新增的循环模式之外,我们仍然还可以使用
原有的普通循环模式,只不过它们之间是有效率区别的,我们来看下面一段代码:

static class Counter {      int mCount;  }  Counter[] mArray = ...  public void zero() {      int sum = 0;      for (int i = 0; i < mArray.length; ++i) {          sum += mArray[i].mCount;      }  }  public void one() {      int sum = 0;      Counter[] localArray = mArray;      int len = localArray.length;      for (int i = 0; i < len; ++i) {          sum += localArray[i].mCount;      }  }  public void two() {      int sum = 0;      for (Counter a : mArray) {          sum += a.mCount;      }  } 

可以看到,上述代码当中我们使用了三种不同的循环方式来对mArray中的所有元素进行求和。其中zero()方法是最慢的一种,因为它是把mArray.length写在循环当中的,也就是说每循环一次都需要重新计算一次mArray的长度。而one()方法则相对快得多,因为它使用了一个局部变量len来记录数组的长度,这样就省去了每次循环时字段搜寻的时间。two()方法在没有JIT(Just In Time Compiler)的设备上是运行最快的,而在有JIT的设备上运行效率和one()方法不相上下,唯一需要注意的是这种写法需要JDK 1.5之后才支持。

但是这里要跟大家提一个特殊情况,对于ArrayList这种集合,自己手写的循环要比增强型for循环更快,而其他的集合就没有这种情况。因此,对于我们来说,默认情况下可以都使用增强型for循环,而遍历ArrayList时就还是使用传统的循环方式吧。

6、try_catch 方法的使用

初衷是为了防止出现不可预见的bug,保持程序的健壮性。但很多人把这当做万能的保险,有些可预见bug判断没做,会造成执行该方法之后抛出一个异常。抛出一个异常也需要手机内存的。所以我们一定要代码严谨,正确理解try_catch的方法。

7、多使用系统封装好的API

因为使用系统的API在很多时候比我们自己写的代码要快得多,它们的很多功能都是通过底层的汇编模式执行的。

8、避免在内部调用Getters/Setters方法

因为字段搜寻要比方法调用效率高得多,我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。不过我们肯定不能仅仅因为效率的原因就将封装这个技巧给抛弃了,编写代码还是要按照面向对象思维的,但是我们可以在能优化的地方进行优化,比如说避免在内部调用getters/setters方法。

那什么叫做在内部调用getters/setters方法呢?这里我举一个非常简单的例子:

public class Calculate {      private int one = 1;      private int two = 2;      public int getOne()   {           return one;      }      public int getTwo()   {          return two;          }      public int getSum()    {                   return getOne() + getTwo();         }  } 

9、静态优于抽象

在Android上使用抽象会带来额外的内存开支,因为抽象的编程方法需要编写额外的代码,虽然这些代码根本执行不到,但是却也要映射到内存当中,不仅占用了更多的内存,在执行效率方面也会有所降低。当然这里我并不是提倡大家完全不使用抽象编程,而是谨慎使用抽象编程,不要认为这是一种很酷的编程方式而去肆意使用它,只在你认为有必要的情况下才去使用。

如果你并不需要访问一个对象中的某些字段,只是想调用它的某个方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,这会让调用的速度提升15%-20%,同时也不用为了调用这个方法而去专门创建对象了,这样还满足了上面的一条原则。另外这也是一种好的编程习惯,因为我们可以放心地调用静态方法,而不用担心调用这个方法后是否会改变对象的状态(静态方法内无法访问非静态字段)。

10、尽量避免使用依赖注入框架现在有很多人都喜欢在Android工程当中使用依赖注入框架,比如说像Guice或者RoboGuice等,因为它们可以简化一些复杂的编码操作,比如可以将下面的一段代码:

class AndroidWay extends Activity {       TextView name;       ImageView thumbnail;         LocationManager loc;      Drawable icon;          String myName;      public void onCreate(Bundle savedInstanceState) {                   super.onCreate(savedInstanceState);                         setContentView(R.layout.main);                   name = (TextView) findViewById(R.id.name);                    thumbnail = (ImageView) findViewById(R.id.thumbnail);                    loc = (LocationManager) getSystemService(Activity.LOCATION_SERVICE);                    icon = getResources().getDrawable(R.drawable.icon);                    myName = getString(R.string.app_name);                    name.setText( "Hello, " + myName );                 } }

简化成这样的一种写法:

@ContentView(R.layout.main) class RoboWay extends RoboActivity {        @InjectView(R.id.name) TextView name;              @InjectView(R.id.thumbnail) ImageView thumbnail;       @InjectResource(R.drawable.icon) Drawable icon;       @InjectResource(R.string.app_name) String myName;    @Inject LocationManager loc;      public void onCreate(Bundle savedInstanceState) {           super.onCreate(savedInstanceState);                      name.setText( "Hello, " + myName );                }   }

看上去确实十分诱人,我们甚至可以将findViewById()这一类的繁琐操作全部省去了。但是这些框架为了要搜寻代码中的注解,通常都需要经历较长的初始化过程,并且还可能将一些你用不到的对象也一并加载到内存当中。这些用不到的对象会一直占用着内存空间,可能要过很久之后才会得到释放,相较之下,也许多敲几行看似繁琐的代码才是更好的选择。

三、代码优化

1、及时关闭资源,比如cursor.close,使用Bitmap及时调用recycle()等。

Cursor cursor = null; try{      cursor = mContext.getContentResolver().query(uri,null,null,null,null);          if (cursor != null)      {                     cursor.moveToFirst();             // 处理数据           }       } catch (Exception e)      {                e.printStatckTrace();        } finally {               if (cursor != null){                             cursor.close();              } } 

2、静态变量引起内存泄露

在代码优化的过程中,我们需要对代码中的静态变量特别留意。静态变量是类相关的变量,它的生命周期是从这个类被声明,到这个类彻底被垃圾回收器回收才会被销毁。所以,一般情况下,静态变量从所在的类被使用开始就要一直占用着内存空间,直到程序退出。如果不注意,静态变量引用了占用大量内存的资源,造成垃圾回收器无法对内存进行回收,就可能造成内存的浪费。

先来看一段代码,这段代码定义了一个Activity。

private static Resources mResources;   @Override protected void onCreate(Bundle state) {     super.onCreate(state);      if (mResources == null) {              mResources = this.getResources();           } } 

这段代码中有一个静态的Resources对象。代码片段mResources = this.getResources()对Resources对象进行了初始化。这时Resources对象拥有了当前Activity对象的引用,Activity又引用了整个页面中所有的对象。如果当前的Activity被重新创建(比如横竖屏切换,默认情况下整个Activity会被重新创建),由于Resources引用了第一次创建的Activity,就会导致第一次创建的Activity不能被垃圾回收器回收,从而导致第一次创建的Activity中的所有对象都不能被回收。这个时候,一部分内存就浪费掉了。

经验分享:在实际项目中,我们经常会把一些对象的引用加入到集合中,如果这个集合是静态的话,就需要特别注意了。当不需要某对象时,务必及时把它的引用从集合中清理掉。

3、使用Application的Context

在Android中,Application Context的生命周期和应用的生命周期一样长,而不是取决于某个Activity的生命周期。如果想保持一个长期生命的对象,并且这个对象需要一个Context,就可以使用Application对象。可以通过调用Context.getApplicationContext()方法或者Activity.getApplication()方法来获得Application对象。

依然拿上面的代码作为例子。可以将代码修改成下面的样子。

private static Resources mResources; @Override protected void onCreate(Bundle state) {     super.onCreate(state);       if (mResources == null) {           // mResources = this.getResources();         mResources = this.getApplication().getResources();           } }

在这里将this.getResources()修改为this.getApplication().getResources()。修改以后,Resources对象拥有的是Application对象的引用。如果Activity被重新创建,第一次创建的Activity就可以被回收了。

4、降低执行时间

(1). 缓存缓存主要包括对象缓存、IO缓存、网络缓存、DB缓存,对象缓存能减少内存的分配,IO缓存减少磁盘的读写次数,网络缓存减少网络传输,DB缓存较少Database的访问次数。在内存、文件、数据库、网络的读写速度中,内存都是最优的,且速度数量级差别,所以尽量将需要频繁访问或访问一次消耗较大的数据存储在缓存中。

(2). 数据存储优化包括数据类型、数据结构的选择。

a. 数据类型选择字符串拼接用StringBuilder代替String,在非并发情况下用StringBuilder代替StringBuffer(非线程安全是指多线程操作同一个对象可能会出现问题。而线程安全则是多线程操作同一个对象不会有问题。线程安全必须要使用很多synchronized关键字来同步控制,所以必然会导致性能的降低。)如果你对字符串的长度有大致了解,如100字符左右,可以直接new StringBuilder(128)指定初始大小,减少空间不够时的再次分配。64位类型如long double的处理比32位如int慢,使用SoftReference、WeakReference相对正常的强应用来说更有利于系统垃圾回收,final类型存储在常量区中读取效率更高,LocalBroadcastManager代替普通BroadcastReceiver,效率和安全性都更高,jason解析数据时,尽量多使用基本数据类型,少使用引用数据类型。因为引用数据类型是一个对象,对象存储需占用栈内存和堆内存。而基本数据类型只需要占用栈内存就可以了。

b. 数据结构选择常见的数据结构选择

如:ArrayList和LinkedList的选择,ArrayList根据index取值更快,LinkedList更占内存、随机插入删除更快速、扩容效率更高。一般推荐ArrayList。ArrayList、HashMap、LinkedHashMap、HashSet的选择,hash系列数据结构查询速度更优,ArrayList存储有序元素,HashMap为键值对数据结构,LinkedHashMap可以记住加入次序的hashMap,HashSet不允许重复元素。HashMap、WeakHashMap选择,WeakHashMap中元素可在适当时候被系统垃圾回收器自动回收,所以适合在内存紧张型中使用。Collections.synchronizedMap和ConcurrentHashMap的选择,ConcurrentHashMap为细分锁,锁粒度更小,并发性能更优。Collections.synchronizedMap为对象锁,自己添加函数进行锁控制更方便。Android也提供了一些性能更优的数据类型,如SparseArray、SparseBooleanArray、SparseIntArray、Pair。Sparse系列的数据结构是为key为int情况的特殊处理,采用二分查找及简单的数组存储,加上不需要泛型转换的开销,相对Map来说性能更优。

5、异步,利用多线程提高TPS充分利用多核Cpu优势,利用线程解决密集型计算、IO、网络等操作。

在Android应用程序中由于系统ANR的限制,将可能造成主线程超时操作放入另外的工作线程中。在工作线程中可以通过handler和主线程交互。

6、提前或延迟操作,错开时间段提高TPS

(1) 延迟操作不在Activity、Service、BroadcastReceiver的生命周期等对响应时间敏感函数中执行耗时操作,可适当delay。Java中延迟操作可使用ScheduledExecutorService,不推荐使用Timer.schedule;Android中除了支持ScheduledExecutorService之外,还有一些delay操作,如handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定时等。

(2) 提前操作对于第一次调用较耗时操作,可统一放到初始化中,将耗时提前。如得到壁纸wallpaperManager.getDrawable();

7、网络优化以下是网络优化中一些客户端和服务器端需要尽量遵守的准则:

a. 图片必须缓存,最好根据机型做图片做图片适配

b. 所有http请求必须添加httptimeout

c. 开启gzip压缩

d. api接口数据以json格式返回,而不是xml或html

e. 根据http头信息中的Cache-Control及expires域确定是否缓存请求结果。

f. 确定网络请求的connection是否keep-alive

g. 减少网络请求次数,服务器端适当做请求合并。

h. 减少重定向次数

i. api接口服务器端响应时间不超过100ms

j.支持断点续传,并缓存 Http Resonse 的 ETag 标识,下次请求时带上,从而确定是否数据改变过,未改变则直接返回 304。







0 0
原创粉丝点击