從去年七月份(2018/7/13)入職到如今(2019/8/15)已經一年多了,這一年從一個菜鳥開始慢慢學習到了不少東西,記錄一下在開發過程當中遇到的代碼優化和性能優化經驗,方便讓其餘人少走彎路。php
Boolean isShow =new Boolean(true) ;
複製代碼
上面的代碼會帶來以下問題: html
上面的意思總結一下就是,採用裝箱在java 5及以上是不必的,採用裝箱的方式構造一個對象會佔用更多的內存,而使用好比說Boolean.TRUE的方式只是一個常量因此採用下面的方式更節約內存,正確的方式以下://boolean
Boolean isShow =Boolean.TRUE ;
//integer
Integer i= 2;
複製代碼
final SharedPreferences.Editor edit = settings.edit();
edit.putBoolean(TIPS, true);
edit.commit();
複製代碼
上面的代碼會帶來以下問題: java
上面的代碼若是在ui線程執行會帶來ui線程的阻塞,可能會形成掉幀,緣由是commit是在當前線程中執行寫內存操做的而且commit執行完後會返回一個bool值來表示是否寫成功,而apply會在異步線程裏面寫操做,執行完後不會返回是否寫成功的值,其實大部分狀況下並不須要知道sharedPreferences是否寫成功因此能夠用apply來替代commit。錯誤android
<?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會致使以下問題 canvas
selector中press的item屬性永遠不會被觸發,爲何呢?由於在selector中從前日後匹配屬性,第一個item和任何屬性都會匹配,因此就算是執行了press的也是先匹配到上面的第一個item。 正確:<?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。性能優化
public static WifiManager getWIFIManager(Context ctx) {
WifiManager wm = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
}
複製代碼
上面的代碼直接使用context來獲取系統的WiFi服務,會致使下面問題 app
獲取WiFi_SERVICE必須要用application 的context不能使用activity的context,若是上面方法中傳入的是activity的context就會形成內存泄漏。 正確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. */ 複製代碼
在Android中若是要存放的<key,value>中的key是基本數據類型:int,long,等基本數據類型時能夠用SparseArray來代替HashMap,能夠避免自動裝箱和HashMap中的entry等帶來的內存消耗。能被SparseArray代替的<key,value>類型有以下幾種:ide
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會有更高的查找速度。
問題代碼
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的調用時機 一、view初始化的時候。 二、當view的invalidate() 方法被調用。何時會調用invalidate()方法呢?當view的狀態須要改變的時候,好比botton被點擊,editText相應輸入等,就會reDraw調用onDraw方法。
問題代碼
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);
}
複製代碼
上面的代碼會致使以下問題:
上面代碼中靜態變量sInstance持有來context而這裏的context是持有當前dialog的activity,因爲靜態變量通常只有在App銷燬的時候纔會進行銷燬(此時類經歷了,加載、鏈接、初始化、使用、和卸載)因此當activity執行完時因爲被dialog中的靜態變量持有沒法被gc,因此形成內存泄漏。而這種dialog若是被多個地方調用就會形成嚴重的內存泄漏。問題代碼
<?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問題。
問題代碼
<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",這個時候會帶來下面的問題
當linearLayout的佈局裏面只有一個子view使用weight屬性時若是LinearLayout是垂直佈局這個子view應該設置layout_height="0dp",若是是水平佈局這個子view應該layout_width="0dp",這樣執行onMeasure的時候會首先不去measure 這個佈局,能夠提升性能。String text="bitch";
//1
StringBuilder.append(" fuck " + text);
//2
mStringBuilder.append(" fuck ").append(text);
複製代碼
上面代碼中1和2哪一個代碼性能更好?第二種性能更高,第一種方式會先new一個「fuck」+text的字符串而後在append到StringBuilder中,而第二種方法中不用new一個字符串,因此性能更高。
原始代碼:
ArrayList<BaseFile> lists = null;
if (getImages() != null) {
lists = new ArrayList<>();
lists .addAll(getImages());
}
複製代碼
優化代碼:
ArrayList<BaseFile> lists = null;
if (getImages() != null) {
lists = new ArrayList<>(getImages());
}
複製代碼
原始代碼:
for(int i=0;i<ITEM_SIZE;i++){
mContentsArray[i] = contents[i];
}
複製代碼
優化代碼:
System.arraycopy(contents, 0, mContentsArray, 0, ITEM_SIZE);
複製代碼
一、stackoverflow.com/questions/5…
二、stackoverflow.com/questions/4…
3developer.android.com/reference/a…
四、stackoverflow.com/questions/2…
五、developer.android.com/training/cu… 六、stackoverflow.com/questions/1…