Android中如何节省内存占用
2017-01-17 23:09
253 查看
内容介绍
使用单例模式的技巧谨慎合理选择Android的集合
如何更好控制Activity的实例创建
枚举的替代方案
Android中那些被隐式创建的对象们
关于减少内存占用,这些细节必须知道
内存介绍
JVM运行时数据区:程序计数器,JVM栈,堆内存,方法区,运行时常量池,本地方法栈程序计数器:用来记录当前正在执行的指令,线程私有。占用空间很小,唯一一个不抛出OOM的区域。
JVM栈:存放栈帧,一个栈帧随着一个方法的调用开始而创建,调用完成而结束
堆内存:用来存放对象和数组,多个线程共享。堆内存随着JVM启动而创建。
方法区:存放类的信息,比如类加载器引用,属性、方法代码和构造方法和常量等
运行时常量池:是一个类或者接口的class文件中常量池表的运行时展示形式。
本地方法栈:一个支持native方法调用的JVM实现,需要有这样一个数据区,就是本地方法栈。
基本原则
避免创建不必要的对象不必要的对象可能是显式创建也能是隐式创建
这些不必要的对象,可以直接避免,也可以另辟蹊径绕过
深入细节和原理是发现并解决问题的有效方法
按需创建对象是重中之重
1.单例模式
定义
单例模式,指的是一个类只有一个实例,并且提供一个全局的访问点。对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。
由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
需要统一管理的时候也可以使用单例模式。比如一个AppConfig
然而单例模式也存在不好的地方,比如影响依赖注入、单元测试等
如何创建单例
1.饿汉式(Eager initialization)//利用类加载机制,生成实例对象 private static SingleInstance sInstance = new SingleInstance(); //1.构造方法私有 private SingleInstance() { } //2.全局公开获取实例对象的的方法 public static SingleInstance getsInstance() { return sInsta d2c9 nce; }
类在加载的时候创建好单例对象
过于急切,如果放在集中初始化的地方(如application 或者 activity.onCreate()方法),可能会降低性能
2.懒汉式(lazy initialization)
private static SingleInstance sInstance; private SingleInstance() { } public static SingleInstance getInstance() { if (null == sInstance) { sInstance = new SingleInstance(); } return sInstance; }
当真正使用单例时才创建
以上写法在如果单例只在单一线程使用,是没有问题的。但是多线程就可能有问题。
3.Synchronized修饰方法
private static SingleInstance sInstance; private SingleInstance() { } public static synchronized SingleInstance getsInstance() { if (null == sInstance) { sInstance = new SingleInstance(); } return sInstance; }
使用synchronized修饰getInstance方法后必然会导致性能下降,而且getInstance是一个被频繁调用的方法。虽然这种方法能解决问题,但是不推荐
4.双重检查
private static volatile SingleInstance sInstance; private SingleInstance() { } public SingleInstance getsInstance(){ if (null == sInstance) { synchronized(SingleInstance.class){ if (null == sInstance) { sInstance = new SingleInstance(); } } } return sInstance; }
volatile它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。使用volatile修饰sInstance变量之后,可以确保多个线程之间正确处理sInstance变量。
5.利用java的static特性
private SingleInstance() { } public static SingleInstance getInstance() { return SingleInstanceHolder.sInstance; } private static class SingleInstanceHolder { private static SingleInstance sInstance = new SingleInstance(); }
在java中,类的静态初始化会在类被加载时触发,我们利用这个原理,可以实现利用这一特性,结合内部类
6.使用枚举创建单例
public enum EasySingleton{ INSTANCE; }
反编译发现,枚举是在调用的时候进行new生成对象。
2.集合的问题
常用的集合:ArrayList,HashMap,ContentValues等问题:默认初始容量小,多次扩容(基于数组的容器)
问题:原始类型发生自动装箱
扩容:以ArrayList add方法扩容为例
初始容量小,多次扩容
private static int newCapacity(int currentCapacity){ int increment = (currentCapacity < (MIN_CAPACITY_INCREMENT / 2)? MIN_CAPACITY_INCREMENT:currentCapacity >> 1); return currentCapacity + increment; }
如果当前容量小于MIN_CAPACITY_INCREMENT的一半,则扩容至currentCapacity + MIN_CAPACITY_INCREMENT
如果当前容量大于MIN_CAPACITY_INCREMENT的一半,则扩容至1.5 x currentCapacity
ArrayList中MIN_CAPACITY_INCREMENT的值为12
默认情况下初始容量为0
如何解决频繁扩容
在可以(大概)预知目标数据容量的情况下,设定合理的初始容量
合理选择数据结构,比如某些场景下,我们可以使用基于链表的结果,如可以,这里的ArrayList可以替换成LinkedList
自动发生的装箱
装箱就是java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱
自动装箱指的是装箱操作由compiler自动完成
向集合中添加原始类型的元素,则会发生自动装箱操作,即实际存放如集合的伪装箱后的对象。
从集合中读取这些装箱的元素,可能发生自动拆箱,即自动装箱的逆过程。
一些可以避免自动装箱的集合:
SpareseArray,SparseBooleanArray,SparseIntArray,LongSparseArray等
在时间与空间对比合理时,可以考虑用上述不发生自动装箱的集合。
3.控制Activity的创建
使用正确的launchmode1.通常我们声明Activity
<activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> </activity>
2.启动一个Activity
private void startActivity(){ startActivity(new Intent(this, MainActivity.class)); }
当我们多次调用startActivity方法,会创建多个MainActivity示例
下面介绍四种Launchmode
standard,默认的启动模式,每当有一次Intent请求,就会创建一个新的Activity实例
singleTop,如果调用的目标Activity已经位于调用者的Task的栈顶,则不创建新实例,而是使用当前的这个Activity实例,并调用这个实例的onNewIntent方法。
singleTask,使用singleTask启动模式的Activity在系统中只会存在一个实例。如果这个实例已经存在,intent就会通过onNewIntent传递到这个Activity(所有位于该Activity上面的Activity实例都将被销毁掉)。否则新的Activity实例被创建。
singleInstance这个模式和singleTask差不多,因为他们在系统中都只有一份实例。唯一不同的就是存放singleInstance
Activity实例的Task只能存放一个该模式的Activity实例。普通app很少用到这个。
3.处理运行时变化方法
当运行变化时保留实例
调用setRetainInstance(true)方法
手动处理运行时变化
在manifest配置文件中,设置android:configChanges=”orientation”按照屏幕旋转的放向重写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_LANDSCAP) { setContentView(R.layout.landscape_layout); } }
4.枚举的替代方案
枚举出现之前private static final int COLOR_RED = 0; private static final int COLOR_YELLOW = 1; private static final int COLOR = 2; private void setColor(int color){ //some codes here }
setColor理论上可以接受除上述三种颜色之外的任意int值
一个简单的枚举
public enum Color{ RED,YELLOW,BLUE } private void setColorEnum(Color color){ //some code }
枚举相当于一个对象,占用的内存比传统的数据类型大,所以不建议使用枚举,尤其是安卓中有了替代方案以后。
要用到注解:后边待续
其他避免创建不必要对象的场景
字符串拼接减少布局层级
提前检查,减少不必要的异常
不要过多创建线程
字符串拼接
public static void main(String[] args){ String userName = "Andy"; String age = "24"; String job = "Developer"; String info = userName + age + job; System.out.println(info); }
多个字符串拼接,实现方式为隐式创建一个StringBuilder,然后依次调动append,最后调用toString返回结果字符串。
减少布局层级
布局层级过多,不仅导致inflate过程耗时,还多创建了多余的辅助布局。所以减少辅助布局还是很有必要的。可以尝试其他布局方式或者自定义视图来解决这类的问题。
如果上面采用LinearyLayout,RelativeLayout,或者自定义View效果则截然不同。
提前检查,减少不必要的异常
不要创建过多的线程
private void testThread(){ new Thread(){ @Override public void run() { super.run(); //do some IO work } }.start(); }
这种方式会每次创建一个线程,而线程的创建成本很大
建议使用HandlerThread或者ThreadPool处理耗时任务
不建议使用AsyncTask和Executors
HandlerThread
HandlerThread是一个自带Looper的Thread.结合Handler我们可以实现post,postAtFrontOfQueue,postAtTime和postDelayed以及对应的sendMessage实现
使用HandlerThread处理本地IO读写操作(数据库,文件),因为本地IO操作大多数的耗时属于毫秒级别,对于单线程+异步队列的形式不会产生较大的阻塞。因此在这个HandlerThread中不适合加入网络IO操作。
为什么不建议AsyncTask
以一个四核手机为例,当我们持续调用AsyncTask任务过程中在AsyncTask线程数量小于CORE_POOL_SIZE(5)时,会启动新的线程处理任务,不重用之前空闲的线程
当数量超过CORE_POOL_SIZE(5),才开始重用之前的线程处理任务
但是由于AsyncTask属于默认线性执行任务,导致并发执行器总是处于某一个线程工作的状态,因而造成了ThreadPool中其他线程的浪费。同时由于AsyncTask中并不存在allowCoreThreadTimeOut(boolean)的调用,所以ThreadPool中的核心线程即使处于空闲状态也不会销毁掉。
为什么不建议使用Executors
//Executors source code public static ExecutorService new FixedThreadPool(int nThreads){ return new ThreadPoolExecutor(nThreads,nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } //ThreadPoolExecutor source code public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue){ this(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue, Executors.defaultThreadFactory(),defaultHandler); }
CORE_POOL_SIZE和MAXIMUM_POOL_SIZE都是同样的值,如果把nThreads当成核心线程数,则无法保证最大并发,而如果当做最大并发线程数,则会造成线程的浪费。因而Executors这样的API导致了我们无法在最大并发数和线程节省上做到平衡。
为了达到最大并发数和线程节省的平衡,建议自行创建ThreadPoolExecutor,根据业务和设备信息确定CORE_POOL_SIZE和MAXIMUM_POOL_SIZE的合理性。
相关文章推荐
- Android图片加载方案——如何保证图片清晰度同时,最大限度节省内存使用量
- Android如何高效的加载图片(4)--- 图片占用内存的管理
- android如何查看app的内存占用情况
- Python 如何获取Android应用内存使用和CPU占用信息,并且时时展示曲线图形。
- 如何在linux下看进程实际占用内存
- 讨教:WINCE 开发中,如何让.net程序及时地释放内存,如何可以节省内存?
- 如何在程序中找到何处在不断占用内存
- 如何得到程序运行时占用的内存?
- 如何计算java对象占用的内存
- 如何释放sqlservr.exe所占用的内存?
- 如何手动设置SQL server的最大内存占用
- W3wp.exe内存占用问题以及如何查看一个网站对应的w3wp.exe(转)
- 如何计算java对象占用的内存
- Android 小项目之--解析如何获取SDCard 内存
- 如何解决Firefox内存占用高、启动速度慢的问题
- 如何提高《个人助手》的运行速度,减少内存占用!
- C++ 如何得到当前进程所占用的内存呢?
- [编程]如何减少.Net应用程序启动时占用的内存 [2007-04-02 更新]
- 如何计算java对象占用的内存
- 如何清除占用内存的文件