注:根據Android官方的建議,編寫高效代碼的三個基本準則以下:java
正確的選擇合適的數據結構很重要,對java中常見的數據結構例如ArrayList和LinkedList、HashMap和HashSet等,須要作到對它們的聯繫與區別有教深刻的理解,這樣在編寫代碼中面臨選擇時才能做出正確的選擇,下面咱們以android開發中使用SparseArray代替HashMap爲例進行說明。SparseArray是Android平臺特有的稀疏數組的實現,它是Integer到Object的一個映射,在特定場合可用於代替HashMap<Integer,<E>>,提升性能。核心實現是二分法查找算法。android
————————————————————————————————————————程序員
SparseArray家族目前有如下四類:算法
————————————————————————————————————————編程
HashMap<Integer, Boolean> booleanHashMap = new HashMap<>();
SparseBooleanArray booleanArray = new SparseBooleanArray();
HashMap<Integer,Integer> integerHashMap = new HashMap<>();
SparseIntArray intArray = new SparseIntArray();
HashMap<Integer,Long> longHashMap = new HashMap<>();
SparseLongArray sparseLongArray = new SparseLongArray();
HashMap<Integer,String> stringHashMap = new HashMap<>();
SparseArray<String> sparseArray = new SparseArray<>();複製代碼
————————————————————————————————————————設計模式
須要注意的幾點以下:數組
在Android工程中運行Lint進行靜態代碼塊分析,會有一個名爲AndroidLintUseSparseArrays的檢查項,若是違規,它會提示:緩存
————————————————————————————————————————安全
HashMap can be replaced with SparseArraybash
————————————————————————————————————————
這樣能夠很輕鬆地找到工程中優化的地方。
————————————————————————————————————————
Android代碼中涉及線程間通訊的地方常常會使用Handler,典型的代碼結構以下:
————————————————————————————————————————
public class HandlerActivity extends Activity {
private final Handler mLeakyHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}複製代碼
————————————————————————————————————————
使用AndroidLint分析這段代碼,會違反檢查項AndroidLintHanderLeak,獲得以下提示:
————————————————————————————————————————
This Handler class should be static or leaks might occur
————————————————————————————————————————
那麼產生內存泄漏的緣由多是什麼?Handler和Looper以及MessageQueue一塊兒工做的,在Android中,一個應用啓動後,系統默認會建立一個爲主線程服務的Looper對象,該Looper對象用於處理主線程的全部Message對象,它的生命週期貫穿於整個應用的生命週期。在主線程中使用的Handler都會默認綁定到這個Looper對象。在主線程中建立Handler對象時,它會當即關聯主線程Looper對象的MessageQueue,這時發送到MessageQueue中的Message對象都會持有這個Handler對象的引用,這樣Looper處理消息時才能回調到Handler的handlerMessage方法。所以,若是Message尚未被處理完成,那麼Handler對象也就不會被垃圾回收。
在上面的代碼中,將Handler的實例聲明爲HandlerActivity類的內部類。而在Java語言中,非靜態內部匿名類會持有外部類的一個隱式的引用,這樣就可能會致使外部類沒法被垃圾回收。所以,最終因爲MessageQueue中的Message尚未處理完成,就會持有Handler對象的引用,而非靜態的Handler對象會持有外部類HandlerActivity的引用,這樣Activity沒法被垃圾回收,從而致使內存泄漏。
一個明顯的會引入內存泄漏的例子以下:
————————————————————————————————————————
/**
* ================================================================
* User:xijiufu
* Email:xjfsml@163.com
* Version:1.0
* Time:2017/4/20--2:07
* Function:
* ModifyHistory:
* ================================================================
*/
public class HandlerActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//延遲10分鐘發送消息
mLeakyHandler.postAtTime(new Runnable() {
@Override
public void run() {
/***/
}
}, 1000 * 60 * 10);
}
}複製代碼
————————————————————————————————————————
因爲消息延遲10分鐘發送,所以,當用戶進入這個Activity並退出後,在消息發送並處理完成以前,這個Activity是不會被系統回收(系統內存確實不夠使用的狀況例外)
如何解決呢?兩個方案:
————————————————————————————————————————
/**
* ================================================================
* User:xijiufu
* Email:xjfsml@163.com
* Version:1.0
* Time:2017/4/20--2:07
* Function:
* ModifyHistory:
* ================================================================
*/
public class HandlerActivity extends Activity {
/***
* 聲明一個靜態的Handler內部類,並持有外部類的弱引用
*/
private static class InnerHandler extends Handler {
private final WeakReference<HandlerActivity> mActivity;
public InnerHandler(HandlerActivity activity) {
this.mActivity = new WeakReference<HandlerActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
HandlerActivity activity = mActivity.get();
if (activity != null) {
//..
}
}
}
private final InnerHandler mHandler = new InnerHandler(this);
/**
* 靜態的匿名內部類不會持有外部類的引用
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {
/****/
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//延遲10分鐘發送消息
mHandler.postAtTime(sRunnable, 1000 * 60 * 10);
}
}複製代碼
————————————————————————————————————————
Context應該是每一個入門Android開發的程序員第一個接觸到的概念,它表明當前的上下文環境,能夠用來實現不少功能的調用,語句以下:
————————————————————————————————————————
//獲取資源管理器對象,進而能夠訪問到例如string,color等資源
Resources resources = context.getResources();
//啓動指定的Activity
context.startActivity(new Intent(this, MainActivity.class));
//獲取各類系統服務
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
//獲取系統文件目錄
File internalDir = context.getCacheDir();
File externalDir = context.getExternalCacheDir();
//更多。。。複製代碼
可見,正確理解Context的概念是很重要的,雖然應用開發中隨處可見Context的使用,但並非全部的Context實例都具有相同的功能,在使用上須要區別對待,不然極可能會引入問題。咱們首先來總結下Context的種類。
根據Context依託的組件以及用途不一樣,咱們能夠將Context分爲以下幾種。
錯誤地使用Context可能會致使內存泄漏,典型的例子是在實現單例模式時使用Context,以下代碼是可能會致使內存泄漏的單例實現。
————————————————————————————————————————
/**
* ================================================================
* User:xijiufu
* Email:xjfsml@163.com
* Version:1.0
* Time:2017/4/20--23:57
* Function:
* ModifyHistory:
* ================================================================
*/
public class SingleInstance {
private Context mContext;
private static SingleInstance sInstance;
private SingleInstance(Context context) {
mContext = context;
}
public static SingleInstance getInstance(Context context) {
if (sInstance == null) {
sInstance = new SingleInstance(context);
}
return sInstance;
}
}複製代碼
————————————————————————————————————————
若是使用者調用getInstance時傳入的Context是一個Activity或者Service的實例,那麼在應用退出以前,因爲單例一直存在,會致使對應的Activity或者Service被單例引用,從而不會被垃圾回收,Activity或者Service中關聯的其餘View或者數據結構對象也不會被釋放,從而致使內存泄漏。正確的作法是使用Application Context,由於它是應用惟一的,並且聲明週期是跟着應用一致的,正確的單例實現以下:
————————————————————————————————————————
/**
* ================================================================
* User:xijiufu
* Email:xjfsml@163.com
* Version:1.0
* Time:2017/4/20--23:57
* Function:
* ModifyHistory:
* ================================================================
*/
public class SingleInstance {
private Context mContext;
private static SingleInstance sInstance;
private SingleInstance(Context context) {
mContext = context;
}
public static SingleInstance getInstance(Context context) {
if (sInstance == null) {
sInstance = new SingleInstance(context.getApplicationContext());//這一句關鍵
}
return sInstance;
}
}複製代碼
不一樣組件中的Context能提供的功能不盡相同,總結起來,以下表:
功能 | Application | Activity | Service | BroadcastReceiver | ContentProvider |
顯示Dialog | NO | YES | NO | NO | NO |
啓動Activity | NO[1] | YES | NO[1] | NO[1] | NO[1] |
實現LayoutInflation | NO[2] | YES | NO[2] | NO[2] | NO[2] |
啓動Service | YES | YES | YES | YES | YES |
綁定Service | YES | YES | YES | YES | NO |
發送Broadcast | YES | YES | YES | YES | YES |
註冊Broadcast | YES | YES | YES | YES | NO[3] |
加載資源Resource | YES | YES | YES | YES | YES |
其中NO[1]標記表示對應的組件並非真的不能夠啓動Activity,而是建議不要這麼作,由於這些組件會在新的Task中建立Activity,而不是在原來的Task中。
NO[2]標記也是表示不建議這麼作,由於在非Activity中進行Layout Inflation,會使用系統默認的主題,而不是應用中設置的主題。
NO[3]標記表示在Android4.2及以上的系統上,若是註冊的BroadcastReceiver是null時是能夠的,用來獲取sticky廣播的當前值。
掌握java的四種引用類型對於寫出內存使用良好的應用是很關鍵的。
強引用:Java裏面最普遍使用的一種,也是對象默認的引用類型。若是一個對象具備強引用,那麼垃圾回收期是不會對它進行回收操做的,當內存空間不足時,Java虛擬機將會拋出OutOfMemoryError錯誤,這時應用將會終止運行。一句話總結,只要引用存在,垃圾回收器永遠不會回收。Object obj = new Object(); 能夠直接經過obj取得對應的對象,只有obj這個引用被釋放以後,對象纔會被釋放掉。
Object object = new Object();
SoftReference<Object> sf = new SoftReference<Object>(object);複製代碼
Object object = new Object();
WeakReference<Object> reference = new WeakReference<Object>(object);複製代碼
————————————————————————————————————————
對象的建立須要內存分配,對象的銷燬須要垃圾回收,這些都會必定程度上影響到應用的性能。所以通常來水,最好是重用對象而不是在每次須要的時候去建立一個功能相同的新對象,特別是注意不要在循環中重複建立相同的對象。
————————————————————————————————————————
對於基本數據類型和String類型的常量,建議使用static final 修飾,由於final類型的常量會在會進入靜態dex文件的域初始化部分,這是對基本數據類型和String類型常量的調用不會涉及類的初始化,而是直接調用字面量。
————————————————————————————————————————
在面向對象編程中,Getters/Setters的做用主要是對外屏蔽具體的變量定義,從而達到更好的封裝性。但若是是在類內部還使用Getters/Setters函數訪問變量的話,會下降訪問的速度。根據Android官方文檔,在沒有JIT(Just In Time)編譯器時,直接訪問變量的速度是調用Getter方法的3倍;在JIT編譯時,直接訪問變量的速度是調用Getters方法的7倍。固然,若是你的應用中使用了ProGuard(混淆代碼)的話,那麼ProGuard會對Getters/Setters進行內聯操做,從而達到直接訪問的效果。
代碼的重構是一項長期的鍥而不捨的工做,須要依靠團隊中每個成員來維護代碼庫的高質量,要會去享受高質量代碼帶來的快感,如何有效的進行代碼重構,除了須要對你所在項目有較深刻的理解以外,你還須要必定的方法指導。重構代碼可使用不一樣的設計模式來達到高質量的代碼,這兒能夠關注個人設計模式系列博客:Android設計模式之——單例模式(一) Android設計模式之——Builder模式(二)