性能优化时Android老生常谈的问题了,接下来我们分步骤来看下一可以从哪些方面来进行性能的优化。
1、数据结构的选择。
正确的选择合适的数据结构是很重要的,对于Java中常见的数据结构我们也是经常使用的到例如
ArrayList和LinkedList、HashMap和HashSet等等。我们一SparseArray代替HashMap为例进行说明。
SparseArray是Android平台独有的稀疏数组的实现,它是Integer到Object的一个映射,它可以对应的HashMap的写法是
HashMap<Ingteger,<E>>。SparseArray的核心算法是二分法查找。我们来看一下
public static int banerySearch(int[] array,int value) { int start=0; int end=array.length; while (start<=end) { int mid_p=(start+end)>>>1; if (value>array[mid_p]) { start=mid_p+1; }else if (value<array[mid_p]) { end=mid_p-1; }else { return mid_p+1; } } return -1;//没有找到 }
我们看下SparseArray家族有哪些类:SparseBooleanArray 、SparseIntArray、SparseLongArray、SparseArray<String>
我们接下来看看HashMap的get原理
具体get过程(考虑特殊情况如果两个键的hashcode相同,你如何获取值对象?)
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
我们通过一幅图来看一下
这种方式来查找的话显然速度不如SparseArray更加的灵活,快捷。当然并不是说SparseArray要比HashMap更优,只是在某些地方上使用它更合适些。HashMap的构造还是有点复杂的,接下来也会写一篇关于HashMap的构造和实现原理的。
SparseArray需要注意的一些地方:
- SparseArray不是线程安全的
- 由于要进行二分查找,因此SparseArray会对插入的数据按照Key值大小顺序插入。
- SparseArray对删除操作做了优化,它不会立即删除这个元素,而是通过设置标识位(DELETE)的方式,后面尝试重用。
2、内存泄漏
Android中我们常常会用Handle来进行线程间通信。我们知道Java中非静态匿名内部类会持有外部类的一个隐式的引用,这样就可能会导致外部类无法被垃圾回收。所以我们要如果解决这种问题呢?
办法就是将Handler声明成为静态的内部类,这样他就不会持有外部类的引用了。
public class HandlerInnerActivity extends AppCompatActivity { private static class InnerHandler extends Handler{ private final WeakReference<HandlerInnerActivity> innerActivity; public InnerHandler(HandlerInnerActivity activity){ innerActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { HandlerInnerActivity ac = innerActivity.get(); if (ac != null) { //继续代码 } } } private final InnerHandler handler = new InnerHandler(this); private static final Runnable runnable=()->{//lambda表达式 //书写自己的代码 }; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); handler.postDelayed(runnable, 1000 * 60 * 3); } }
3、Context的选择
单独的把Context拿出来写是因为我之前在开发中因为Context的选取错误导致内存溢出,对是内存溢出。为什么会内存溢出?我想大家应该能猜出来是在哪些地方导致的内存溢出。是图片的加载。我们大家应该都用过Glide的吧,Glide的基础方法
Glide.with(context) .load(url) .into(imageView); 我相信大家都很熟悉。我们从中可以看到context的身影。而这个Context的生命周期实际上就是我们这个ImageView的生命周期,我想很多人这个context会选择的是Activity的Context或者Application的Context。说道这里大家可能会说我们都是这样用的为啥没出现过内存溢出。那么我要说的问题在哪呢?这里先卖个关子,我们来看下Activity+Fragment这种模式大家都用过吧。这种模式一般都是要加载大量的列表,而当我们的列表大都是包含图片的时候,这时我们的内存中会缓存大量的图片数据,上面我提到的Context生命周期的问题,我们来想一下,当我们的Activity加载fragment在左右切换的时候大量的bitmap汇聚到内存中,而且我们的ImageView又不能被回收,最终导致OOM,我们应该怎么解决呢?其实很简单我们在取Context的时候取imageView的context。这样就不会有OOM的问题了。在这里安利下Glide的框架,作为主流的图片加载框架之一,glide的可以加载gif,包体积量小,很好的内存复用机制,使用方法简单等等,基本可以满足我们大众app的图片加载需求。
上面说的是使用生命周期短的Context,接下来我们要说的是要使用生命周期长的Context。单利模式大家都用过。有时候我们也是要传递Context给他进行一下操作。如果我们传递的是activity的context的话,会导致对应的Activity被单利引用,从而不会被垃圾回收,而且其关联的View或者数据结构对象也不会被释放,从而会导致内存泄漏。正确的做法呢是使用生命周期最长的Application的Context。
接下来我们看一下Context的功能
NO[1]表示不建议这么做 NO[2]也是一样
4、其他代码微优化或者标准
- 避免创建非必要的对象(避免在循环条件中创建相同的对象,如果需要的话可以使用对象池)
- 对基本数据类型和String类型的常量建议使用static final 修饰。(对于基本数据类型和String类型就不会涉及类的初始化问题,而是直接调用字面量)
- 尽量少使用Getters/Setters。Android中我们一般继承 Parcel来实现序列化操作
- 代码重构。这个不做探讨。
5、图片优化
1、我们应该都知道Png格式和JPEG格式的图片区别。JPEG是一种有损压缩图像标准格式,它不支持透明。而png格式的图片是一种无损压缩格式,支持完整的透明通道。所以一般png格式的图片都会比JPEG格式的图片占用空间大。所以可以从这边进行app的瘦身操作。
6、电量优化
电量消耗一般是对于地图、导航、运动类App来说。
因此我们需要正确使用BroadcastReceiver、AlarmManager、WakeLock等
BroadcastReceiver:为了减少应用耗损的电量,我们在代码中需要进来的避免一些无用的操作代码的执行。比如当我们的应用退出到后台的时候,界面的刷新就是一种浪费内存和电量的事情。我们可以在界面onpause的时候取消广播的监听操作.
public void enableBroadcast(boolean isEnable,Class<?> receiver){ PackageManager pm = getPackageManager(); ComponentName receiverName = new ComponentName(this, receiver); int newState; if (isEnable) { newState = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; }else { newState = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; } pm.setComponentEnabledSetting(receiverName, newState, PackageManager.DONT_KILL_APP); }
同样的定位监听的取消
public void disableLocaltionListener(LocationListener listener){ LocationManager manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return; } manager.removeUpdates(listener); } 根据具体的业务需求设置一个合适的更新位置的频率值,通常需要在定位精度和耗电量之间综合考虑。我们可以通过requestLocationUpdates方法进行设置,主要的两个参数是 minTime:用来指定更新通知的最小时间间隔,单位是毫秒 minDistance:用来指定位置更新通知的最小距离,单位是米。 实际开发中大都会选择百度或者高德等的定位这些都存在了电量和精度方面的优化。
WakeLock:
private PowerManager.WakeLock wakeLock = null; //获取唤醒锁 private void acquireWakeLock(){ if (wakeLock == null) { PowerManager powerManager = (PowerManager) this.getSystemService(Context.POWER_SERVICE); wakeLock = powerManager.newWakeLock( PowerManager.ON_AFTER_RELEASE | PowerManager.PARTIAL_WAKE_LOCK,"PlayService"); if (wakeLock != null) { wakeLock.acquire(); } } } //释放唤醒锁 private void releaseWakeLock(){ if (wakeLock != null) { wakeLock.release(); wakeLock = null; } } 在我们需要唤醒屏幕的地方启动,在不需要持续点亮屏幕的时候关闭释放。
7、布局优化
- 使用include标签来引入其他的xml文件
<include layout="@layout/layout_bottom" />
- 使用ViewStub标签来延迟加载
- 使用merge标签来减少布局的层次
- 避免过深的布局嵌套。
- 最外层布局尽量少使用background属性
- 尽量少使用android:layout_weight属性。