Android高性能编码实践

最近在项目开发的过程中,发现之前写的代码问题比较多,而像我这么有代码洁癖的是绝对不能忍受的,于是。。。几乎重写了整个页面,感叹之余,总结下开发中需要注意的性能问题,另外可以多关注下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

    ​ 官方推荐使用,节约内存开销,不过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…

你可能感兴趣的:(Android)