編寫良好的程式碼有助於我們開發健壯的APP,程式碼優化必不可少。
1、避免使用靜態的變數尤其是靜態的Context、View、Drawable等消耗記憶體的物件,如果一定要使用可以使用弱引用,即WeakReference這個類,如下:
private static WeakReference<Context> context;
@Override
public void onCreate() {
super.onCreate();
context = new WeakReference<Context>(this);
}
public static Context getContext() {
return context.get();
}
複製程式碼
2、避免非靜態內部類引用外部類,因為靜態內部類會引用外部類的物件或View物件,造成記憶體洩露,最典型的是handler使用,如下:
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(Act.this, "I am handler", Toast.LENGTH_SHORT).show();
}
};
複製程式碼
或
private TextView textView;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
textView.setText("text from handler");
}
};
複製程式碼
替代為:
private Handler handler = new MyHandler(this);
private static class MyHandler extends Handler {
private WeakReference<Act> activity;
public MyHandler(Act activity) {
this.activity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Toast.makeText(activity.get(), "show toast from handler", Toast.LENGTH_SHORT).show();
}
}
複製程式碼
3、避免使用列舉,在Android中官方已經不推薦使用列舉,可以使用android.support包提供的annotation編譯期註解配合常量來替代,可以參考系統的Toast類原始碼如下:
public static final int LENGTH_SHORT = 0;
public static final int LENGTH_LONG = 1;
@IntDef({LENGTH_SHORT, LENGTH_LONG})
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {
}
複製程式碼
4、在大部分情況下,使用增強的foreach替代for迴圈,如下:
int[] num = new int[]{1, 3, 5, 6, 9};
int sum = 0;
for (int j = 0; j < num.length; j++) {
sum += num[j];
}
複製程式碼
可以替代為:
int[] num = new int[]{1, 3, 5, 6, 9};
int sum = 0;
for (int j : num) {
sum += j;
}
複製程式碼
5、避免在迴圈語句內部反覆建立和銷燬物件,避免記憶體抖動,影響效能,如下:
int[] numArray1 = new int[]{1, 3, 5, 6, 9};
int[] numArray2 = new int[]{1, 3, 5, 6, 9};
int sum = 0;
for (int j = 0; j < numArray1.length; j++) {
int num1 = numArray1[j];
int num2 = numArray2[j];
sum += num1 + num2;
}
複製程式碼
可以替代為:
int[] numArray1 = new int[]{1, 3, 5, 6, 9};
int[] numArray2 = new int[]{1, 3, 5, 6, 9};
int sum = 0;
int num1;
int num2;
for (int j = 0; j < numArray1.length; j++) {
num1 = numArray1[j];
num2 = numArray2[j];
sum += num1 + num2;
}
複製程式碼
6、使用更加高效的資料結構,在Java中如Map、HashMap等,在Android中有專門的設計類都是以Sparsexx開頭,如下:
Map<Integer, String> map1 = new HashMap<>();
Map<Integer, File> map2 = new HashMap<>();
複製程式碼
可以替代為:
SparseArray<String> s1 = new SparseArray<>();
SparseArray<File> s2 = new SparseArray<>();
複製程式碼
SparseArray內部定義了一個int型別的陣列用於儲存key,所以如果map的key是int型別,就可以使用SparseArray替代,下面是類定義:
當然除了這個類還有其他的類,如:SparseBooleanArray、SparseIntArray、SparseLongArray,這三個類內部都定義了int型別的陣列來儲存key。7、字串大量拼接時,使用StringBuilder替代+,且StringBuilder效率比StringBuffer高,如下:
String str = "我";
List<String> names = new ArrayList<>();
names.add("小明");
names.add("小王");
names.add("小劉");
names.add("小趙");
for (String s : names) {
str += s;
}
複製程式碼
可以替代為:
StringBuilder str = new StringBuilder("我");
List<String> names = new ArrayList<>();
names.add("小明");
names.add("小王");
names.add("小劉");
names.add("小趙");
for (String s : names) {
str.append(s);
}
複製程式碼
注意:不要在使用了StringBuilder中又同時使用字串+,如這樣:str.append(s+"\n"),可以這樣:str.append(s).append("\n")。
8、避免在自定義View的onDraw方法中重複申請和釋放記憶體,如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(28);
}
複製程式碼
改為在onDraw方法之前申明:
Paint paint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(Color.RED);
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(28);
}
複製程式碼
9、避免使用系統已經過時的方法,如果要使用要對系統API版本進行判斷處理,如下:
getResources().getColor(R.color.color_0d0d0d);
複製程式碼
getColor帶一個引數的這個方法已經標註為過時,替代為:
if (Build.VERSION.SDK_INT >= 23) {
getResources().getColor(R.color.color_0d0d0d, getTheme());
} else {
getResources().getColor(R.color.color_0d0d0d);
}
複製程式碼
推薦寫法,使用ContextCompat這個向後相容的幫助類:
ContextCompat.getColor(this,R.color.color_0d0d0d);
複製程式碼
這樣就不用我們自己做api版本判斷了,其實它內部的邏輯跟第一種寫法是一樣的,下面是它的原始碼:
public static final int getColor(Context context, @ColorRes int id) {
final int version = Build.VERSION.SDK_INT;
if (version >= 23) {
return ContextCompatApi23.getColor(context, id);
} else {
return context.getResources().getColor(id);
}
}
複製程式碼
ContextCompatApi23.getColor方法內部又呼叫了context的getColor方法:
public static int getColor(Context context, int id) {
return context.getColor(id);
}
複製程式碼
最終還是呼叫Resources的getColor(@ColorRes int id, @Nullable Theme theme)方法:
public final int getColor(@ColorRes int id) {
return getResources().getColor(id, getTheme());
}
複製程式碼
10、使用泛型的時候如果明確知道型別最好限制泛型型別,如下:
public <VIEW> VIEW findViewById(int resId) {
return (VIEW) contentView.findViewById(resId);
}
複製程式碼
替代為:
public <VIEW extends View> VIEW findViewById(int resId) {
return (VIEW) contentView.findViewById(resId);
}
複製程式碼
11、使用android.support.annotation包改進程式碼,增強程式碼健壯性,如下:
import android.support.annotation.IdRes;
public View findViewById(@IdRes int resId) {
return contentView.findViewById(resId);
}
複製程式碼
@IdRes是一個編譯期註解,程式碼會在編譯階段進行檢查,這樣做的好處是外部要呼叫findViewById方法時不可以隨便傳入數值,必須是R.id.xx中的某一個,當然傳入0和-1也是沒問題,一般會用來做預設判斷處理,如:
public View findViewById(@IdRes int resId) {
if (resId <= 0) {
throw new IllegalArgumentException("傳入的引數不對,只能是資源ID.");
}
return contentView.findViewById(resId);
}
複製程式碼
資源型註解:
- @LayoutRes:限制型別為R.layout中的資源
- @StringRes:限制型別為R.string中的資源
- @ColorRes:限制型別為R.color中的資源
- @StyleRes:限制型別為R.style中的資源
- @DrawableRes:限制型別為R.drawable或R.mipmap中的資源
- @MenuRes:限制型別為R.menu中的資源
- @DimenRes:限制型別為R.dimen中的資源
- @AnimRes:限制型別為R.anim中的資源
數值型註解:
- @IntDef:定義一組取值常量,限制取值範圍,可以用來替代列舉
- @IntRange:限制int取值範圍,用法:
void add(@IntRange(from = 0, to = 20) int num) {
}
複製程式碼
add方法只能傳入0-20之間的整數。
12、使用ListView時Adapter一定要複用convertView並使用ViewHolder,如下:
public class MyAdapter extends BaseAdapter {
private List<String> data;
private final LayoutInflater layoutInflater;
private class MyViewHolder {
public TextView textView;
public MyViewHolder(View itemView) {
textView = (TextView) itemView.findViewById(R.id.text);
}
}
public MyAdapter(Context context, List<String> data) {
layoutInflater = LayoutInflater.from(context);
this.data = data;
}
@Override
public int getCount() {
return data == null ? 0 : data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MyViewHolder viewHolder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.item_text, parent, false);
viewHolder = new MyViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (MyViewHolder) convertView.getTag();
}
viewHolder.textView.setText(getItem(position).toString());
return convertView;
}
}
複製程式碼
13、使用RecyclerView替代ListView,Android 5.0推出了RecyclerView,RecyclerView更加強大、靈活、可擴充套件性強,簡單用法如下:
private class MyViewHolder extends RecyclerView.ViewHolder {
public final TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text);
}
}
private class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
private final LayoutInflater layoutInflater;
private List<String> data;
public MyAdapter(Context context, List<String> data) {
layoutInflater = LayoutInflater.from(context);
this.data = data;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(layoutInflater.inflate(R.layout.item_text, parent, false));
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText(getItem(position));
}
public String getItem(int position) {
return data.get(position);
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
}
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
List<String> languages = new ArrayList<>();
languages.add("C");
languages.add("C++");
languages.add("Java");
languages.add("Kotlin");
languages.add("Python");
MyAdapter adapter = new MyAdapter(this, languages);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
複製程式碼
注意:RecyclerView一定要呼叫setLayoutManager來指定item佈局排列,否則無法展示列表資料。具體詳細用法,請移步。
14、如果成員變數的值是固定不變的,改用final static修飾,因為常量會經過編譯器優化,如下:
private String TAG = "MyAdapter";
private int pageSize = 15;
複製程式碼
改為:
private final static String TAG = "MyAdapter";
private final static int pageSize = 15;
複製程式碼
15、使用Timer或TimerTask時要記得取消,在Activity的onDestroy或Fragment的onDetach方法中呼叫cancel方法取消。
16、使用TypedArray完畢後及時呼叫typedArray.recycle()方法釋放資源。
17、檔案或流操作時要在finally語句塊中關閉而不要在catch語句塊中關閉,因為如果發生異常程式就不會往下執行,如下:
FileInputStream fis;
try {
fis = new FileInputStream(Environment.getExternalStorageDirectory() + "aa.txt");
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
複製程式碼
改為:
FileInputStream fis = null;
try {
fis = new FileInputStream(Environment.getExternalStorageDirectory() + "aa.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製程式碼
18、ArrayList清空資料使用clear()方法,不要用list.removeAll(list),我見過有人這樣寫!!!會引起記憶體溢位!
19、集合新增所有資料到另外一個集合中,用addAll而不要用for迴圈,如下:
ArrayList<String> list = new ArrayList<>();
list.add("C");
list.add("C++");
list.add("Java");
list.add("Kotlin");
ArrayList<String> list2 = new ArrayList<>();
for (String item : list) {
list2.add(item);
}
複製程式碼
這樣的程式碼寫過吧!不用的,上面這種方式資料少還好,多的話就呵呵了,很簡單也高效:
ArrayList<String> list = new ArrayList<>();
list.add("C");
list.add("C++");
list.add("Java");
list.add("Kotlin");
ArrayList<String> list2 = new ArrayList<>();
list2.addAll(list);
複製程式碼
addAll方法是Collection介面提供的,具體實現是在ArrayList中:
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
複製程式碼
陣列也可以,用Arrays這個工具類將陣列轉成List:
String[] arrays = new String[]{"C", "C++", "Java", "Kotlin"};
ArrayList<String> list2 = new ArrayList<>();
list2.addAll(Arrays.asList(arrays));
複製程式碼
addAll方法核心用到的是陣列拷貝。System.arraycopy是native方法,具體由C實現。