在android开发内存优化中,有哪些好的内存优化方式

Android&内存优化
一、&Android的内存机制
&&&&Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似。程序员通过new为对象分配内存,所有对象在java堆内分配空间;然而对象的释放是由垃圾回收器来完成的。C/C++中的内存机制是“谁污染,谁治理”,java的就比较人性化了,给我们请了一个专门的清洁工(GC)。
&&&&那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象&(连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
二、Android的内存溢出
&&&&Android的内存溢出是如何发生的?
&&&&Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。
为什么会出现内存不够用的情况呢?我想原因主要有两个:
由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放。
保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。
三、万恶的static
&&&&static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。
public&class&ClassName&{&&
&&&&&private&static&Context&mC&&
&&&&&//省略&&
以上的代码是很危险的,如果将Activity赋值到么mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放。
&我们举Android文档中的一个例子。
private&static&Drawable&sB&&
&@Override&&
&protected&void&onCreate(Bundle&state)&{&&
&&&super.onCreate(state);&&
&&&TextView&label&=&new&TextView(this);&&
&&&label.setText("Leaks&are&bad");&&
&&&if&(sBackground&==&null)&{&&
&&&&&sBackground&=&getDrawable(R.drawable.large_bitmap);&&
&&&label.setBackgroundDrawable(sBackground);&&
&&&setContentView(label);&&
&&&&sBackground,&是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:
&&&&Drawable-&TextView-&Context
&&&&所以,最终该Context也没有得到释放,发生了内存泄露。
&&&&如何才能有效的避免这种引用的发生呢?
&&&&第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
&&&&第二、Context尽量使用Application&Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
&&&&第三、使用WeakReference代替强引用。比如可以使用WeakReference&mContextR
&&&&该部分的详细内容也可以参考Android文档中Article部分。
四、都是线程惹的祸
&&&&线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
public&class&MyActivity&extends&Activity&{&&
&&&&@Override&&
&&&&public&void&onCreate(Bundle&savedInstanceState)&{&&
&&&&&&&&super.onCreate(savedInstanceState);&&
&&&&&&&&setContentView(R.layout.main);&&
&&&&&&&&new&MyThread().start();&&
&&&&private&class&MyThread&extends&Thread{&&
&&&&&&&&@Override&&
&&&&&&&&public&void&run()&{&&
&&&&&&&&&&&&super.run();&&
&&&&&&&&&&&&//do&somthing&&
&&&&&&&&}&&
&&&&这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。
&&&&由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
&有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。
&&&&这种线程导致的内存泄露问题应该如何解决呢?
&&&&第一、将线程的内部类,改为静态内部类。
&&&&第二、在线程内部采用弱引用保存Context引用。
&&&&解决的模型如下:
public&abstract&class&WeakAsyncTaskProgress,&Result,&WeakTarget&&extends&&
&&&&&&&&AsyncTaskProgress,&Result&&{&&
&&&&protected&WeakReference&mT&&
&&&&public&WeakAsyncTask(WeakTarget&target)&{&&
&&&&&&&&mTarget&=&new&WeakReference(target);&&
&&&&@Override&&
&&&&protected&final&void&onPreExecute()&{&&
&&&&&&&&final&WeakTarget&target&=&mTarget.get();&&
&&&&&&&&if&(target&!=&null)&{&&
&&&&&&&&&&&&this.onPreExecute(target);&&
&&&&&&&&}&&
&&&&@Override&&
&&&&protected&final&Result&doInBackground(Params...&params)&{&&
&&&&&&&&final&WeakTarget&target&=&mTarget.get();&&
&&&&&&&&if&(target&!=&null)&{&&
&&&&&&&&&&&&return&this.doInBackground(target,&params);&&
&&&&&&&&}&else&{&&
&&&&&&&&&&&&return&&&
&&&&&&&&}&&
&&&&@Override&&
&&&&protected&final&void&onPostExecute(Result&result)&{&&
&&&&&&&&final&WeakTarget&target&=&mTarget.get();&&
&&&&&&&&if&(target&!=&null)&{&&
&&&&&&&&&&&&this.onPostExecute(target,&result);&&
&&&&&&&&}&&
&&&&protected&void&onPreExecute(WeakTarget&target)&{&&
&&&&&&&&//&No&default&action&&
&&&&protected&abstract&Result&doInBackground(WeakTarget&target,&Params...&params);&&
&&&&protected&void&onPostExecute(WeakTarget&target,&Result&result)&{&&
&&&&&&&&//&No&default&action&&
&&&&事实上,线程的问题并不仅仅在于内存泄露,还会带来一些灾难性的问题。由于本文讨论的是内存问题,所以在此不做讨论。
由于51cto不让我一次传完,说我的字数太多了,所以分开传了。&
五、超级大胖子Bitmap
&&&&可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,它是一个“超级大胖子”,特别是分辨率大的图片,如果要显示多张那问题就更显著了。
&&&&如何解决Bitmap带给我们的内存问题?
&&&&第一、及时的销毁。
&&&&虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。
&&&&第二、设置一定的采样率。
&&&&有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
&private&ImageView&&&
&BitmapFactory.Options&options&=&new&BitmapFactory.Options();&&
&options.inSampleSize&=&2;//图片宽高都为原来的二分之一,即图片为原来的四分之一&&
&Bitmap&bitmap&=&BitmapFactory.decodeStream(cr.openInputStream(uri),&null,&options);&&
preview.setImageBitmap(bitmap);&&
&&&&第三、巧妙的运用软引用(SoftRefrence)
&&&&有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。如下例:
private&class&MyAdapter&extends&BaseAdapter&{&&
&&&&private&ArrayList&&mBitmapRefs&=&new&ArrayList&();&&
&&&&private&ArrayList&mV&&
&&&&private&Context&mC&&
&&&&private&LayoutInflater&mI&&
&&&&MyAdapter(Context&context,&ArrayList&values)&{&&
&&&&&&&&mContext&=&&&
&&&&&&&&mValues&=&&&
&&&&&&&&mInflater&=&(LayoutInflater)&context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);&&
&&&&public&int&getCount()&{&&
&&&&&&&&return&mValues.size();&&
&&&&public&Object&getItem(int&i)&{&&
&&&&&&&&return&mValues.get(i);&&
&&&&public&long&getItemId(int&i)&{&&
&&&&&&&&return&i;&&
&&&&public&View&getView(int&i,&View&view,&ViewGroup&viewGroup)&{&&
&&&&&&&&View&newView&=&&&
&&&&&&&&if(view&!=&null)&{&&
&&&&&&&&&&&&newView&=&&&
&&&&&&&&}&else&{&&
&&&&&&&&&&&&newView&=(View)mInflater.inflate(R.layout.image_view,&false);&&
&&&&&&&&}&&
&&&&&&&&Bitmap&bitmap&=&BitmapFactory.decodeFile(mValues.get(i).fileName);&&
&&&&&&&&mBitmapRefs.add(new&SoftReference(bitmap));&&&&&//此处加入ArrayList&&
&&&&&&&&((ImageView)newView).setImageBitmap(bitmap);&&
&&&&&&&&return&newV&&
六、行踪诡异的Cursor
&&&&Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。
&&&&然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor&close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。
&&&&所以我们使用Cursor的方式一般如下:
Cursor&cursor&=&&&
&&&&cursor&=&mContext.getContentResolver().query(uri,null,&null,null,null);&&
&&&&if(cursor&!=&null)&{&&
&&&&&&&&cursor.moveToFirst();&&
&&&&&&&&//do&something&&
}&catch&(Exception&e)&{&&
&&&&e.printStackTrace();&&&&
}&finally&{&&
&&&&if&(cursor&!=&null)&{&&
&&&&&&&cursor.close();&&
&&&&有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。
@Override&&
protected&void&onDestroy()&{&&&&&&&&
&&&&if&(mAdapter&!=&null&&&&mAdapter.getCurosr()&!=&null)&{&&
&&&&&&&&mAdapter.getCursor().close();&&
&&&&super.onDestroy();&&&
&&CursorAdapter中的changeCursor函数,会将原来的Cursor释放掉,并替换为新的Cursor,所以你不用担心原来的Cursor没有被关闭。
&&你可能会想到使用Activity的managedQuery来生成Cursor,这样Cursor就会与Acitivity的生命周期一致了,多么完美的解决方法!然而事实上managedQuery也有很大的局限性。
&&&&managedQuery生成的Cursor必须确保不会被替换,因为可能很多程序事实上查询条件都是不确定的,因此我们经常会用新查询的Cursor来替换掉原先的Cursor。因此这种方法适用范围也是很小。
七、其它要说的。
&&&&其实,要减小内存的使用,其实还有很多方法和要求。比如不要使用整张整张的图,尽量使用9path图片。Adapter要使用convertView等等,好多细节都可以节省内存。这些都需要我们去挖掘,谁叫Android的内存不给力来着。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。主题信息(必填)
主题描述(最多限制在50个字符)
申请人信息(必填)
申请信息已提交审核,请注意查收邮件,我们会尽快给您反馈。
如有疑问,请联系
如今的编程是一场程序员和上帝的竞赛,程序员要开发出更大更好、傻瓜都会用到软件。而上帝在努力创造出更大更傻的傻瓜。目前为止,上帝是赢的。个人网站:。个人QQ群:、
CSDN &《程序员》编辑/记者,我的邮箱
全栈攻城狮不知道是怎么炼成的
一只文艺范的软件攻城狮,Keep Learn,Always.
个人大数据技术博客:
作者简介:胡凯(),腾讯Android工程师,热爱开源与分享,维护Android官方培训课程协作项目,关注Android应用性能优化的总结与分享,推崇Android官方最佳实践。个人博客:,Github:。
第4季的课程学习笔记终于在2015年的最后一天完成了(并于日正式发布在CSDN上),文章共17个段落,包含的内容大致有:优化网络请求的行为,优化安装包的资源文件,优化数据传输的效率,性能优化的几大基础原理等等。因为学习认知水平有限,肯定存在不少理解偏差甚至错误的地方,请多多交流指正!1) Cachematters for networking想要使得Android系统上的网络访问操作更加的高效就必须做好网络数据的缓存,这是提高网络访问性能最基础的步骤之一。从手机的缓存中直接读取数据肯定比从网络上获取数据要更加的便捷高效,特别是对于那些会被频繁访问到的数据,需要把这些数据缓存到设备上,以便更加快速的进行访问。Android系统上关于网络请求的Http Response Cache是默认关闭的,这样会导致每次即使请求的数据内容是一样的也会需要重复被调用执行,效率低下。我们可以通过下面的代码示例开启。开启Http Response Cache之后,Http操作相关的返回数据就会缓存到文件系统上,不仅仅是主程序自己编写的网络请求相关的数据会被缓存,另外引入的library库中的网络相关的请求数据也会被缓存到这个Cache中。网络请求的场景有可以是普通的http请求,也可以打开某个URL去获取数据,如下图所示:我们有两种方式来清除HttpResponseCache的缓存数据:第一种方式是缓存溢出的时候删除最旧最老的文件,第二种方式是通过Http返回Header中的Cache-Control字段来进行控制的。如下图所示:通常来说,HttpResponseCache会缓存所有的返回信息,包括实际的数据与Header的部分.一般情况下,这个Cache会自动根据协议返回Cache-Control的内容与当前缓存的数据量来决定哪些数据应该继续保留,哪些数据应该删除。但是在一些极端的情况下,例如服务器返回的数据没有设置Cache废弃的时间,或者是本地的Cache文件系统与返回的缓存数据有冲突,或者是某些特殊的网络环境导致HttpResponseCache工作异常,在这些情况下就需要我们自己来实现Http的缓存Cache。实现自定义的http缓存,需要解决两个问题:第一个是实现一个DiskCacheManager,另外一个是制定Cache的缓存策略。关于DiskCacheManager,我们可以扩展Android系统提供的来实现。而Cache的缓存策略,相对来说复杂一些,我们可能需要把部分JSON数据设计成不能缓存的,另外一些JSON数据设计成可以缓存几天的,把缩略图设计成缓存一两天的等等,为不同的数据类型根据他们的使用特点制定不同的缓存策略。想要比较好的实现这两件事情,如果全部自己从头开始写会比较繁琐复杂,所幸的是,有不少著名的开源框架帮助我们快速的解决了那些问题。我们可以使用、、来实现网络缓存。实现好网络缓存之后,我们可以使用Android Studio里面的Network Traffic Tools来查看网络数据的请求与返回情况,另外我们还可以使用工具来抓取网络数据包进行分析查看。2) Optimizing Network Request Frequencies应用程序的一个基础功能是能够保持确保界面上呈现的信息是即时最新的,例如呈现最新的新闻,天气,信息流等等信息。但是,过于频繁的促使手机客户端应用去同步最新的服务器数据会对性能产生很大的负面影响,不仅仅使得CPU不停的在工作,内存,网络流量,电量等等都会持续的被消耗,所以在进行网络请求操作的时候一定要避免多度同步操作。退到后台的应用为了能够在切换回前台的时候呈现最新的数据,会偷偷在后台不停的做同步的操作。这种行为会带来很严重的问题,首先因为网络请求的行为异常的耗电,其次不停的进行网络同步会耗费很多带宽流量。为了能够尽量的减少不必要的同步操作,我们需要遵守下面的一些规则:
首先我们要对网络行为进行分类,区分需要立即更新数据的行为和其他可以进行延迟的更新行为,为不同的场景进行差异化处理。
其次要避免客户端对服务器的轮询操作,这样会浪费很多的电量与带宽流量。解决这个问题,我们可以使用Google Cloud Message来对更新的数据进行推送。
然后在某些必须做同步的场景下,需要避免使用固定的间隔频率来进行更新操作,我们应该在返回的数据无更新的时候,使用双倍的间隔时间来进行下一次同步。
最后更进一步,我们还可以通过判断当前设备的状态来决定同步的频率,例如判断设备处于休眠,运动等不同的状态设计各自不同时间间隔的同步频率。
另外,我们还可以通过判断设备是否连接上Wi-Fi,是否正在充电来决定更新的频率。为了能够方便的实现这个功能,Android为我们提供了来判断设备当下的状态,从而设计更加高效的网络同步操作,如下图所示:3) Effective Prefetching关于提升网络操作的性能,除了避免频繁的网络同步操作之外,还可以使用捆绑批量访问的方式来减少访问的频率,为了达到这个目的,我们就需要了解Prefetching。举个例子,在某个场景下,一开始发出了网络请求得到了某张图片,隔了10s之后,发出第二次请求想要拿到另外一张图片,再隔了6s发出第三张图片的网络请求。这会导致设备的无线蜂窝一直处于高消耗的状态。Prefetching就是预先判定那些可能马上就会使用到的网络资源,捆绑一起集中进行网络请求。这样能够极大的减少电量的消耗,提升设备的续航时间。使用Prefetching的难点在于如何判断事先获取的数据量到底是多少,如果预取的数据量偏少,那么就起不到什么效果,但是如果预取过多,又可能导致访问的时间过长。那么问题来了,到底预取多少才比较合适呢?一个比较普适的规则是,在3G网络下可以预取1-5MB的数据量,或者是按照提前预期后续1-2分钟的数据作为基线标准。在实际的操作当中,我们还需要考虑当前的网络速度来决定预取的数据量,例如在同样的时间下,4G网络可以获取到12张图片的数据,而2G网络则只能拿到3张图片的数据。所以,我们还需要把当前的网络环境情况添加到设计预取数据量的策略当中去。判断当前设备的状态与网络情况,可以使用前面提到过的。4) Adapting to Latency网络延迟通常来说很容易被用户察觉到,严重的网络延迟会对用户体验造成很大的影响,用户很容易抱怨应用程序写的不好。一个典型的网络操作行为,通常包含以下几个步骤:首先手机端发起网络请求,到达网络服务运营商的基站,再转移到服务提供者的服务器上,经过解码之后,接着访问本地的存储数据库,获取到数据之后,进行编码,最后按照原来传递的路径逐层返回。如下图所示:在上面的网络请求链路当中的任何一个环节都有可能导致严重的延迟,成为性能瓶颈,但是这些环节可能出现的问题,客户端应用是无法进行调节控制的,应用能够做的就只是根据当前的网络环境选择当下最佳的策略来降低出现网络延迟的概率。主要的实施步骤有两步:第1步检测收集当前的网络环境信息,第2步根据当前收集到的信息进行网络请求行为的调整。关于第1步检测当前的网络环境,我们可以使用系统提供的API来获取到相关的信息,如下图所示:通过上面的示例,我们可以获取到移动网络的详细子类型,例如4G(LTE)、3G等等,详细分类见下图,获取到详细的移动网络类型之后,我们可以根据当前网络的速率来调整网络请求的行为:关于第2步根据收集到的信息进行策略的调整,通常来说,我们可以把网络请求延迟划分为三档:例如把网络延迟小于60ms的划分为GOOD,大于220ms的划分为BAD,介于两者之间的划分为OK(这里的60ms、220ms会需要根据不同的场景提前进行预算推测)。如果网络延迟属于GOOD的范畴,我们就可以做更多比较激进的预取数据的操作,如果网络延迟属于BAD的范畴,我们就应该考虑把当下的网络请求操作Hold住等待网络状况恢复到GOOD的状态再进行处理。前面提到说60ms、220ms是需要提前自己预测的,可是预测的工作相当复杂。首先针对不同的机器与网络环境,网络延迟的三档阈值都不太一样,出现的概率也不尽相同,我们会需要针对这些不同的用户与设备选择不同的阈值进行差异化处理:Android官方为了帮助我们设计自己的网络请求策略,提供了模拟器的网络流量控制功能来对实际环境进行模拟测量,或者还可以使用AT&T提供的来帮助预估网络延迟。5) Minimizing Asset Payload为了能够减小网络传输的数据量,我们需要对传输的数据做压缩的处理,这样能够提高网络操作的性能。首先不同的网络环境,下载速度以及网络延迟是存在差异的,如下图所示:如果我们选择在网速更低的网络环境下进行数据传输,这就意味着需要执行更长的时间,而更长的网络操作行为,会导致电量消耗更加严重。另外传输的数据如果不做压缩处理,也同样会增加网络传输的时间,消耗更多的电量。不仅如此,未经过压缩的数据,也会消耗更多的流量,使得用户需要付出更多的流量费。通常来说,网络传输数据量的大小主要由两部分组成:图片与序列化的数据,那么我们需要做的就是减少这两部分的数据传输大小,分下面两个方面来讨论。
A)首先需要做的是减少图片的大小,选择合适的图片保存格式是第一步。下图展示了PNG,JPEG,WEBP三种主流格式在占用空间与图片质量之间的对比:
对于JPEG与WEBP格式的图片,不同的清晰度对占用空间的大小也会产生很大的影响,适当的减少JPG Quality,可以大大地缩小图片占用的空间大小。另外,我们需要为不同的使用场景提供当前场景下最合适的图片大小,例如针对全屏显示的情况我们会需要一张清晰度比较高的图片,而如果只是显示为缩略图的形式,就只需要服务器提供一个相对清晰度低很多的图片即可。服务器应该支持到为不同的使用场景分别准备多套清晰度不一样的图片,以便在对应的场景下能够获取到最适合自己的图片。这虽然会增加服务端的工作量,可是这个付出却十分值得!
B)其次需要做的是减少序列化数据的大小。JSON与XML为了提高可读性,在文件中加入了大量的符号,空格等等字符,而这些字符对于程序来说是没有任何意义的。我们应该使用Protocal Buffers,Nano-Proto-Buffers,FlatBuffer来减小序列化的数据的大小。
Android系统为我们提供了工具来查看网络传输的数据情况,打开Android Studio的Monitor,里面有网络访问的模块。或者是打开AT&T提供的工具来查看网络请求状态。6) Service Performance PatternsService是Android程序里面最常用的基础组件之一,但是使用Service很容易引起电量的过度消耗以及系统资源的未及时释放。学会在何时启用Service以及使用何种方式杀掉Service就显得十分有必要了。简要过一下Service的特性:Service和UI没有关联,Service的创建,执行,销毁Service都是需要占用系统时间和内存的。另外Service是默认运行在UI线程的,这意味着Service可能会影响到系统的流畅度。使用Service应该遵循下面的一些规则:
避免错误的使用Service,例如我们不应该使用Service来监听某些事件的变化,不应该搞一个Service在后台对服务器不断的进行轮询(应该使用Google Cloud Messaging)
如果已经事先知道Service里面的任务应该执行在后台线程(非默认的主线程)的时候,我们应该使用IntentService或者结合HanderThread,AsycnTask Loader实现的Service。
Android系统为我们提供了以下的一些异步相关的工具类
BroadcastReciever
LocalBroadcastReciever
WakefulBroadcastReciver
HandlerThreads
AsyncTaskLoaders
IntentService
如果使用上面的诸多方案还是无法替代普通的Service,那么需要注意的就是如何正确的关闭Service。
普通的Started Service,需要通过stopSelf()来停止Service;
另外一种Bound Service,会在其他组件都unBind之后自动关闭自己。
把上面两种Service进行合并之后,我们可以得到如下图所示的Service(相关知识,还可以参考, )7) Removing unused code使用第三方库(library)可以在不用自己编写大量代码的前提下帮助我们解决一些难题,节约大量的时间,但是这些引入的第三方库很可能会导致主程序代码臃肿冗余。如果我们处在人力、财力都相对匮乏的情况下,通常会倾向大量使用第三方库来帮助编写应用程序。这其实是无可厚非的,那些著名的第三方库的可行性早就被很多应用所采用并实践证明过。但是这里面存在的问题是,如果我们因为只需要某个library的一小部分功能而把整个library都导入自己的项目,这就会引起代码臃肿。一旦发生代码臃肿,用户就会下载到安装包偏大的应用程序,另外因为代码臃肿,还很有可能会超过单个编译文件只能有65536个方法的上限。解决这个问题的办法是使用MultiDex的方案,可是这实在是无奈之举,原则上,我们还是应该尽量避免出现这种情况。Android为我们提供了Proguard的工具来帮助应用程序对代码进行瘦身,优化,混淆的处理。它会帮助移除那些没有使用到的代码,还可以对类名,方法名进行混淆处理以避免程序被反编译。举个例子,Google I/O 2015这个应用使用了大量的library,没有经过Proguard处理之前编译出来的包是8.4Mb大小,经过处理之后的包仅仅是4.1Mb大小。使用Proguard相当的简单,只需要在build.gradle文件中配置minifEnable为true即可,如下图所示:但是Proguard还是不足够聪明到能够判断哪些类,哪些方法是不能够被混淆的,针对这些情况,我们需要手动的把这些需要保留的类名与方法名添加到Proguard的配置文件中,如下图所示:在使用library的时候,需要特别注意这些library在proguard配置上的说明文档,我们需要把这些配置信息添加到自己的主项目中。关于Proguard的详细说明,请看官方文档8) Removing unused resources减少APK安装包的大小也是Android程序优化中很重要的一个方面,我们不应该给用户下载到一个臃肿的安装包。假设这样一个场景,我们引入了Google Play Service的library,是想要使用里面的Maps的功能,但是里面的登入等等其他功能是不需要的,可是这些功能相关的代码与图片资源,布局资源如果也被引入我们的项目,这样就会导致我们的程序安装包臃肿。所幸的是,我们可以使用Gradle来帮助我们分析代码,分析引用的资源,对于那些没有被引用到的资源,会在编译阶段被排除在APK安装包之外,要实现这个功能,对我们来说仅仅只需要在build.gradle文件中配置shrinkResource为true就好了,如下图所示:为了辅助gradle对资源进行瘦身,或者是某些时候的特殊需要,我们可以通过tools:keep或者是tools:discard标签来实现对特定资源的保留与废弃,如下图所示:Gradle目前无法对values,drawable等根据运行时来决定使用的资源进行优化,对于这些资源,需要我们自己来确保资源不会有冗余。9) Perf Theory: Caching当我们讨论性能优化的时候,缓存是最常见最有效的策略之一。无论是为了提高CPU的计算速度还是提高数据的访问速度,在绝大多数的场景下,我们都会使用到缓存。关于缓存是如何提高效率的,这里就不赘述了。那么在什么地方,在何时应该利用好缓存来提高效率呢?请看下面的例子,很明显的演示了在某些细节上是如何利用缓存的原理来提高代码的执行效率的:类似上面的例子采用缓存原理的地方还有很多,例如缓存到内存里面的图片资源,网络请求返回数据的缓存等等。总之,使用缓存就是为了减少不必要的操作,尽量复用已有的对象来提高效率。10) Perf Theory: Approximation(近似法)很多时候,我们都需要学会在性能更优与体验更好之间做一定的权衡取舍。为了获取更好的表现性能,我们可能会需要牺牲一些用户体验,例如把某些细节做删除或者是降级处理以便有更好的性能。例如,导航类的应用,如果在导航期间是不停的执行定位的操作,这样能够很及时的获取到最新的位置信息以及当下位置相关的其他提示信息,但是这样会导致网络流量以及手机电量的过度消耗。所以我们可以做一定的降级处理,每隔固定的一段时间才去获取一次位置信息,损失一点及时性来换取更长的续航时间。还有很多地方都会用到近似法则来优化程序的性能,例如使用一张比较接近实际大小的图片来替代原图,换取更快的加载速度。所以对于那些对计算结果要求不需要十分精确的场景,我们可以使用近似法则来提高程序的性能。11) Perf Theory: Culling(遴选,挑选)在以前的性能优化课程里面,我们知道可以通过减少Overdraw来提高程序的渲染性能(主要手段有移除非必须的background,减少重叠的布局,使用clipRect来提高自定义View的绘制性能),今天在这里要介绍的另外一个提高性能的方法是逐步对数据进行过滤筛选,减小搜索的数据集,以此提高程序的执行性能。例如我们需要搜索到居住在某个地方,年龄是多少,符合某些特定条件的候选人,就可以通过逐层过滤筛选的方式来提高后续搜索的执行效率。12) Perf Theory: Threading使用多线程并发处理任务,从某种程度上可以快速提高程序的执行性能。对于Android程序来说,主线程通常也成为UI线程,需要处理UI的渲染,响应用户的操作等等。对于那些可能影响到UI线程的任务都需要特别留意是否有必要放到其他的线程来进行处理。如果处理不当,很有可能引起程序ANR。关于多线程的使用建议,可以参考官方的培训课程13) Perf Theory: Batching关于Batching,在前几季的性能优化课程里面也不止一次提到,下面使用一张图演示下Batching的原理:网络请求的批量执行是另外一个比较适合说明batching使用场景的例子,因为每次发起网络请求都相对来说比较耗时耗电,如果能够做到批量一起执行,可以大大的减少电量的消耗。14) Serialization performance数据的序列化是程序代码里面必不可少的组成部分,当我们讨论到数据序列化的性能的时候,需要了解有哪些候选的方案,他们各自的优缺点是什么。首先什么是序列化?用下面的图来解释一下:数据序列化的行为可能发生在数据传递过程中的任何阶段,例如网络传输,不同进程间数据传递,不同类之间的参数传递,把数据存储到磁盘上等等。通常情况下,我们会把那些需要序列化的类实现Serializable接口(如下图所示),但是这种传统的做法效率不高,实施的过程会消耗更多的内存。但是我们如果使用GSON库来处理这个序列化的问题,不仅仅执行速度更快,内存的使用效率也更高。Android的XML布局文件会在编译的阶段被转换成更加复杂的格式,具备更加高效的执行性能与更高的内存使用效率。下面介绍三个数据序列化的候选方案:
:强大,灵活,但是对内存的消耗会比较大,并不是移动终端上的最佳选择。
:基于Protocal,为移动终端做了特殊的优化,代码执行效率更高,内存使用效率更佳。
:这个开源库最开始是由Google研发的,专注于提供更优秀的性能。
上面这些方案在性能方面的数据对比如下图所示:为了避免序列化带来的性能问题,我们其实可以考虑使用SharedPreference或者SQLite来存储那些数据,避免需要先把那些复杂的数据进行序列化的操作。15) Smaller Serialized Data数据呈现的顺序以及结构会对序列化之后的空间产生不小的影响。通常来说,一般的数据序列化的过程如下图所示:上面的过程,存在两个弊端,第一个是重复的属性名称:另外一个是GZIP没有办法对上面的数据进行更加有效的压缩,假如相似数据间隔了32k的数据量,这样GZIP就无法进行更加有效的压缩:但是我们稍微改变下数据的记录方式,就可以得到占用空间更小的数据,如下图所示:通过优化,至少有三方面的性能提升,如下图所示:1)减少了重复的属性名:2)使得GZIP的压缩效率更高:3)同样的数据类型可以批量优化:16) Caching UI data如今绝大多数的应用界面上呈现的数据都依赖于网络请求返回的结果,如何做到在网络数据返回之前避免呈现一个空白的等待页面呢(当然这里说的是非首次冷启动的情况)?这就会涉及到如何缓存UI界面上的数据。缓存UI界面上的数据,可以采用方案有存储到文件系统,Preference,SQLite等等,做了缓存之后,这样就可以在请求数据返回结果之前,呈现给用户旧的数据,而不是使用正在加载的方式让用户什么数据都看不到,当然在请求网络最新数据的过程中,需要有正在刷新的提示。至于到底选择哪个方案来对数据进行缓存,就需要根据具体情况来做选择了。17) CPU Frequency Scaling调节CPU的频率会执行的性能产生较大的影响,为了最大化的延长设备的续航时间,系统会动态调整CPU的频率,频率越高执行代码的速度自然就越快。Android系统会在电量消耗与表现性能之间不断的做权衡,当有需要的时候会迅速调整CPU的频率到一个比较高负荷的状态,当程序不需要高性能的时候就会降低频率来确保更长的续航时间。Android系统检测到需要调整CPU的频率到CPU频率真的达到对应频率会需要花费大概20ms的时间,在此期间很有可能会因为CPU频率不够而导致代码执行偏慢。我们可以使用Systrace工具来导出CPU的执行情况,以便帮助定位性能问题。
系列阅读:
责任编辑:唐小引(),邮箱:。
本文为CSDN特约文章,未经允许不得转载,如需转载请联系mobile#csdn.net(#换成@)}

我要回帖

更多关于 android内存优化 汇总 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信