配色: 字号:
Android 性能优化——内存篇
2017-01-12 | 阅:  转:  |  分享 
  
Android性能优化——内存篇



一、android官方一些内存方面的内存tips

1、避免创建不必要的对象。



如尽量避免字符串的加号拼接,可以使用StringBuilder来拼接。

如果需要TextView设置多个字符串片段,可以使用textView.append方法,不要直接用加号拼起来。

2、尽量使用for-each循环,对于ArrayList,请使用普通的for,如:



intlen=list.size();

for(inti=0;i
//todosomgthing

}

3、使用系统库函数。



使用系统库函数,并且还有汇编级别的优化,他们通常比带有JIT的Java编译出来的代码更高效如:System.arraycopy();String.indexOf()等。

如果系统函数能够解决的,不要加入第三方库,可能第三方库使用起来比较简单(jsoup,htmlparser等)。如解析HTML可以使用系统的XmlPullParser。

二、使用ArrayMap、SparseArray代替HashMap

ArrayMap和HashMap的在内存的使用上,更加高效。



ArrayMap实现上有两个数组,一个数组是保存keyhash,另一个数组保存value,ArrayMap通过二分法(binarysearch)来进行查找的。



HashMap通过一个数组来实现的,keyhash作为数组的索引,这样就需要更大的内存来减少keyhash的冲突,keyhash就是数组的索引,所以查找效率很高。



使用建议:



当数据量比较小的时候(小于1000),优先使用ArrayMap,否则使用HashMap。



map里嵌套map。



使用方法:



ArrayMap和HashMap的使用方法都是一样的,ArrayMap也实现了Map接口。



另外,ArrayMap可以通过keyAt(index)方法来获取第index位置的key,keyValue(intindex)同理。但是HashMap是不可以的。



arrayMap.keyAt(0);

arrayMap.valueAt(0);

SparseArray和ArrayMap非常像,它们都是通过两种紧密包装的数组,而不是一个大的哈希散列,从而减少了整个内存的覆盖区。但是查询的速度就慢了。



只不过SparseArray和ArrayMap最大的区别是SparseArray的key是一个基本类型。



SparseArray的key是int类型,而不是Integer。像以前使用HashMap的时候,如果key是整形,必须是Integer。



Integer占16个字节,int只占4个字节,如果元素比较多,从而可以很好的减少内存的占用。



除了SparseArray类还有如下类可供使用:



SparseBooleanMap

SparseIntMap

SparseLongMap

SparseArray和ArrayMap的使用建议和使用方法都是一样的。



三、Thread与ThreadPool

在android开发中,一些耗时的操作都会放到后台线程去执行,比如:网络、本地文件、数据库等。



newThread(newRunnable(){

@Override

publicvoidrun(){

//dosomething...

}

}).start();

每个线程至少消耗64k的内存,如果你在某个时间点,迅速开启了很多线程(比如加载列表图片,然后用户滑动列表),这个时候可能内存使用量就会飙升。



会出现内存抖动(memorychurn),因为短时间开启了很多线程,完成任务后,这些线程都会被回收。内存表现为:低-高-低。甚至可能出现OOM。



一个系统所能处理的线程数量是有限的,如果超多了最大承载量,性能会受到很大的影响。而且可能还会影响用户的后续操作。



这时候ThreadPool线程池的作用就凸显出来了。



Java为我们提供了操作线程池的apiThreadPoolExecutor,ExecutorService是一个接口,相关的线程池的类都实现了该接口,如ThreadPoolExecutor。



创建一个线程池可以通过ThreadPoolExecutor类来实现。



ThreadPoolExecutorpool=newThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue);//新建一个线程池

pool.execute(Runnable);//执行任务

下面是官方对ThreadPoolExecutor的参数说明:



Parameters:

corePoolSize-thenumberofthreadstokeepinthepool,eveniftheyareidle,unlessallowCoreThreadTimeOutisset

maximumPoolSize-themaximumnumberofthreadstoallowinthepool

keepAliveTime-whenthenumberofthreadsisgreaterthanthecore,thisisthemaximumtimethatexcessidlethreadswillwaitfornewtasksbeforeterminating.

unit-thetimeunitforthekeepAliveTimeargument

workQueue-thequeuetouseforholdingtasksbeforetheyareexecuted.ThisqueuewillholdonlytheRunnabletaskssubmittedbytheexecutemethod.

corePoolSize核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。



maxPoolSize线程池允许最大的线程数量。



keepAliveTime当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。



allowCoreThreadTimeout是否允许核心线程空闲keepAliveTime退出,默认值为false。



workQueue任务队列。pool.execute(runnable)提交的task都会放到workQueue。



下面来一个简单的sample:



publicclassMyClass{



privateThreadPoolExecutorpool;



privateMyClass(){

//创建线程池

pool=newThreadPoolExecutor(4,7,60,TimeUnit.MILLISECONDS,newLinkedBlockingQueue());

}



publicstaticvoidmain(String[]args){

MyClassmyClass=newMyClass();

for(inti=0;i<10;i++){

//提交任务

myClass.pool.execute(newMyRunnable(myClass));

}

myClass.pool.shutdown();

}



privateStringgetCount(){

returnpool.getCorePoolSize()+"-"+pool.getActiveCount()+"-"+pool.getMaximumPoolSize();

}



privatestaticclassMyRunnableimplementsRunnable{

MyClassmyClass;



MyRunnable(MyClassmyClass){

this.myClass=myClass;

}



@Override

publicvoidrun(){

System.out.println("threadname:"+Thread.currentThread().getName()+"start"+myClass.getCount());

try{

//模拟耗时任务

Thread.sleep(3000L);

System.out.println("threadname:"+Thread.currentThread().getName()+"end"+myClass.getCount());



}catch(InterruptedExceptione){

e.printStackTrace();

}

}

}

}

上面的代码很简单:创建了一个corePoolSize为4,maxPoolSize为7的线程池。

然后往线程池里提交10个任务,每个任务打印pool.getCorePoolSize()+"-"+pool.getActiveCount()+"-"+pool.getMaximumPoolSize(),即corePoolSize(核心线程数),activeCount(正在活动的线程总数)和maximumPoolSize(线程池允许的最大线程数)值。



测试结果如下:



threadname:pool-1-thread-1start4-2-7

threadname:pool-1-thread-2start4-4-7

threadname:pool-1-thread-3start4-4-7

threadname:pool-1-thread-4start4-4-7

threadname:pool-1-thread-1end4-4-7

threadname:pool-1-thread-2end4-3-7

threadname:pool-1-thread-4end4-4-7

threadname:pool-1-thread-1start4-4-7

threadname:pool-1-thread-4start4-4-7

threadname:pool-1-thread-2start4-4-7

threadname:pool-1-thread-3end4-4-7

threadname:pool-1-thread-3start4-4-7

threadname:pool-1-thread-2end4-4-7

threadname:pool-1-thread-4end4-4-7

threadname:pool-1-thread-3end4-4-7

threadname:pool-1-thread-4start4-4-7

threadname:pool-1-thread-1end4-3-7

threadname:pool-1-thread-2start4-4-7

threadname:pool-1-thread-4end4-2-7

threadname:pool-1-thread-2end4-2-7



Processfinishedwithexitcode0

从测试结果来看,我们打印pool.getCorePoolSize()+"-"+pool.getActiveCount()+"-"+pool.getMaximumPoolSize()的值是正常的。但是只创建了4个线程:



pool-1-thread-1

pool-1-thread-2

pool-1-thread-3

pool-1-thread-4

我们设置了线程池的最大数为7,我们提交了10个任务,但是为什么只创建了corePoolSize=4个线程?



查看官方文档可以找到答案:



当通过execute(Runnable)提交一个新任务,并且小于corePoolSize正在运行的线程数,将会创建一个新的线程来处理这个任务,不管线程池里有没有空闲的线程。



IftherearemorethancorePoolSizebutlessthanmaximumPoolSizethreadsrunning,anewthreadwillbecreatedonlyifthequeueisfull.

大于corePoolSize小于maximumPoolSize,workQueue队列满了,才会创建新的线程。



如果corePoolSize和maximumPoolSize值设置成一样的,相当于创建了一个固定数量的线程池。



多数情况下,都是通过构造方法来设置corePoolSize和maximumPoolSize,但是也可以通过setCorePoolSize和setMaximumPoolSize来动态设置。



所以上面的例子,只创建了4个线程,因为虽然我们提交了10个任务,但是构建workQueue时候没有传入队列大小,默认大小是Integer.MAX_VALUE,所以workQueue是不会满的。所以最多就创建了4个线程。



据此,我把workQueue队列容量改成4:



pool=newThreadPoolExecutor(4,7,60,TimeUnit.MILLISECONDS,newLinkedBlockingQueue(4));

测试结果:



threadname:pool-1-thread-1start4-2-7

threadname:pool-1-thread-2start4-2-7

threadname:pool-1-thread-3start4-3-7

threadname:pool-1-thread-4start4-4-7

threadname:pool-1-thread-5start4-6-7

threadname:pool-1-thread-6start4-6-7

threadname:pool-1-thread-1end4-6-7

threadname:pool-1-thread-2end4-6-7

threadname:pool-1-thread-2start4-5-7

threadname:pool-1-thread-1start4-6-7

threadname:pool-1-thread-3end4-6-7

threadname:pool-1-thread-3start4-6-7

threadname:pool-1-thread-4end4-6-7

threadname:pool-1-thread-5end4-6-7

threadname:pool-1-thread-4start4-6-7

threadname:pool-1-thread-6end4-5-7

threadname:pool-1-thread-1end4-4-7

threadname:pool-1-thread-2end4-4-7

threadname:pool-1-thread-3end4-2-7

threadname:pool-1-thread-4end4-1-7



Processfinishedwithexitcode0

发现创建了6个线程,大于上一次的测试结果(上一次是创建了4个线程),可是我们设置的maximumPoolSize为7,按道理应该是创建7个线程才对呀,这是为什么呢?



这需要了解下workQueue队列的策略了。我们上面的列子使用的是LinkedBlockingQueue。



下面来看看官方文档对BlockingQueue的描述:



AnylinkBlockingQueuemaybeusedtotransferandhold

submittedtasks.Theuseofthisqueueinteractswithpoolsizing:

IffewerthancorePoolSizethreadsarerunning,theExecutor

alwaysprefersaddinganewthread

ratherthanqueuing.



IfcorePoolSizeormorethreadsarerunning,theExecutor

alwaysprefersqueuingarequestratherthanaddinganew

thread.



Ifarequestcannotbequeued,anewthreadiscreatedunless

thiswouldexceedmaximumPoolSize,inwhichcase,thetaskwillbe

rejected.

主要意思就是:



如果运行的线程少于corePoolSize,Executor会创建新线程来执行任务,不会把任务放进queue。



如果运行的线程等于或多于corePoolSize,Executor将请求加入队列,而不是创建新的线程。



如果队列已满,无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。



这样就能解释为什么只创建6个线程了。



总共有10个task,核心线程corePoolSize=4,所以3个任务是不会放进queue的,直接创建3个新线程来处理task了,然后再执行execute(Runnable)的时候,就会大于等于corePoolSize,所以就会把接下来的4个任务放进queue(容量为4),然后就剩下3个task了,发现队列已经满了,创建3个线程来处理这剩下的3个task,所以总共只创建6个线程了。



maximumPoolSize=7,我就是想让它创建7个线程,我们知道了上面的原理就很简单了,可以把队列的最大容量改成3或者添加11个任务就可以了:



newLinkedBlockingQueue(3)





for(inti=0;i<11;i++){

myClass.pool.execute(newMyRunnable(myClass));

}

效果如下:



threadname:pool-1-thread-1start0-1-7

threadname:pool-1-thread-2start0-2-7

threadname:pool-1-thread-3start1-4-7

threadname:pool-1-thread-4start4-5-7

threadname:pool-1-thread-5start4-7-7

threadname:pool-1-thread-6start4-7-7

threadname:pool-1-thread-7start4-7-7

.....

Java提供了3种策略的Queue



SynchronousQueue直接传送task(Directhandoffs)



LinkedBlockingQueue无边界队列(Unboundedqueues)



ArrayBlockingQueue有边界队列(Boundedqueues)

更多详细信息可以查看官方文档。



Java给我们提供了一些工厂方法来来创建线程池():



Executors.newFixedThreadPool(intthreads);



Executors.newCachedThreadPool();



Executors.newSingleThreadExecutor();



Executors.newScheduledThreadPool(intthreads);



这些方法都是通过构建ThreadPoolExecutor来实现的,具体的细节可以去看看文档,如果都不满足你的需求,可以自己构造ThreadPoolExecutor。



四、IntentService与Service

一般我们在app里的版本更新逻辑在Service里起一个线程来检测。



为了避免Service一直存在,减少内存消耗,检测版本后,还需要手动stopSelf,略麻烦。



这时候用IntentService就比较合适了,默认就给你启动了一个线程来执行耗时操作,完成自动关闭service。



Service和IntentService主要区别:



IntentService执行完会自动关闭(stopSelf),而Service不会。



IntentService会启动一个线程来执行耗时操作,把耗时操作放到onHandleIntent(Intentintent)方法里。而Service需要自己newThread。



如果调用startService(intent)多次,IntentService会执行多次onHandleIntent(Intentintent),且必须等本次的onHandleIntent(Intentintent)执行完,才会执行下一次onHandleIntent(Intentintent),说白了就是如果正在执行任务,会把后面启动的命令放到队列里。而多次调用startService(intent),Service仅仅会多次调用onStartCommand方法。



五、避免常见的内存泄露

1、CountDownTimer、TimerTask、Handler导致的内存泄露



在项目中,我们常常可能要做活动倒计时的功能,我是用CountDownTimer来做的。如:



publicstaticclassTimeCounterextendsCountDownTimer{

publicTimeCounter(longmillisInFuture,longcountDownInterval){

super(millisInFuture,countDownInterval);

}



@Override

publicvoidonFinish(){

//倒计时结束

}



@Override

publicvoidonTick(longmillisUntilFinished){

//每间隔固定时间执行一次

//再次处理倒计时逻辑

}

}

如下图所示:



倒计时

因为要在TimeCounter内部要修改View的显示,所以要把TextView传递进来,使用WeakReference来引用TextView避免内存泄露,如:



publicstaticclassTimeCounterextendsCountDownTimer{



privateWeakReferenceweakText;



publicTimeCounter(TextViewtextView,longmillisInFuture,longcountDownInterval){

super(millisInFuture,countDownInterval);

weakText=newWeakReference<>(textView);

}



@Override

publicvoidonFinish(){

//倒计时结束

}



@Override

publicvoidonTick(longmillisUntilFinished){

//每间隔固定时间执行一次

//再次处理倒计时逻辑

}



privatevoidsetText(longmillisUntilFinished){

if(weakText.get()!=null){

weakText.get().setText(xxx);

}

}

}

我们知道,使用WeakReference包装TextView,在发生GC的时候,TextView就会被回收。



需要注意的是,只有WeakReference所引用的对象没有被其他对象引用,当发生了GC,WeakReference所引用的对象才会被回收的。



例如,A界面有个倒计时功能,然后把TextView传给了上面的TimeCounter,然后在A界面的基础上启动了其他界面,这时候假如发生了GC,这时候TextView是不会被回收的。



因为还有A界面对其还有引用。所以只有把A界面关闭了,且发生GC了TextView才会被收回。



在网上也看到一些,加上了WeakReference在GC的时候也没有释放。可能的原因是WeakReference所引用的对象被其他对象引用着,所以发生GC了,该对象还是没被回收。



类似这样的内存泄露除了CountDownTimer还有Timer、Handler等,表现如下:



Timer



//设置为每秒执行一次(TimerTask的run方法是在后台线程执行的)

newTimer().scheduleAtFixedRate(

newTimerTask(){

@Override

publicvoidrun(){

Log.e("Timer","timer==========");

}

},0,1000);

Handler



//设置为每秒执行一次

handler=newandroid.os.Handler(){

@Override

publicvoidhandleMessage(Messagemsg){

super.handleMessage(msg);

Log.e("Handler","Handler");

sendEmptyMessageDelayed(1,1000);

}

};

handler.sendEmptyMessage(1);



//不可见时移除消息

@Override

protectedvoidonStop(){

super.onStop();

handler.removeMessages(1);

}

//可见的时发送消息

@Override

protectedvoidonResume(){

super.onResume();

if(!handler.hasMessages(1)){

sendEmptyMessageDelayed(1,1000);

}

}

上面的Timer和Handler都可以实现诸如每隔1秒执行的功能,都会导致内存泄露的问题。



解决方案:



使用静态内部类(相当于外部类,访问域不一样),如果需要使用外部类的变量如View,使用WeakReference引用外部的View;

当界面关闭的时候一定要把定时器Timer或CountDownTimer关闭掉(cancel),如果是Handler使用removeMessages(intwhat)方法;

2、内部类(如Thread)导致的内存泄露



在项目常常要做一些耗时操作,可以起一个Thread来做,如:



newThread(newRunnable(){

@Override

publicvoidrun(){

Log.e("Log","执行耗时操作");

}

}).start();

如果在Activity直接这样使用,容易造成activity的内存泄露。



因为上面的Thread代码段,实际上是匿名内部类对象,它会对外部类(Activity)有一个引用。



如果Thread一直在执行,就算用户按了返回键,activity对象会一直存在的。



因为匿名内部类对象一直引用者activity对象。



是否泄露在于Thread执行的耗时任务执行时间,如果Thread执行非常短时间就完毕了,基本上造成不了多大的内存泄露,但是耗时任务执行的时间无法预测的。



下面一个简单的例子来演示下这个问题:



publicclassThreadActivityextendsActivity{



@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activty_count_down_timer);

newThread(newRunnable(){

@Override

publicvoidrun(){

Log.e("ThreadActivity","执行耗时操作------"+ThreadActivity.this);

try{

//模拟耗时操作60秒

Thread.sleep(100060);

}catch(InterruptedExceptione){

e.printStackTrace();

}

Log.e("ThreadActivity","耗时操作执行完成------"+ThreadActivity.this);

}

}).start();

}



@Override

protectedvoidonDestroy(){

super.onDestroy();

Log.e("ThreadActivity","onDestroy------");

}

}

运行起来后,接着按返回键,测试效果如下:



执行耗时操作------com.chiclaim.twitter.ThreadActivity@2e92d7ee

onDestroy------

耗时操作执行完成------com.chiclaim.twitter.ThreadActivity@2e92d7ee

从上面的代码可以看出,耗时任务我们定为60秒,启动了Activity后,立马输出了:



执行耗时操作------com.chiclaim.twitter.ThreadAwww.tt951.comctivity@2e92d7ee

紧接着按了返回键,输出了:



onDestroy------

过了大概60秒,输出了:



耗时操作执行完成------com.chiclaim.twitter.ThreadActivity@2e92d7ee

从上面的例子中我们得出了2个结论:



匿名内部类对象,它会对外部类有一个引用。

Activity执行了onDestroy方法,不见得Activity被销毁了。上面的例子中,过了60秒依然可以输出外部类(Activity)对象。

解决方案:



尽量减少非静态内部类的使用,上面的例子可以使用静态匿名内部类对象或者使用静态内部类,这样就不会对外部类对象由引用了。

在app中的这种耗时任务,一般我们处理逻辑的时候,只需要处理成功和失败的情况,比如说网络请求,如果一个界面有多个请求,那么就会有很多内部类(回调)嵌套了,我的做法是使用者实现一个回调接口,该接口定义了成功和失败的两个方法,ActivityonCreate的时候,把回调注册到一个容器内,在onDestroy方法里从容器中移除该回调。这样也不会对Activity造成内存泄露。

另外,如果某个耗时操作,需要传入Context,如果没有特殊的要求,不要传递Activity的Context。传入Application级别的Context,这样不管耗时操作执行多久,都不会导致Activity内存泄露。

3、由static的Activity、View、List导致的内存泄露



千万不要是有static来修饰activity、View对象,这种内存泄露更加严重,因为它将贯穿程序的生命周期。

为了更好的管理Activity常常把Activity放进容器中,如Stack。如果忘记了移除,也会造成内存泄漏。



六、onTrimMemory(intlevel)与onLowMemory()

onTrimMemory回调是Android4.0之后提供的一个API。



它的主要用来提示开发者在系统内存不足的时候,根据当前内存情况(level),释放相关资源以减小系统内存压力,这样可以减少app进程被系统杀死的可能性。



尽可能的保存app进程,等到用户在下一次使用的时候,启动速度就会比较快。



在Android4.0之前都是onLowMemory来处理这类逻辑的,onLowMemory和onTrimMemory中的TRIM_MEMORY_COMPLETE级别相同。如果想兼容Android4.0之前的系统可以实现该方法,否则只需要处理onTrimMemory方法。



下面来聊一聊onTrimMemory(intlevel)回调level的常量:



TRIM_MEMORY_UI_HIDDEN=20表示应用程序的所有UI界面被隐藏了,即用户点击了Home键或者剩下最后一个界面,按返回键后,Application的onTrimMemory回调也会调用。



下面三个等级是当我们的应用程序正在前台运行时的回调:



TRIM_MEMORY_RUNNING_MODERATE=5表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经有点低了,你的正在运行的进程需要释放一些不需要的内存资源。



TRIM_MEMORY_RUNNING_LOW=10表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经非常低了,你的正在运行的进程需要释放一些不需要的内存资源。



TRIM_MEMORY_RUNNING_CRITICAL=15表示应用程序仍然正常运行,但是系统内存已经极度低了,即将不能保留任何后台进程了。这个时候我们应当尽可能地去释放任何不必要的资源,下一步onLowwww.baiyuewang.netMemory将会被调用,这样的话,后台将不会保留任何进程。



当app进程不可见处于LRUlist中,则会收到以下常量的回调:



TRIM_MEMORY_BACKGROUND=40app进程不可见,处于LRU列表中,这时候是个释放资源的好时机。



TRIM_MEMORY_MODERATE=60系统目前内存已经很低了,并且我们的程序处于LRU缓存列表的中间位置。腾出一些内存让系统运行其他的进程。



TRIM_MEMORY_COMPLETE=80系统目前内存已经很低了,并且我们的程序处于LRU缓存列表的最边缘位置,如果系统找不到更多可能的内存,我们的app进程很快将被杀死。



从上面可以看出,这些常量大致可以分为两类,一类是大于TRIM_MEMORY_UI_HIDDEN=20,这类表示进程不可见。一类是小于TRIM_MEMORY_UI_HIDDEN=20,这类表示app进程正在前台运行。并且常量值越大,说明系统内存越紧张。



下面是官方推荐实现方案:



/

ReleasememorywhentheUIbecomeshiddenorwhensystemresourcesbecomelow.

@paramlevelthememory-relatedeventthatwasraised.

/

publicvoidonTrimMemory(intlevel){



//Determinewhichlifecycleorsystemeventwasraised.

switch(level){



caseComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

/

ReleaseanyUIobjectsthatcurrentlyholdmemory.



Theuserinterfacehasmovedtothebackground.

/

break;



caseComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:

caseComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:

caseComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

/

Releaseanymemorythatyourappdoesn''tneedtorun.



Thedeviceisrunninglowonmemorywhiletheappisrunning.

Theeventraisedindicatestheseverityofthememory-relatedevent.

IftheeventisTRIM_MEMORY_RUNNING_CRITICAL,thenthesystemwill

beginkillingbackgroundprocesses.

/

break;



caseComponentCallbacks2.TRIM_MEMORY_BACKGROUND:

caseComponentCallbacks2.TRIM_MEMORY_MODERATE:

caseComponentCallbacks2.TRIM_MEMORY_COMPLETE:

/

Releaseasmuchmemoryastheprocesscan.



TheappisontheLRUlistandthesystemisrunninglowonmemory.

TheeventraisedindicateswheretheappsitswithintheLRUlist.

IftheeventisTRIM_MEMORY_COMPLETE,theprocesswillbeoneof

thefirsttobeterminated.

/

break;



default:

/

Releaseanynon-criticaldatastructures.



Theappreceivedanunrecognizedmemorylevelvalue

fromthesystem.Treatthisasagenericlow-memorymessage.

/

break;

}

如何针对上面的这些常量,分别释放app里哪些资源呢?(目前我只处理app在后台的情况)



当回调的参数level=TRIM_MEMORY_BACKGROUND(40)或TRIM_MEMORY_MODERATE(60)或TRIM_MEMORY_COMPLETE(80)时,



把图片占用的内存释放掉。



清空缓存数据,例如列表中List的数据;还可以把动态创建的View或fragment释放掉,甚至activity的相关资源都释放掉变成空Activity,从新回到这个Activity的时候,重新初始化数据。



我会把所有的activity移除掉,仅留下主Activity。当然如果主Activity比较复杂,占用的资源比较多,可以把资源都释放掉,留下一个空主Activity。当用户回来的时候可以迅速回来,如果内容清空了,重新加载即可。



相关注意事项:



在回调中释放内存,要注意该要释放的界面是否可见,界面如果正在显示,你却把当前的界面的List数据清空了,这样显然是不妥的,系统会通知所有实现了onTrimMemory方法的相关组件(Application、Activity、Fragment、Service、ContentProvider)



还要注意,做好相关数据恢复的逻辑,例如,把相关的数据清空了,该界面重新显示的时候,要把相关释放的数据,重新加载,如果可以的话,尽可能回到用户上一次操作的状态,例如滑动的位置,所填写的数据等。

献花(0)
+1
(本文系thedust79首藏)