简介
- 相信很多工程师都想过进行内存性能优化,但是却不知从何下受,本文将分享本人的性能优化路线,以供参考。
步骤
一、解决内存泄漏---Leakcanary
-
Leakcanary是Square提供的优秀开源组件,github地址。
-
这里简单介绍一下这个组件,原理是监控每个activity,在activity ondestory后,在后台线程检测引用,然后过一段时间进行gc,gc后如果引用还在,那么dump出内存堆栈,并解析进行可视化显示。使用LeakCanary可以快速地检测出Android中的内存泄露。
常见的内存泄露问题
单例(由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏)
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context;//问题在此 //建议不要传入activity的context,而使用Application 的context this.context = MyApplication.getContext();//建议使用 } public static AppManager getInstance() { if (instance == null) { instance = new AppManager(); } return instance; } }
静态变量(同样也是因为生命周期比较长)
- 如果成员变量被声明为 static,那其生命周期将与整个app进程生命周期一样。如无法正常销毁,会常驻内存。慎用static
private static Context context;//将出现内存泄漏 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); context = this; }
- 另外,这会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收。
匿名内部类(匿名内部类会引用外部类,导致无法释放,比如线程、各种回调等)
-
Runnable
- 内存泄漏写法
new Thread(new Runnable() { @Override public void run() { try { //模拟耗时操作 Thread.sleep(15000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
- 优化
new Thread(new MyRunnable()).start() private static class MyRunnable implements Runnable { @Override public void run() { try { Thread.sleep(15000); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
AsyncTask
- 内存泄漏写法
new AsyncTask<String,Integer,String>(){ @Override protected String doInBackground(String... params) { try { Thread.sleep( 6000 ); } catch (InterruptedException e) { } return null; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); } }.execute();
- 优化
private static MyTask myTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myTask = new MyTask(); myTask.execute(); } //1、创建静态内部类 private static class MyTask extends AsyncTask { @Override protected Object doInBackground(Object[] params) { try { Thread.sleep(15000); } catch (InterruptedException e) { e.printStackTrace(); } return ""; } } @Override protected void onDestroy() { super.onDestroy(); //2、取消异步任务 if (myTask != null) { myTask.cancel(true); } }
-
TimerTask
- 内存泄漏写法
new Timer().schedule(new TimerTask() { @Override public void run() { while (true) ; } }, 1000); // 1秒后启动一个任务
- 优化
private TimerTask timerTask ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); timerTask = new MyTimerTask() ; new Timer().schedule( timerTask ,1000 ); // 1秒后启动一个任务 } private static class MyTimerTask extends TimerTask { @Override public void run() { while(true){ } } } @Override protected void onDestroy() { super.onDestroy(); //取消定时任务 if ( timerTask != null ){ timerTask.cancel() ; } }
Handler内存泄露
- 内存泄漏写法
private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); }
- 优化
//1、避免Handler引用activity造成的内存泄漏:使用静态内部类+ 使用弱引用 private static class MyHandler extends Handler { WeakReference<HandlerActivity> weakReference; public MyHandler(HandlerActivity activity) { weakReference = new WeakReference<HandlerActivity>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (weakReference.get() != null) { // update android ui } } } private MyHandler mHandler = new MyHandler(this); //2、避免非静态Runnable内部类引用activity造成的内存泄漏 private static class MyRunnable implements Runnable { @Override public void run() { //do something } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mHandler.postDelayed(new MyRunnable(), 1000 * 60 * 10); } @Override protected void onDestroy() { super.onDestroy(); //3、如果参数为null的话,会将所有的Callbacks和Messages全部清除掉。 handler.removeCallbacksAndMessages(null); }
资源使用完未关闭(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。
- 比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
- 资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
- 对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
- Bitmap对象不在使用时调用recycle()释放内存。Bitmap在大多数情况下不需手动recycle,但是当图片占用比较大或者非常介意内存使用情况是,建议还是手动recyecle。
二、图片分辨率相关
- 图片分辨率适配问题大多数情况下是内存优化大户,可能你解决一个内存泄漏才剩下来几百K,但是把图片适配做好,一张图就能剩好几M,完全不是一个数量级。
- 我们知道可以通过将图片放到hdpi/xhdpi/xxhdpi等不同文件夹进行适配,通过xml android:background设置背景图片,或者通过BitmapFactory.decodeResource()方法,图片实际上默认情况下是会进行缩放的。
- 举个例子,对于一张1280×720的图片,如果放在xhdpi,那么xhdpi的设备拿到的大小还是1280×720而xxhpi的设备拿到的可能是1920×1080,这两种情况在内存里的大小分别为:3.68M和8.29M,相差4.61M,在移动设备来说这几M的差距还是很大的。
- 虽然我们现在大多数情况下都会使用成熟的图片加载组件(Glide、Freso等等),它们对内存的优化都是非常先进的,但是也不能完全避免自己使用bitmap,所以在使用的时候建议参考这些开源组件里面的设计思想,做好资源复用、释放等操作。
三、图片压缩
- 很多时候并不需要把高分辨率的原图拿出来,这样会非常吃内存。BitmapFactory 在解码图片时,可以带一个Options,有一些非常使用的参数,如:
- nTargetDensity 表示要被画出来时的目标像素密度。
- inSampleSize 这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4。
- inJustDecodeBounds 字面意思就可以理解就是只解析图片的边界,有时如果只是为了获取图片的大小就可以用这个,而不必直接加载整张图片。
- inPreferredConfig 默认会使用ARGB_8888,在这个模式下一个像素点将会占用4个byte,而对一些没有透明度要求或者图片质量要求不高的图片,可以使用RGB_565,一个像素只会占用2个byte,一下可以省下50%内存。
- inBitmap 官方推荐使用的参数,表示重复利用图片内存,减少内存分配,在4.4以前只有相同大小的图片内存区域可以复用,4.4以后只要原有的图片比将要解码的图片大既可以复用了。