Android中怎样避免创建不必要的对象

前言

随着在APP中分配更多的对象,你就得实施定期的强制垃圾收集,会导致用户体验产生小卡顿现象。并发垃圾处理器在Android 2.3中引入,但是总是应该避免不必要的工作,因此应该在不必要的时候避免创建对象实例。

在编程开发中,内存的占用是我们经常要面对的现实,通常的内存调优的方向就是尽量减少内存的占用。

Android设备不像PC那样有着足够大的内存,而且单个App占用的内存实际上是比较小的。所以避免创建不必要的对象对于Android开发尤为重要。

本文会介绍一些常见的避免创建对象的场景和方法,其中有些属于微优化,有的属于编码技巧,当然也有确实能够起到显著效果的方法。

使用单例

单例是我们常用的设计模式,使用这种模式,我们可以只提供一个对象供全局调用。因此单例是避免创建不必要的对象的一种方式。

单例模式上手容易,但是需要注意很多问题,最重要的就是多线程并发的情况下保证单例的唯一性。当然方式很多,比如饿汉式,懒汉式double-check等。

这里介绍一个很极客的书写单例的方式。

public 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等。

这种特性,极大的减少了编码时的琐碎工作,但是稍有不注意就可能创建了不必要的对象了。比如下面的代码

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 keyvalue时,会检测是否需要扩容,如需要则双倍扩容

@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;
  }

关于扩容的问题,通常有如下几种方法

1.预估一个较大的容量值,避免多次扩容

2.寻找替代的数据结构,确保做到时间和空间的平衡

用好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:label="@string/app_name"
  android:theme="@style/AppTheme.NoActionBar"
  android:configChanges="orientation"
>

然后重写Activity的onConfigurationChanged方法

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);
}

降低字符串拼接的方法有

1.使用String.format替换

2.如果是循环拼接,建议显式在循环外部创建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("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();
    }
  }

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

不要过多创建线程

在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() 。使用池并不会每一次都创建新的对象,而是优先从池中取对象。

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

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

2.合理控制池的增长,避免过大,导致很多对象处于闲置状态

谨慎初始化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中如何避免创建多余对象的总结.欢迎提出意见和观点,共同进步。希望本文的内容能帮助到大家。

(0)

相关推荐

  • Android传递Bitmap对象在两个Activity之间

    通过内部存储方式实现了在两个Activity之间传递Bitmap对象以及其它支持串行化的Java对象,关键点有如下: 1.  HTTP客户端下载图片,通过ImageView对象显示 2.  把ImageView上的Bitmap对象从当前Activity传递到另外一个Activity中并显示出来 3.  基于串行化传递Java对象数据 首先看我是怎么实现HTTP客户端下载图片,通过异步Task接口实现HTTP客户端下载图片并通过Handler来更新ImageView,代码如下: package c

  • 深入Android 五大布局对象的应用

    FrameLayout(帧布局),LinearLayout (线性布局),AbsoluteLayout(绝对布局),RelativeLayout(相对布局),TableLayout(表格布局)FrameLayout:FrameLayout是最简单的一个布局对象.它被定制为你屏幕上的一个空白备用区域,之后你可以在其中填充一个单一对象 ,比如,一张你要发布的图片.所有的子元素将会固定在屏幕的左上角:你不能为FrameLayout中的一个子元素指定一个位置.后一个子元素将会直接在前 一个子元素之上进行

  • Android解析json数组对象的方法及Apply和数组的三个技巧

    json是种常用的数据传输格式,在android开发中,如何借助java语言实现对json数组对象的解析呢,请参阅下面的关键代码: import org.json.JSONArray; import org.json.JSONObject; //jsonData的数据格式:[{ "id": "27JpL~jd99w9nM01c000qc", "version": "abc" },{ "id": "

  • 详解Android中Intent对象与Intent Filter过滤匹配过程

    如果对Intent不是特别了解,可以参见博文<详解Android中Intent的使用方法>,该文对本文要使用的action.category以及data都进行了详细介绍.如果想了解在开发中常见Intent的使用,可以参见<Android中Intent习惯用法>. 本文内容有点长,希望大家可以耐心读完. 本文在描述组件在manifest中注册的Intent Filter过滤器时,统一用intent-filter表示. 一.概述 我们知道,Intent是分两种的:显式Intent和隐式

  • Android webview与js交换JSON对象数据示例

    最近几个项目的测试结果,Android无法主动通过调用 webview.loadUrl("javascript:"+callbackFunction+"('"+data+"')"); 这种方式将jsonobject类型的data传给js,因为js那边得到就是一个string的对象. 与此同时,js主动调用android的对象方式,android也无法返回给js一个jsonobject,需要js做一下转换,例如: Android 代码: 复制代码

  • Android 解析JSON对象及实例说明

    JSON是一种轻量级的对象,数据体积小,方便传输,易于解析! 首先新建一个类工具类JsonUtil,用于获取请求返回的数据 复制代码 代码如下: public class JsonUtil { private static final String TAG = "JSONUTIL"; public static JSONObject getJSON(String url) throws Exception {  return new JSONObject(getRequest(url)

  • Android 不同Activity间数据的传递 Bundle对象的应用

    在应用中,可能会在当跳转到另外一个Activity的时候需要传递数据过去,这时就可能用Bundle对象: 在MainActivity中,有一个导航至BActivity的Intent, Intent 复制代码 代码如下: { Intent intent = new Intent(Context context, Class<?> class); //new一个Bundle对象,并将要传递的数据导入,Bunde相当于Map<Key,Value>结构    Bundle bundle =

  • android中intent传递list或者对象的方法

    本文实例讲述了android中intent传递list或者对象的方法.分享给大家供大家参考.具体实现方法如下: 方法一: 如果单纯的传递List<String> 或者List<Integer>的话 就可以直接使用 代码如下: 复制代码 代码如下: intent.putStringArrayListExtra(name, value)  intent.putIntegerArrayListExtra(name, value) 方法二: 如果传递的是List<Object>

  • android将Bitmap对象保存到SD卡中的方法

    本文实例讲述了android将Bitmap对象保存到SD卡中的方法.分享给大家供大家参考.具体如下: Bitmap logoBitmap = BitmapFactory.decodeResource(mcontext.getResources(), R.drawable.arcnote_logo); ByteArrayOutputStream logoStream = new ByteArrayOutputStream(); boolean res = logoBitmap.compress(B

  • Android中怎样避免创建不必要的对象

    前言 随着在APP中分配更多的对象,你就得实施定期的强制垃圾收集,会导致用户体验产生小卡顿现象.并发垃圾处理器在Android 2.3中引入,但是总是应该避免不必要的工作,因此应该在不必要的时候避免创建对象实例. 在编程开发中,内存的占用是我们经常要面对的现实,通常的内存调优的方向就是尽量减少内存的占用. Android设备不像PC那样有着足够大的内存,而且单个App占用的内存实际上是比较小的.所以避免创建不必要的对象对于Android开发尤为重要. 本文会介绍一些常见的避免创建对象的场景和方法

  • Android中new Notification创建实例的最佳方法

    目前 Android 已经不推荐使用下列方式创建 Notification实例: Notification notification = new Notification(R.drawable.ic_launcher,"This is ticker text",System.currentTimeMillis()); 最好采用下列方式: Notification notification = new Notification.Builder(this) .setContentTitle

  • Android中activity从创建到显示的基本介绍

    前言 说道Android中的Activity,如果你做过iOS开发的话,Activity类似于iOS中的ViewController(视图控制器).在应用中能看到的东西都是放在活动中的.活动是安卓开发比较重要的东西,是用户交互和数据的入口.本篇博客要介绍的内容是活动的创建,活动的跳转与值的透传. iOS中的ViewController也是有自己的生命周期的,了解Activity或者ViewController的生命周期是很有必要的,本文将详细的给大家介绍关于Android中activity从创建

  • Android中为activity创建菜单

    安卓中为activity创建菜单,供大家参考,具体内容如下 1.在res上面右键 > new > Android xml file . 2.在弹出对话框中,找到 resourse type 点开右边的下拉框 选为Menu, file中写上资源名字(随意) 3.之后将下面代码粘到xml中,自己根据需求进行修改 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="

  • Android中使用Intent在Activity之间传递对象(使用Serializable或者Parcelable)的方法

    Android中的不同Activity之间传递对象,我们可以考虑采用Bundle.putSerializable(Key,Object);也可以考虑采用Bundle.putParcelable(Key, Object);其中前面一种方法中的Object要实现Serializable接口,后面一种方法中的Object要实现Parcelable接口.下面我们以一个完整的例子来说明. 1.新建一个Android的工程,其中该工程的目录结构如下图: 2. 修改main.xml布局文件.布局文件的源码如下

  • 在Java中如何避免创建不必要的对象

    目录 简介 1.String和Boolean 2.自动拆箱和装箱 3.正则表达式 总结 简介 在Java开发中,程序员要尽可能的避免创建相同的功能的对象,因为这样既消耗内存,又影响程序运行速度.在这种情况下可以考虑重复利用对象. 接下来举例几种对象重复利用的场景,看看我们是不是有中招了,如果有赶紧趁着还没被发现悄悄改掉,被发现了会被diss啦! 1.String和Boolean 如下两种写法看似没有什么区别,但是如果深入jvm底层了解,我们可以利用jvm运行时常量池的特性,避免创建具有相同功能的

  • Android中使用IntentService创建后台服务实例

    IntentService提供了在单个后台线程运行操作的简单结构.这允许它操作耗时操作,而不影响UI响应.同样,IntentService也不影响UI生命周期事件,所以,它在某些可能关闭AsyncTask的情况下,仍会继续运行(实测在Activity的onDestory里写AsyncTask无法运行). IntentService有如下限制: 1.它不能直接影响UI.要把结果反映给UI,需要发给Activity 2.工作请求会顺序运行.如果一个操作未结束,后面发送的操作必须等它结束(单线程) 3

  • Android中传递对象的三种方法的实现

    Android中,Activity和Fragment之间传递对象,可以通过将对象序列化并存入Bundle或者Intent中进行传递,也可以将对象转化为JSON字符串,进行传递. 序列化对象可以使用Java的Serializable的接口.Parcelable接口.转化成JSON字符串,可以使用Gson等库. 1.Serializable public class Author implements Serializable{ private int id; private String name

  • 详解Android中Handler的内部实现原理

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文<详解Android中Handler的使用方法>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功

  • Android中图片占用内存的深入分析

    目录 前言 一.图片占用内存与宽.高.色彩模式的关系 二.图片占用内存与存放文件夹的关系 三.从文件中加载图片和从网络加载图片占用内存 四.色彩模式 五.总结 前言 Android 在加载图片的时候一定会考虑到的一个点就是如何防止 OOM,那么一张图片在加载的时候到底会占用多少内存呢?有哪些因素会影响占用的内存呢?知道了这些,我们才能知道可以从哪些点去优化,从而避免 OOM. 一.图片占用内存与宽.高.色彩模式的关系 首先我们准备一张 1920*1080 的图片: 然后我使用的测试机是 Redm

随机推荐