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

简介:

在编程开发中,内存的占用是我们经常要面对的现实,通常的内存调优的方向就是尽量减少内存的占用。这其中避免创建不必要的对象是一项重要的方面。

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

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

使用单例

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

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

 
 
  1. public static class SingleInstance { 
  2.     private SingleInstance() { 
  3.     } 
  4.  
  5.    public static SingleInstance getInstance() { 
  6.             return SingleInstanceHolder.sInstance; 
  7.    } 
  8.  
  9.   private static class SingleInstanceHolder { 
  10.             private static SingleInstance sInstance = new SingleInstance(); 
  11.   } 

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

避免进行隐式装箱

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

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

 
 
  1. Integer sum = 0; 
  2.   for (int i = 1000; i < 5000; i++) { 
  3.         sum += i; 

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

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

由于我们这里声明的sum为Integer类型,在上面的循环中会创建将近4000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。

另外,当将原始数据类型的值加入集合中时,也会发生自动装箱,所以这个过程中也是有对象创建的。如有需要避免这种情况,可以选择SparseArray,SparseBooleanArray,SparseLongArray等容器。

谨慎选用容器

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

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

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

 
 
  1. @Override 
  2. public V put(K key, V value) { 
  3.     if (key == null) { 
  4.         return putValueForNullKey(value); 
  5.     } 
  6.     //some code here 
  7.  
  8.     // No entry for (non-nullkey is present; create one 
  9.     modCount++; 
  10.     if (size++ > threshold) { 
  11.         tab = doubleCapacity(); 
  12.         index = hash & (tab.length - 1); 
  13.     } 
  14.     addNewEntry(key, value, hash, index); 
  15.     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声明时,加上

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

然后重写Activity的onConfigurationChanged方法

 
 
  1. @Override 
  2. public void onConfigurationChanged(Configuration newConfig) { 
  3.    super.onConfigurationChanged(newConfig); 
  4.    if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 
  5.         setContentView(R.layout.portrait_layout); 
  6.      } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { 
  7.         setContentView(R.layout.landscape_layout); 
  8.     } 

注意字符串拼接

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

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

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

下面是一段代码循环的代码,这明显是很不好的,因为这其中创建了很多的StringBuilder对象。

 
 
  1. public void implicitUseStringBuilder(String[] values) { 
  2.         String result = ""
  3.         for (int i = 0; i < values.length; i++) { 
  4.             result += values[i]; 
  5.         } 
  6.         System.out.println(result); 
  7.     } 

降低字符串拼接的方法有

  • 使用String.format替换
  • 如果是循环拼接,建议显式在循环外部创建StringBuilder使用

减少布局层级

布局层级过多,不仅导致inflate过程耗时,还多创建了多余的辅助布局。所以减少辅助布局还是很有必要的。可以尝试其他布局方式或者自定义视图来解决这类的问题。

提前检查,减少不必要的异常

异常对于程序来说,在平常不过了,然后其实异常的代码很高的,因为它需要收集现场数据stacktrace。但是还是有一些避免异常抛出的措施的,那就是做一些提前检查。

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

 
 
  1. private void printFileByLine(String filePath) { 
  2.    try { 
  3.       FileInputStream inputStream = new FileInputStream("textfile.txt"); 
  4.       BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); 
  5.       String strLine; 
  6.       //Read File Line By Line 
  7.       while ((strLine = br.readLine()) != null) { 
  8.           // Print the content on the console 
  9.           System.out.println(strLine); 
  10.       } 
  11.         br.close(); 
  12.     } catch (FileNotFoundException e) { 
  13.        e.printStackTrace(); 
  14.     } catch (IOException e) { 
  15.         e.printStackTrace(); 
  16.    } 

如果我们进行文件是否存在的检查,抛出FileNotFoundException的概率会减少很多,

 
 
  1. private void printFileByLine(String filePath) { 
  2.    if (!new File(filePath).exists()) { 
  3.             return
  4.    } 
  5.     try { 
  6.        FileInputStream inputStream = new FileInputStream("anonymous.txt"); 
  7.        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); 
  8.        String strLine; 
  9.       //Read File Line By Line 
  10.        while ((strLine = br.readLine()) != null) { 
  11.             // Print the content on the console 
  12.            System.out.println(strLine); 
  13.        } 
  14.          br.close(); 
  15.     } catch (FileNotFoundException e) { 
  16.         e.printStackTrace(); 
  17.     } catch (IOException e) { 
  18.         e.printStackTrace(); 
  19.    } 

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

不要过多创建线程

在android中,我们应该尽量避免在主线程中执行耗时的操作,因而需要使用其他线程。

 
 
  1. private void testThread() { 
  2.     new Thread() { 
  3.        @Override 
  4.        public void run() { 
  5.           super.run(); 
  6.           //do some io work 
  7.       } 
  8.    }.start(); 

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

使用注解替代枚举

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

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

在一个文件中如下声明

 
 
  1. public class AppConstants { 
  2.    public static final int STATE_OPEN = 0; 
  3.    public static final int STATE_CLOSE = 1; 
  4.    public static final int STATE_BROKEN = 2; 
  5.  
  6.    @IntDef({STATE_OPEN, STATE_CLOSE, STATE_BROKEN}) 
  7.    public @interface DoorState { 
  8.    } 

然后设置书写这样的方法

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

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

选用对象池

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

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

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

  • 将对象放回池中,注意初始化对象的数据,防止存在脏数据
  • 合理控制池的增长,避免过大,导致很多对象处于闲置状态

谨慎初始化Application

Android应用可以支持开启多个进程。 通常的做法是这样

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

通常我们在Application的onCreate方法中会做很多初始化操作,但是每个进程启动都需要执行到这个onCreate方法,为了避免不必要的初始化,建议按照进程(通过判断当前进程名)对应初始化.

 
 
  1. public class MyApplication extends Application { 
  2.    private static final String LOGTAG = "MyApplication"
  3.  
  4.      @Override 
  5.      public void onCreate() { 
  6.         String currentProcessName = getCurrentProcessName(); 
  7.         Log.i(LOGTAG, "onCreate currentProcessName=" + currentProcessName); 
  8.            super.onCreate(); 
  9.            if (getPackageName().equals(currentProcessName)) { 
  10.                 //init for default process 
  11.            } else if (currentProcessName.endsWith(":network")) { 
  12.                 //init for netowrk process 
  13.            } 
  14.      } 
  15.  
  16.     private String getCurrentProcessName() { 
  17.         String currentProcessName = ""
  18.         int pid = android.os.Process.myPid(); 
  19.         ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); 
  20.         for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) { 
  21.             if (processInfo.pid == pid) { 
  22.                     currentProcessName = processInfo.processName; 
  23.                     break; 
  24.             } 
  25.        } 
  26.       return currentProcessName; 
  27.     } 

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




作者:anonymoussf
来源:51CTO
目录
相关文章
|
1月前
|
算法 数据处理 Android开发
掌握安卓性能优化的秘诀:电池寿命与运行效率的提升
【10月更文挑战第6天】 本文深入探讨了安卓应用开发中的性能优化技巧,重点分析了影响电池寿命和运行效率的关键因素,并提供了针对性的优化策略。通过代码优化、资源管理、后台任务处理等方法,开发者可以显著提升应用的续航能力和流畅度。同时,结合具体案例,展示了如何在实际开发中应用这些技巧,确保应用在各种场景下都能保持高效运行。本文旨在为安卓开发者提供实用的性能优化指导,助力其打造更优质的应用体验。
49 2
|
13天前
|
Android开发 开发者
Android性能优化——内存管理的艺术
Android性能优化——内存管理的艺术
|
19天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
28 5
|
18天前
|
缓存 数据库 Android开发
安卓开发中的性能优化技巧
【10月更文挑战第29天】在移动应用的海洋中,性能是船只能否破浪前行的关键。本文将深入探讨安卓开发中的性能优化策略,从代码层面到系统层面,揭示如何让应用运行得更快、更流畅。我们将以实际案例和最佳实践为灯塔,引领开发者避开性能瓶颈的暗礁。
39 3
|
1月前
|
存储 缓存 网络协议
5个Android性能优化相关的深度面试题
本文涵盖五个Android面试题及其解答,包括优化应用启动速度、内存泄漏的检测与解决、UI渲染性能优化、减少内存抖动和内存溢出、优化网络请求性能。每个问题都提供了详细的解答和示例代码。
26 2
|
1月前
|
监控 测试技术 Android开发
掌握安卓性能优化的关键策略
【10月更文挑战第7天】 在移动应用开发领域,性能优化是一项至关重要的任务。本文将探讨安卓应用性能优化的重要性、关键策略以及实际操作建议,帮助开发者提升应用的用户体验和竞争力。通过深入浅出的方式,我们将从背景介绍到具体实践,全面解析安卓性能优化的各个维度。
|
2月前
|
存储 Java 编译器
🔍深入Android底层,揭秘JVM与ART的奥秘,性能优化新视角!🔬
【9月更文挑战第12天】在Android开发领域,深入了解其底层机制对提升应用性能至关重要。本文详述了从早期Dalvik虚拟机到现今Android Runtime(ART)的演变过程,揭示了ART通过预编译技术实现更快启动速度和更高执行效率的奥秘。文中还介绍了ART的编译器与运行时环境,并提出了减少DEX文件数量、优化代码结构及合理管理内存等多种性能优化策略。通过掌握这些知识,开发者可以从全新的角度提升应用性能。
65 11
|
Android开发 Java API
android性能优化优秀文章
郭霖最近整理的文章: 合理管理内存 分析编码过程中如何避免过多内存占用,以及如何实现高性能的内存使用。 尽可能使用intentService; 当界面不可见时释放内存; 当内存紧张时释放内存; 避免bitmap占用内存; 使用android api提供的数据集合; 减少抽象类的使用; 避免使用依赖注入; 使用proGuard简化代码; 使用多个进程。
812 0
|
3天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
8天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
下一篇
无影云桌面