Android开发Android开发经验谈

Android高级进阶之12条代码优化以及性能优化方案

2019-08-25  本文已影响0人  06fd4cf1f427

今天来记录一下在开发过程中遇到的代码优化和性能优化经验,方便让其他人少走弯路。

性能优化

1、装箱带来的内存消耗

Boolean isShow =new Boolean(true) ;  

上面的代码会带来如下问题:

上面的意思总结一下就是,采用装箱在java 5及以上是没必要的,采用装箱的方式构造一个对象会占用更多的内存,而使用比如说Boolean.TRUE的方式只是一个常量所以采用下面的方式更节约内存,正确的方式如下:

//boolean
Boolean isShow =Boolean.TRUE ;

//integer
Integer i= 2;

2、sharedPreferences使用commit带来的线程阻塞

                final SharedPreferences.Editor edit = settings.edit();
                edit.putBoolean(TIPS, true);
                edit.commit();

上面的代码会带来如下问题:

上面的代码如果在ui线程执行会带来ui线程的阻塞,可能会造成掉帧,原因是commit是在当前线程中执行写内存操作的并且commit执行完后会返回一个bool值来表示是否写成功,而apply会在异步线程里面写操作,执行完后不会返回是否写成功的值,其实大部分情况下并不需要知道sharedPreferences是否写成功所以可以用apply来替代commit。

3、selector中item位置错误

错误

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/normal" />
    <item android:drawable="@drawable/pressed" android:state_pressed="true"/>

</selector>

上面的selector会导致如下问题

selector中press的item属性永远不会被触发,为什么呢?因为在selector中从前往后匹配属性,第一个item和任何属性都会匹配,所以就算是执行了press的也是先匹配到上面的第一个item。 正确:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/pressed" android:state_pressed="true"/>
    <item android:drawable="@drawable/normal" />

</selector>

把没有任何条件选择的item放到最后,当前面的item都不匹配时就会选择最后的item。

4、context引起的内存泄漏

public static WifiManager getWIFIManager(Context ctx) {
    WifiManager wm = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
}

上面的代码直接使用context来获取系统的WiFi服务,会导致下面问题

获取WiFi_SERVICE必需要用application 的context不能使用activity的context,如果上面方法中传入的是activity的context就会造成内存泄漏。 正确

public static WifiManager getWIFIManager(Context ctx) {
    WifiManager wm = (WifiManager) ctx.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
    }

这里为什么不用activity的context而要用application的context呢?因为activity的context生命周期和activity一致,当activity释放时context也应该被释放,这里由于context被wifiManager持有会导致activity不能被释放,而出现内存泄漏。application的context生命周期和application生命周期一致。所以当获取与当前activity生命周期无关而与整个应用生命周期有关的资源时,要使用application的context。

public Context getApplicationContext ()
/*Return the context of the single, global Application object of the current process. This generally should only be used if you need a Context whose lifecycle is separate from the current context, that is tied to the lifetime of the process rather than the current component.
*/

5、使用SparseArray代替HashMap

在Android中如果要存放的<key,value>中的key是基本数据类型:int,long,等基本数据类型时可以用SparseArray来代替HashMap,可以避免自动装箱和HashMap中的entry等带来的内存消耗。能被SparseArray代替的<key,value>类型有如下几种:

SparseArray          <Integer, Object>
SparseBooleanArray   <Integer, Boolean>
SparseIntArray       <Integer, Integer>
SparseLongArray      <Integer, Long>
LongSparseArray      <Long, Object>
LongSparseLongArray  <Long, Long>   //this is not a public class  

对比存放1000个元素的SparseIntArray和HashMap<Integer, Integer> 如下:

SparseIntArray

class SparseIntArray {
    int[] keys;
    int[] values;
    int size;
}

Class = 12 + 3 * 4 = 24 bytes Array = 20 + 1000 * 4 = 4024 bytes Total = 8,072 bytes

HashMap

class HashMap<K, V> {
    Entry<K, V>[] table;
    Entry<K, V> forNull;
    int size;
    int modCount;
    int threshold;
    Set<K> keys
    Set<Entry<K, V>> entries;
    Collection<V> values;
}

Class = 12 + 8 * 4 = 48 bytes Entry = 32 + 16 + 16 = 64 bytes Array = 20 + 1000 * 64 = 64024 bytes Total = 64,136 bytes 可以看到存放相同的元素,HashMap占用的内存几乎是SparseIntArray的8倍。

SparseIntArray的缺点

SparseIntArray采用的二分查找法来查找个keys,因此查找某个元素的速度没有Hashmap的速度快。存储的元素越多时速度比hashmap的越慢,因此当当数据量不大时可以采用SparseIntArray,但是当数据量特别大时采用HashMap会有更高的查找速度。

6、自定义view中在layout、draw、onMeasue中new对象

问题代码

pubic class myview extends View
{

    public myview(Context context) {
        super(context);
    }
    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        int x=80;
        int y=80;
        int radius=40;
        Paint paint=new Paint();
        // Use Color.parseColor to define HTML colors
        paint.setColor(Color.parseColor("#CD5C5C"));
        canvas.drawCircle(x,x, radius, paint);
    }

}

自定义view的时候经常会重写onLayout ,onDraw ,onMeasue方法,但是要注意的是,如果在这些方法里面new 对象就会有如下问题

优化代码

public class myview extends View
{
        int x;
        int y;
        int radius;
        Paint paint;
    public myview(Context context) {
        super(context);
        init();
    }
    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        // Use Color.parseColor to define HTML colors
        paint.setColor(Color.parseColor("#CD5C5C"));
        canvas.drawCircle(x,x, radius, paint);
    }
    private void init()
    {
        x=80;
        y=80;
        radius=40;
        paint=new Paint();
    }   
}

看下Android官网上的解释为什么不能在onLayout ,onDraw ,onMeasue方法里面执行new 对象和执行初始化操作:

Creating objects ahead of time is an important optimization. Views are redrawn very frequently, and many drawing objects require expensive initialization. Creating drawing objects within your onDraw() method significantly reduces performance and can make your UI appear sluggish.

上面的意思总结一下就是在自定义view的时候最好提前初始化和new 对象,因为onDraw,onMeasure,onLayout的方法调用十分频繁,如果在这里初始化和new 对象会导致频繁的gc操作严重影响性能,也可能会导致掉帧现象。

ondraw的调用时机 1、view初始化的时候。 2、当view的invalidate() 方法被调用。什么时候会调用invalidate()方法呢?当view的状态需要改变的时候,比如botton被点击,editText相应输入等,就会reDraw调用onDraw方法。

7、静态变量引起的内存泄漏

问题代码

public class MyDlg extends Dialog {

    private View mDialog;
    private static MyDlg sInstance;
    public MyDlg(Context context) {
        super(context);
        sInstance = this;
        init();
    }
    private void init() {
        mDialog = LayoutInflater.from(mCtx).inflate(R.layout.dialog_app_praise, null);
        setContentView(mDialog);
  }       

上面的代码会导致如下问题:

上面代码中静态变量sInstance持有来context而这里的context是持有当前dialog的activity,由于静态变量一般只有在App销毁的时候才会进行销毁(此时类经历了,加载、连接、初始化、使用、和卸载)所以当activity执行完时由于被dialog中的静态变量持有无法被gc,所以造成内存泄漏。而这种dialog如果被多个地方调用就会造成严重的内存泄漏。

8、overdraw问题

问题代码

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:background="@drawable/layout_content_bkg" >

    <include layout="@layout/title_bar" />

    <ScrollView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="12dip"
        android:scrollbars="vertical" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:background="@drawable/layout_content_bkg"
            android:layout_marginTop="14dip"
            android:paddingLeft="13dip"
            android:paddingRight="13dip" >
            .......
        </LinearLayout>
    </ScrollView>
</LinearLayout>

上面代码中linearlayout的background和ScrollView 里面的background一样只需要保留父布局LinearLayout里面的background就行了,不然会多进行一次绘制也就是引起overdraw问题。

9、inefficent layout weight

问题代码

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_gravity="bottom"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        >

    <TextView
        android:layout_weight="1"
        android:id="@+id/text"
        android:layout_width="165dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="15dp"
        android:textSize="15sp" />

    <android.support.v7.widget.SwitchCompat
        android:checked="true"
        android:id="@+id/0checkbox"
        android:layout_width="wrap_content"
        android:layout_height="36dp"
        android:layout_marginEnd="15dp" />
    </LinearLayout>

上面布局中一个linearLayout里面包含一个textview和一个SwitchCompat,textview中的layout_weight="1",此时也明确给出了textview的layout_width="165dp",这个时候会带来下面的问题

当linearLayout的布局里面只有一个子view使用weight属性时如果LinearLayout是垂直布局这个子view应该设置layout_height="0dp",如果是水平布局这个子view应该layout_width="0dp",这样执行onMeasure的时候会首先不去measure 这个布局,可以提高性能。

10、字符串操作优化

String text="bitch";
//1
StringBuilder.append("  fuck " + text);
//2
mStringBuilder.append("  fuck ").append(text);

上面代码中1和2哪个代码性能更好?第二种性能更高,第一种方式会先new一个“fuck”+text的字符串然后在append到StringBuilder中,而第二种方法中不用new一个字符串,所以性能更高。

代码优化

1、消除redundant “Collection.addAll()”

原始代码:

        ArrayList<BaseFile> lists = null;
        if (getImages() != null) {
            lists = new ArrayList<>();
            lists .addAll(getImages());
        }

优化代码:

        ArrayList<BaseFile> lists = null;
        if (getImages() != null) {
            lists = new ArrayList<>(getImages());
        }

2、使用array的copy方法

原始代码:

        for(int i=0;i<ITEM_SIZE;i++){
            mContentsArray[i] = contents[i];
        }

优化代码:

System.arraycopy(contents, 0, mContentsArray, 0, ITEM_SIZE);

文末

好啦,如此文章到这里就结束了,如果你觉得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!

转发+点赞+关注,第一时间获取最新知识点

Android架构师之路很漫长,一起共勉吧!

以下墙裂推荐阅读!!!

最后祝大家生活愉快~

上一篇下一篇

猜你喜欢

热点阅读