从去年七月份(2018/7/13)入职到现在(2019/8/15)已经一年多了,这一年从一个菜鸟开始慢慢学习到了很多东西,记录一下在开发过程中遇到的代码优化和性能优化经验,方便让其他人少走弯路。
性能优化
1、装箱带来的内存消耗
Boolean isShow =new Boolean(true) ;
上面的代码会带来如下问题:
//boolean
Boolean isShow =Boolean.TRUE ;
//integer
Integer i= 2;
2、sharedPreferences使用commit带来的线程阻塞
final SharedPreferences.Editor edit = settings.edit();
edit.putBoolean(TIPS, true);
edit.commit();
上面的代码会带来如下问题:
3、selector中item位置错误
错误
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/normal" />
<item android:drawable="@drawable/pressed" android:state_pressed="true"/>
</selector>
上面的selector会导致如下问题
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/pressed" android:state_pressed="true"/>
<item android:drawable="@drawable/normal" />
</selector>
把没有任何条件选择的item放到最后,当前面的item都不匹配时就会选择最后的item。
4、context引起的内存泄漏
public static WifiManager getWIFIManager(Context ctx) {
WifiManager wm = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
}
上面的代码直接使用context来获取系统的WiFi服务,会导致下面问题
public static WifiManager getWIFIManager(Context ctx) {
WifiManager wm = (WifiManager) ctx.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
}
这里为什么不用activity的context而要用application的context呢?因为activity的context生命周期和activity一致,当activity释放时context也应该被释放,这里由于context被wifiManager持有会导致activity不能被释放,而出现内存泄漏。application的context生命周期和application生命周期一致。所以当获取与当前activity生命周期无关而与整个应用生命周期有关的资源时,要使用application的context。
public Context getApplicationContext ()
/*Return the context of the single, global Application object of the current process. This generally should only be used if you need a Context whose lifecycle is separate from the current context, that is tied to the lifetime of the process rather than the current component.
*/
5、使用SparseArray代替HashMap
在Android中如果要存放的<key,value>中的key是基本数据类型:int,long,等基本数据类型时可以用SparseArray来代替HashMap,可以避免自动装箱和HashMap中的entry等带来的内存消耗。能被SparseArray代替的<key,value>类型有如下几种:
SparseArray <Integer, Object>
SparseBooleanArray <Integer, Boolean>
SparseIntArray <Integer, Integer>
SparseLongArray <Integer, Long>
LongSparseArray <Long, Object>
LongSparseLongArray <Long, Long> //this is not a public class
对比存放1000个元素的SparseIntArray和HashMap<Integer, Integer> 如下:
SparseIntArray
class SparseIntArray {
int[] keys;
int[] values;
int size;
}
Class = 12 + 3 * 4 = 24 bytes Array = 20 + 1000 * 4 = 4024 bytes Total = 8,072 bytes
HashMap
class HashMap<K, V> {
Entry<K, V>[] table;
Entry<K, V> forNull;
int size;
int modCount;
int threshold;
Set<K> keys
Set<Entry<K, V>> entries;
Collection<V> values;
}
Class = 12 + 8 * 4 = 48 bytes Entry = 32 + 16 + 16 = 64 bytes Array = 20 + 1000 * 64 = 64024 bytes Total = 64,136 bytes 可以看到存放相同的元素,HashMap占用的内存几乎是SparseIntArray的8倍。
SparseIntArray的缺点
SparseIntArray采用的二分查找法来查找个keys,因此查找某个元素的速度没有Hashmap的速度快。存储的元素越多时速度比hashmap的越慢,因此当当数据量不大时可以采用SparseIntArray,但是当数据量特别大时采用HashMap会有更高的查找速度。
6、自定义view中在layout、draw、onMeasue中new对象
问题代码
pubic class myview extends View
{
public myview(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
int x=80;
int y=80;
int radius=40;
Paint paint=new Paint();
// Use Color.parseColor to define HTML colors
paint.setColor(Color.parseColor("#CD5C5C"));
canvas.drawCircle(x,x, radius, paint);
}
}
自定义view的时候经常会重写onLayout ,onDraw ,onMeasue方法,但是要注意的是,如果在这些方法里面new 对象就会有如下问题
public class myview extends View
{
int x;
int y;
int radius;
Paint paint;
public myview(Context context) {
super(context);
init();
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
// Use Color.parseColor to define HTML colors
paint.setColor(Color.parseColor("#CD5C5C"));
canvas.drawCircle(x,x, radius, paint);
}
private void init()
{
x=80;
y=80;
radius=40;
paint=new Paint();
}
}
看下Android官网上的解释为什么不能在onLayout ,onDraw ,onMeasue方法里面执行new 对象和执行初始化操作:
Creating objects ahead of time is an important optimization. Views are redrawn very frequently, and many drawing objects require expensive initialization. Creating drawing objects within your onDraw() method significantly reduces performance and can make your UI appear sluggish.
上面的意思总结一下就是在自定义view的时候最好提前初始化和new 对象,因为onDraw,onMeasure,onLayout的方法调用十分频繁,如果在这里初始化和new 对象会导致频繁的gc操作严重影响性能,也可能会导致掉帧现象。
ondraw的调用时机 1、view初始化的时候。 2、当view的invalidate() 方法被调用。什么时候会调用invalidate()方法呢?当view的状态需要改变的时候,比如botton被点击,editText相应输入等,就会reDraw调用onDraw方法。
7、静态变量引起的内存泄漏
问题代码
public class MyDlg extends Dialog {
private View mDialog;
private static MyDlg sInstance;
public MyDlg(Context context) {
super(context);
sInstance = this;
init();
}
private void init() {
mDialog = LayoutInflater.from(mCtx).inflate(R.layout.dialog_app_praise, null);
setContentView(mDialog);
}
上面的代码会导致如下问题:
8、overdraw问题
问题代码
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="@drawable/layout_content_bkg" >
<include layout="@layout/title_bar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dip"
android:scrollbars="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/layout_content_bkg"
android:layout_marginTop="14dip"
android:paddingLeft="13dip"
android:paddingRight="13dip" >
.......
</LinearLayout>
</ScrollView>
</LinearLayout>
上面代码中linearlayout的background和ScrollView 里面的background一样只需要保留父布局LinearLayout里面的background就行了,不然会多进行一次绘制也就是引起overdraw问题。
9、inefficent layout weight
问题代码
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal"
>
<TextView
android:layout_weight="1"
android:id="@+id/text"
android:layout_width="165dp"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:textSize="15sp" />
<android.support.v7.widget.SwitchCompat
android:checked="true"
android:id="@+id/0checkbox"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_marginEnd="15dp" />
</LinearLayout>
上面布局中一个linearLayout里面包含一个textview和一个SwitchCompat,textview中的layout_weight="1",此时也明确给出了textview的layout_width="165dp",这个时候会带来下面的问题
10、字符串操作优化
String text="bitch";
//1
StringBuilder.append(" fuck " + text);
//2
mStringBuilder.append(" fuck ").append(text);
上面代码中1和2哪个代码性能更好?第二种性能更高,第一种方式会先new一个“fuck”+text的字符串然后在append到StringBuilder中,而第二种方法中不用new一个字符串,所以性能更高。
代码优化
1、消除redundant “Collection.addAll()”
原始代码:
ArrayList<BaseFile> lists = null;
if (getImages() != null) {
lists = new ArrayList<>();
lists .addAll(getImages());
}
优化代码:
ArrayList<BaseFile> lists = null;
if (getImages() != null) {
lists = new ArrayList<>(getImages());
}
2、使用array的copy方法
原始代码:
for(int i=0;i<ITEM_SIZE;i++){
mContentsArray[i] = contents[i];
}
优化代码:
System.arraycopy(contents, 0, mContentsArray, 0, ITEM_SIZE);
参考文献
1、stackoverflow.com/questions/5…
2、stackoverflow.com/questions/4…
3developer.android.com/reference/a…
4、stackoverflow.com/questions/2…
5、developer.android.com/training/cu… 6、stackoverflow.com/questions/1…