android高級進階之12條代碼優化以及性能優化

從去年七月份(2018/7/13)入職到現在(2019/8/15)已經一年多了,這一年從一個菜鳥開始慢慢學習到了很多東西,記錄一下在開發過程中遇到的代碼優化和性能優化經驗,方便讓其他人少走彎路。

性能優化

1、裝箱帶來的內存消耗

Boolean isShow =new Boolean(true) ;  

上面的代碼會帶來如下問題:
在這裏插入圖片描述
上面的意思總結一下就是,採用裝箱在java 5及以上是沒必要的,採用裝箱的方式構造一個對象會佔用更多的內存,而使用比如說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();

上面的代碼會帶來如下問題:
在這裏插入圖片描述
上面的代碼如果在ui線程執行會帶來ui線程的阻塞,可能會造成掉幀,原因是commit是在當前線程中執行寫內存操作的並且commit執行完後會返回一個bool值來表示是否寫成功,而apply會在異步線程裏面寫操作,執行完後不會返回是否寫成功的值,其實大部分情況下並不需要知道sharedPreferences是否寫成功所以可以用apply來替代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會導致如下問題
在這裏插入圖片描述
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。

4、context引起的內存泄漏

public static WifiManager getWIFIManager(Context ctx) {
	WifiManager wm = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
}

上面的代碼直接使用context來獲取系統的WiFi服務,會導致下面問題
在這裏插入圖片描述
獲取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.
*/

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);
  }       

上面的代碼會導致如下問題:
在這裏插入圖片描述
上面代碼中靜態變量sInstance持有來context而這裏的context是持有當前dialog的activity,由於靜態變量一般只有在App銷燬的時候纔會進行銷燬(此時類經歷了,加載、連接、初始化、使用、和卸載)所以當activity執行完時由於被dialog中的靜態變量持有無法被gc,所以造成內存泄漏。而這種dialog如果被多個地方調用就會造成嚴重的內存泄漏。

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”,這個時候會帶來下面的問題
在這裏插入圖片描述
當linearLayout的佈局裏面只有一個子view使用weight屬性時如果LinearLayout是垂直佈局這個子view應該設置layout_height=“0dp”,如果是水平佈局這個子view應該layout_width=“0dp”,這樣執行onMeasure的時候會首先不去measure 這個佈局,可以提高性能。

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、https://stackoverflow.com/questions/5960678/whats-the-difference-between-commit-and-apply-in-shared-preference

2、https://stackoverflow.com/questions/4128589/difference-between-activity-context-and-application-context

3https://developer.android.com/reference/android/content/ContextWrapper.html#getApplicationContext()

4、https://stackoverflow.com/questions/25560629/sparsearray-vs-hashmap

5、https://developer.android.com/training/custom-views/custom-drawing
6、https://stackoverflow.com/questions/11912406/view-ondraw-when-does-it-get-called

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章