最近在项目开发的过程中,发现之前写的代码问题比较多,而像我这么有代码洁癖的是绝对不能忍受的,于是。。。几乎重写了整个页面,感叹之余,总结下开发中需要注意的性能问题,另外可以多关注下Google I/O大会上发布的Best Practice。结合项目中实际遇到的,让我们更直观感受下吧。
界面及数据复用
举个项目中刚刚改写的一个showPopupWindow方法,点击后弹出一个筛选框,原代码是这样的。
private void showPopupWindow() {
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
View popupWindowModelView = inflater.inflate(R.layout.xxx,null);
tf_model = (TagFlowLayout) popupWindowModelView.findViewById(R.id.xxx);
modelPopupWindow = new BackgroundDarkPopupWindow(popupWindowModelView, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
modelPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
modelPopupWindow.setOutsideTouchable(true);
modelPopupWindow.setFocusable(true);
modelPopupWindow.setTouchInterceptor(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
xxx
return false;
}
});
modelPopupWindow.setDarkColor(Color.parseColor("#a0000000"));//颜色
modelPopupWindow.darkBelow(v_model_pop);//下于
modelPopupWindow.darkFillScreen();
//初始化数据
initXXX();
modelPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
}
});
modelPopupWindow.showAsDropDown(xxx);
}
每次点击弹出这个popupWindow,这个popupWindow都是重新inflate进来,并初始化各种属性,设置各种监听,更耗性能的是数据每次都是重新从数据库里取,而这些数据基本可以认为是固定数据。因此,对于这段代码需要做两点优化:复用界面和复用数据。
从这个优化里衍生出另外一个相似的场景,就是常见的列表(ListView/RecyleView)数据,setAdapter应该是初始化只执行一次,设置数据源,每次更新数据源+notifyDataSetChange(RecyleView更是可以单条更新),而不是每次去setAdapter。
布局嵌套优化
布局多重嵌套在项目中比比皆是,其中不乏毫无用处的父Layout,除了这些可以直接去掉的嵌套,推荐使用support包里的ConstraintLayout约束布局,尽可能减少嵌套,而且可视化布局直接拖拉,编写效率更高。组合使用减少外层多余父Layout。
再举个经典的例子,设置drawableLeft代替
<LinearLayout>
<ImageView/>
<TextView/>
LinearLayout>
drawable大小需要控制时可以使用自定义View–>CustomDrawableTextView
SpannableStringBuilder使用优化多TextView组合
下面是一个显示数量+单位的需求,这样的代码在项目中太常见了:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="16sp"
android:text="19300"/>
<TextView
android:layout_marginStart="2dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0000"
android:textSize="11sp"
android:text=" Value"/>
LinearLayout>
这样的写法非常地不优雅,来,删掉tv_num TextView外的所有控件
<TextView
android:id="@+id/tv_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="16sp"
android:text="19300"/>
然后在Java代码里,用SpannableStringBuilder实现一样的功能
ForegroundColorSpan colorSpan=new ForegroundColorSpan(0xffff0000);
//这里单位是px,需要写下sp转px,或者直接用dp为单位 new AbsoluteSizeSpan(11,true);
AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(22);
SpannableStringBuilder ssb_volume = new SpannableStringBuilder(" Volume");
ssb_volume.setSpan(colorSpan,0,ssb_volume.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb_volume.setSpan(sizeSpan,0,ssb_volume.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tvNum.setText(new SpannableStringBuilder(num).append(ssb_volume));
ViewStub优化隐藏不常用布局
对于特定情况才显示View使用ViewStub提升页面加载效率,在布局加载时会忽略ViewStub,使用的时候inflate进来或者setVisibility(View.VISIBLE)。项目中的ToolBar的布局,右上角的button写了N个,多数页面右上角一个按钮都不需要,这时候就可以用ViewStub去优化布局。
新建一个lazy_add.xml,里面包含一个输入框和一个搜索按钮。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit_view"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="search"/>
LinearLayout>
在目标布局引入ViewStub,layout设置lazy_add,注意宽高一定要设置,不然会报错
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/lazy_add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
......
LinearLayout>
需要显示viewStub布局时,有两种方式:
viewStub.inflate();
或者
viewStub.setVisibility(View.VISIBLE);
区别在于只有inflate()方法可以返回ViewStub根布局,但是只能调用一次,再次调用就会报错。因为inflate()之后再去findViewById(R.id.view_stub)已经找不到viewStub,相当于已经被替换成lazy_add布局,此时this.findviewById能找到其内部view。
而setVisibility则可以多次调用,但不会返回ViewStub根布局。
使用lazyAddView里面的控件时,
if (findViewById(R.id.viewstub) != null){
ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);
try {
View lazyAddView = viewStub.inflate();
Button btnSearch = (Button) lazyAddView.findViewById(R.id.btn_search);
} catch (Exception e){//前面判断了findViewById不可null,所以没有inflate()过,实际不可能走这里,只是给另一种思路
viewStub.setVisibility(View.VISIBLE);
Button btnSearch = (Button) this.findViewById(R.id.btn_search);
}
}
Intent传输序列化数据时使用Parcelable
Android中的序列化有两种形式,实现Parcelable或者Serializable接口,Serializable几乎不需要增加额外的代码,而Parcelable需要实现读和写两个方法。为什么这么麻烦还要推荐用Parcelable呢?原因当然是Parcelable性能更好,并且内存开销更小。
在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上,读的时候再从硬盘读取。这就很好理解两者的使用场景了:如果数据是要固化到硬盘上保存,那只能使用Serializable,其它情况可以在内存中操作的就用Parcelable提升性能,使用最多的场景便是Intent传输序列化数据。
下面给出一个Parcelable序列化的模板,注意一点,读写变量的顺序要保持一致。
public class User implements Parcelable {
private Long userId;
private String userName;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(userId);
dest.writeString(userName);
}
public User(){}
public User(Parcel source){
userId = source.readLong();
userName = source.readString();
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
}
尽量使用基本类型,而不是包装类/对象类
例:int 比 Integer 效率更高,同样int[]数组也是一样,而两个数组Foo[]和Bar[]直接使用会比使用一个封装后的数组custom(Foo, Bar) 来得更高效。
字串符可预测的拼接,使用StringBuffer/StringBuilder 代替创建String对象
错误写法:
String a = "a";
String b = "b"
String c = a + b;
正确写法:
String c = new StringBuilder("a").append("b").toString();
StringBuilder效率最高,但是StringBuilder是线程不安全的,而StringBuffer是线程安全的。
使用ArrayMap
官方推荐使用,节约内存开销,不过SparseArray每次在插入的时候都要使用二分查找判断是否有相同的值,效率稍低于HashMap。
for循环时数据或者列表的长度不要写在for()里面,每循环一次都需要重新计算一次
错误写法:
for (int i=0;i<list.size();i++)
正确写法:
int size = list.size();
for (int i=0;i<size;i++)
暂时想到这些,后续开发中遇到再补充。
To be continue…