Android性能优化篇:Android中如何避免建立没必要要的对象

在编程开发中,内存的占用是咱们常常要面对的现实,一般的内存调优的方向就是尽可能减小内存的占用。这其中避免建立没必要要的对象是一项重要的方面。android

Android设备不像PC那样有着足够大的内存,并且单个App占用的内存其实是比较小的。因此避免建立没必要要的对象对于Android开发尤其重要。编程

本文会介绍一些常见的避免建立对象的场景和方法,其中有些属于微优化,有的属于编码技巧,固然也有确实可以起到显著效果的方法。设计模式

使用单例

单例是咱们经常使用的设计模式,使用这种模式,咱们能够只提供一个对象供全局调用。所以单例是避免建立没必要要的对象的一种方式。数据结构

单例模式上手容易,可是须要注意不少问题,最重要的就是多线程并发的状况下保证单例的惟一性。固然方式不少,好比饿汉式,懒汉式double-check等。这里介绍一个很极客的书写单例的方式。多线程

public static class SingleInstance {
    private SingleInstance() {
    }

   public static SingleInstance getInstance() {
            return SingleInstanceHolder.sInstance;
   }

  private static class SingleInstanceHolder {
            private static SingleInstance sInstance = new SingleInstance();
  }
}

在Java中,类的静态初始化会在类被加载时触发,咱们利用这个原理,能够实现利用这一特性,结合内部类,能够实现上面的代码,进行懒汉式建立实例。并发

避免进行隐式装箱

自动装箱是Java 5 引入的一个特性,即自动将原始类型的数据转换成对应的引用类型,好比将int转为Integer等。app

这种特性,极大的减小了编码时的琐碎工做,可是稍有不注意就可能建立了没必要要的对象了。好比下面的代码ide

Integer sum = 0;
  for (int i = 1000; i < 5000; i++) {
        sum += i;
}

上面的代码sum+=i能够当作sum = sum + i,可是+这个操做符不适用于Integer对象,首先sum进行自动拆箱操做,进行数值相加操做,最后发生自动装箱操做转换成Integer对象。其内部变化以下布局

int result = sum.intValue() + i;
Integer sum = new Integer(result);

因为咱们这里声明的sum为Integer类型,在上面的循环中会建立将近4000个无用的Integer对象,在这样庞大的循环中,会下降程序的性能而且加剧了垃圾回收的工做量。所以在咱们编程时,须要注意到这一点,正确地声明变量类型,避免由于自动装箱引发的性能问题。性能

另外,当将原始数据类型的值加入集合中时,也会发生自动装箱,因此这个过程当中也是有对象建立的。若有须要避免这种状况,能够选择SparseArray,SparseBooleanArray,SparseLongArray等容器。

谨慎选用容器

Java和Android提供了不少编辑的容器集合来组织对象。好比ArrayList,ContentValues,HashMap等。

然而,这样容器虽然使用起来方便,但也存在一些问题,就是他们会自动扩容,这其中不是建立新的对象,而是建立一个更大的容器对象。这就意味这将占用更大的内存空间。

以HashMap为例,当咱们put key和value时,会检测是否须要扩容,如须要则双倍扩容

@Override
public V put(K key, V value) {
    if (key == null) {
        return putValueForNullKey(value);
    }
    //some code here

    // No entry for (non-null) key is present; create one
    modCount++;
    if (size++ > threshold) {
        tab = doubleCapacity();
        index = hash & (tab.length - 1);
    }
    addNewEntry(key, value, hash, index);
    return null;
}

关于扩容的问题,一般有以下几种方法

  • 预估一个较大的容量值,避免屡次扩容

  • 寻找替代的数据结构,确保作到时间和空间的平衡

用好LaunchMode

提到LaunchMode必然和Activity有关系。正常状况下咱们在manifest中声明Activity,若是不设置LaunchMode就使用默认的standard模式。

一旦设置成standard,每当有一次Intent请求,就会建立一个新的Activity实例。举个例子,若是有10个撰写邮件的Intent,那么就会建立10个ComposeMailActivity的实例来处理这些Intent。结果很明显,这种模式会建立某个Activity的多个实例。

若是对于一个搜索功能的Activity,实际上保持一个Activity示例就能够了,使用standard模式会形成Activity实例的过多建立,于是很差。

确保符合常理的状况下,合理的使用LaunchMode,减小Activity的建立。

Activity处理onConfigurationChanged

这又是一个关于Activity对象建立相关的,由于Activity建立的成本相对其余对象要高不少。

默认状况下,当咱们进行屏幕旋转时,原Activity会销毁,一个新的Activity被建立,之因此这样作是为了处理布局适应。固然这是系统默认的作法,在咱们开发可控的状况下,咱们能够避免从新建立Activity。

以屏幕切换为例,在Activity声明时,加上

<activity
  android:name=".MainActivity"
  android:configChanges="orientation"
  android:label="@string/app_name"
  android:theme="@style/AppTheme.NoActionBar"/>

而后重写Activity的onConfigurationChanged方法

@Override
public void onConfigurationChanged(Configuration newConfig) {
   super.onConfigurationChanged(newConfig);
   if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
        setContentView(R.layout.portrait_layout);
     } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        setContentView(R.layout.landscape_layout);
    }
}

注意字符串拼接

字符串这个或许是最不起眼的一项了。这里主要讲的是字符串的拼接

Log.i(LOGTAG, "onCreate bundle=" + savedInstanceState);

这应该是咱们最多见的打log的方式了,然而字符串的拼接内部实际是生成StringBuilder对象,而后挨个进行append,直至最后调用toString方法的过程。

下面是一段代码循环的代码,这明显是很很差的,由于这其中建立了不少的StringBuilder对象。

public void implicitUseStringBuilder(String[] values) {
        String result = "";
        for (int i = 0; i < values.length; i++) {
            result += values[i];
        }
        System.out.println(result);
    }

下降字符串拼接的方法有

  • 使用String.format替换

  • 若是是循环拼接,建议显式在循环外部建立StringBuilder使用

减小布局层级

布局层级过多,不只致使inflate过程耗时,还多建立了多余的辅助布局。因此减小辅助布局仍是颇有必要的。能够尝试其余布局方式或者自定义视图来解决这类的问题。

提早检查,减小没必要要的异常

异常对于程序来讲,在日常不过了,而后其实异常的代码很高的,由于它须要收集现场数据stacktrace。可是仍是有一些避免异常抛出的措施的,那就是作一些提早检查。

好比,咱们想要打印一个文件的每一行字符串,没作检查的代码以下,是存在FileNotFoundException抛出可能的。

private void printFileByLine(String filePath) {
   try {
      FileInputStream inputStream = new FileInputStream("textfile.txt");
      BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
      String strLine;
      //Read File Line By Line
      while ((strLine = br.readLine()) != null) {
          // Print the content on the console
          System.out.println(strLine);
      }
        br.close();
    } catch (FileNotFoundException e) {
       e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
   }
}

若是咱们进行文件是否存在的检查,抛出FileNotFoundException的几率会减小不少,

private void printFileByLine(String filePath) {
   if (!new File(filePath).exists()) {
            return;
   }
    try {
       FileInputStream inputStream = new FileInputStream("anonymous.txt");
       BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
       String strLine;
      //Read File Line By Line
       while ((strLine = br.readLine()) != null) {
            // Print the content on the console
           System.out.println(strLine);
       }
         br.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
   }
}

上述的检查是一个不错的编码技巧,建议采纳。

不要过多建立线程

在android中,咱们应该尽可能避免在主线程中执行耗时的操做,于是须要使用其余线程。

private void testThread() {
    new Thread() {
       @Override
       public void run() {
          super.run();
          //do some io work
      }
   }.start();
}

虽然这些能工做,可是建立线程的代价远比普通对象要高的多,建议使用HandlerThread或者ThreadPool作替换。

使用注解替代枚举

枚举是咱们常用的一种用做值限定的手段,使用枚举比单纯的常量约定要靠谱。而后枚举的实质仍是建立对象。好在Android提供了相关的注解,使得值限定在编译时进行,进而减小了运行时的压力。相关的注解为IntDef和StringDef。

以下以IntDef为例,介绍如何使用

在一个文件中以下声明

public class AppConstants {
   public static final int STATE_OPEN = 0;
   public static final int STATE_CLOSE = 1;
   public static final int STATE_BROKEN = 2;

   @IntDef({STATE_OPEN, STATE_CLOSE, STATE_BROKEN})
   public @interface DoorState {
   }
}

而后设置书写这样的方法

private void setDoorState(@AppConstants.DoorState int state) {
   //some code
}

当调用方法时只能使用STATE_OPEN,STATE_CLOSE和STATE_BROKEN。使用其余值会致使编译提醒和警告。

选用对象池

在Android中有不少池的概念,如线程池,链接池。包括咱们很长用的Handler.Message就是使用了池的技术。

好比,咱们想要使用Handler发送消息,可使用Message msg = new Message(),也可使用Message msg = handler.obtainMessage()。使用池并不会每一次都建立新的对象,而是优先从池中取对象。

使用对象池须要须要注意几点

  • 将对象放回池中,注意初始化对象的数据,防止存在脏数据

  • 合理控制池的增加,避免过大,致使不少对象处于闲置状态

谨慎初始化Application

Android应用能够支持开启多个进程。 一般的作法是这样

<service
   android:name=".NetworkService"
   android:process=":network"/>

一般咱们在Application的onCreate方法中会作不少初始化操做,可是每一个进程启动都须要执行到这个onCreate方法,为了不没必要要的初始化,建议按照进程(经过判断当前进程名)对应初始化.

public class MyApplication extends Application {
   private static final String LOGTAG = "MyApplication";

     @Override
     public void onCreate() {
        String currentProcessName = getCurrentProcessName();
        Log.i(LOGTAG, "onCreate currentProcessName=" + currentProcessName);
           super.onCreate();
           if (getPackageName().equals(currentProcessName)) {
                //init for default process
           } else if (currentProcessName.endsWith(":network")) {
                //init for netowrk process
           }
     }

    private String getCurrentProcessName() {
        String currentProcessName = "";
        int pid = android.os.Process.myPid();
        ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
            if (processInfo.pid == pid) {
                    currentProcessName = processInfo.processName;
                    break;
            }
       }
      return currentProcessName;
    }
}

上面的一些知识就是关于Android中如何避免建立多余对象的总结.欢迎提出意见和观点,共同进步.

相关文章
相关标签/搜索