最近在项目开发的过程中,发现之前写的代码问题比较多,而像我这么有代码洁癖的是绝对不能忍受的,于是。。。几乎重写了整个页面,感叹之余,总结下开发中需要注意的性能问题,另外可以多关注下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,里面包含一个输入框和一个搜索按钮。
<?xml version="1.0" encoding="utf-8"?> <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,注意宽高一定要设置,不然会报错
<?xml version="1.0" encoding="utf-8"?> <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<K, V>和SparseArray代替HashMap<K, V>
官方推荐使用,节约内存开销,不过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…