android优化篇

布局优化

大家肯定都知道Android中有许多布局,比如Linerlayout、RelativeLayout等,布局优化就是减少布局文件层级,层级减少了,那么程序绘制时就快了许多,所以可以提高性能。

在布局代码中,使用什么布局基本遵守以下规则:

  • 如果布局中既可以使用LinearLayout也可以使用RelativeLayout,那么就采用LinearLayout,这是因为RelativeLayout的功能比较复杂,它的布局过程需要花费更多的CPU时间。
  • 如果布局需要通过嵌套的方式来完成。这种情况下还是建议采用RelativeLayout,因为ViewGroup的嵌套就相当于增加了布局的层级,同样会降低程序的性能。

3.使用<include>或<merge>标签和ViewStub,提取布局中公共部分的布局,可提高布局初始化效率。

内存泄漏优化

内存泄漏优化换句话说,就是什么情况可能会导致内存泄漏:

  • 不要再Acticity中声明静态变量,这样会是的Activity无法完全销毁释放
  • 单例设计模式一起的内存泄漏,单例设计模式的静态特性会使他的生命周期和应用程序的生命周期一样长,这就说明了如果一个对象不在使用了,而这时单例对象还在持有该对象的引用,这时GC就会无法回收该对象,造成了内存泄露的情况。所以使用单例模式时,传入的context应该使用ApplicationContext
  • 非静态内部类创建的静态实例造成的内存泄漏
  • Handler造成的内存泄漏,不要在Activity中用非静态匿名内部类的方式去引用hanlder,比如:
public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

这样hanlder会持有Activity的引用,handler是运行在一个Looper线程中的,而Looper线程是轮询来处理消息队列中的消息的,假设我们处理的消息有10条,而当他执行到第6条的时候,用户退出销毁了当前的Activity,这个时候消息还没有处理完,handler还在持有Activity的引用,这个时候就会导致无法被GC回收,造成了内存泄漏。

RecyclerView优化

RecyclerView缓存机制

众所周知,RecyclerView拥有四级缓存,它们分别是:

  • Scrap缓存:包括mAttachedScrap和mChangedScrap,又称屏内缓存,不参与滑动时的回收复用,只是用作临时保存的变量。
    • mAttachedScrap:只保存重新布局时从RecyclerView分离的item的无效、未移除、未更新的holder。
    • mChangedScrap:只会负责保存重新布局时发生变化的item的无效、未移除的holder。
  • CacheView缓存:mCachedViews又称离屏缓存,用于保存最新被移除(remove)的ViewHolder,已经和RecyclerView分离的视图,这一级的缓存是有容量限制的,默认最大数量为2。
  • ViewCacheExtension:mViewCacheExtension又称拓展缓存,为开发者预留的缓存池,开发者可以自己拓展回收池,一般不会用到。
  • RecycledViewPool:终极的回收缓存池,真正存放着被标识废弃(其他池都不愿意回收)的ViewHolder的缓存池。这里的ViewHolder是已经被抹除数据的,没有任何绑定的痕迹,需要重新绑定数据。

RecyclerView的回收原理

(1)如果是RecyclerView不滚动情况下缓存(比如删除item)、重新布局时。

  • 把屏幕上的ViewHolder与屏幕分离下来,存放到Scrap中,即发生改变的ViewHolder缓存到mChangedScrap中,不发生改变的ViewHolder存放到mAttachedScrap中。
  • 剩下ViewHolder会按照mCachedViews > RecycledViewPool的优先级缓存到mCachedViews或者RecycledViewPool中。

(2)如果是RecyclerView滚动情况下缓存(比如滑动列表),在滑动时填充布局。

  • 先移除滑出屏幕的item,第一级缓存mCachedViews优先缓存这些ViewHolder。
  • 由于mCachedViews最大容量为2,当mCachedViews满了以后,会利用先进先出原则,把旧的ViewHolder存放到RecycledViewPool中后移除掉,腾出空间,再将新的ViewHolder添加到mCachedViews中。
  • 最后剩下的ViewHolder都会缓存到终极回收池RecycledViewPool中,它是根据itemType来缓存不同类型的ArrayList,最大容量为5。

RecyclerView的复用原理

当RecyclerView要拿一个复用的ViewHolder时:

  • 如果是预加载,则会先去mChangedScrap中精准查找(分别根据position和id)对应的ViewHolder。
  • 如果没有就再去mAttachedScrap和mCachedViews中精确查找(先position后id)是不是原来的ViewHolder。
  • 如果还没有,则最终去mRecyclerPool找,如果itemType类型匹配对应的ViewHolder,那么返回实例,让它重新绑定数据
  • 如果mRecyclerPool也没有返回ViewHolder才会调用createViewHolder()重新去创建一个。

这里有几点需要注意:

  • 在mChangedScrap、mAttachedScrap、mCachedViews中拿到的ViewHolder都是精准匹配。
  • mAttachedScrap和mCachedViews没有发生变化,是直接使用的。
  • mChangedScrap由于发生了变化,mRecyclerPool由于数据已被抹去,所以都需要调用onBindViewHolder()重新绑定数据才能使用。

缓存机制总结

  • RecyclerView最多可以缓存 N(屏幕最多可显示的item数【Scrap缓存】) + 2 (屏幕外的缓存【CacheView缓存】) + 5*M (M代表M个ViewType,缓存池的缓存【RecycledViewPool】)。
  • RecyclerView实际只有两层缓存可供使用和优化。因为Scrap缓存池不参与滚动的回收复用,所以CacheView缓存池被称为一级缓存,又因为ViewCacheExtension缓存池是给开发者定义的缓存池,一般不用到,所以RecycledViewPool缓存池被称为二级缓存。

如果想深入了解RecyclerView缓存机制的同学,可以参考《RecyclerView的回收复用缓存机制详解》 这篇文章。

性能优化方案

根据上面我们对缓存机制的了解,我们可以简单得到以下几个大方向:

  • 1.提高ViewHolder的复用,减少ViewHolder的创建和数据绑定工作。【最重要】
  • 2.优化onBindViewHolder方法,减少ViewHolder绑定的时间。由于ViewHolder可能会进行多次绑定,所以在onBindViewHolder()尽量只做简单的工作。
  • 3.优化onCreateViewHolder方法,减少ViewHolder创建的时间。

提高ViewHolder的复用

1.多使用Scrap进行局部更新。

  • (1) 使用notifyItemChangenotifyItemInsertednotifyItemMovednotifyItemRemoved等方法替代notifyDataSetChanged方法。
  • (2) 使用notifyItemChanged(int position, @Nullable Object payload)方法,传入需要刷新的内容进行局部增量刷新。这个方法一般很少有人知道,具体做法如下:
    • 首先在notify的时候,在payload中传入需要刷新的数据,一般使用Bundle作为数据的载体。
    • 然后重写RecyclerView.AdapteronBindViewHolder(@NonNull RecyclerViewHolder holder, int position, @NonNull List<Object> payloads)方法
       java 

      复制代码

      @Override public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position, @NonNull List<Object> payloads) { if (CollectionUtils.isEmpty(payloads)) { Logger.e("正在进行全量刷新:" + position); onBindViewHolder(holder, position); return; } // payloads为非空的情况,进行局部刷新 //取出我们在getChangePayload()方法返回的bundle Bundle payload = WidgetUtils.getChangePayload(payloads); if (payload == null) { return; } Logger.e("正在进行增量刷新:" + position); for (String key : payload.keySet()) { if (KEY_SELECT_STATUS.equals(key)) { holder.checked(R.id.scb_select, payload.getBoolean(key)); } } }

详细使用方法可参考XUI中的RecyclerView局部增量刷新 中的代码。

  • (3) 使用DiffUtilSortedList进行局部增量刷新,提高刷新效率。和上面讲的传入payload原理一样,这两个是Android默认提供给我们使用的两个封装类。这里我以DiffUtil举例说明该如何使用。
    • 首先需要实现DiffUtil.Callback的5个抽象方法,具体可参考DiffUtilCallback.java
    • 然后调用DiffUtil.calculateDiff方法返回比较的结果DiffUtil.DiffResult
    • 最后调用DiffUtil.DiffResultdispatchUpdatesTo方法,传入RecyclerView.Adapter进行数据刷新。

详细使用方法可参考XUI中的DiffUtil局部刷新XUI中的SortedList自动数据排序刷新 中的代码。

2.合理设置RecyclerViewPool的大小。如果一屏的item较多,那么RecyclerViewPool的大小就不能再使用默认的5,可适度增大Pool池的大小。如果存在RecyclerView中嵌套RecyclerView的情况,可以考虑复用RecyclerViewPool缓存池,减少开销。

3.为RecyclerView设置setHasStableIds为true,并同时重写RecyclerView.Adapter的getItemId方法来给每个Item一个唯一的ID,提高缓存的复用率。

4.视情况使用setItemViewCacheSize(size)来加大CacheView缓存数目,用空间换取时间提高流畅度。对于可能来回滑动的RecyclerView,把CacheViews的缓存数量设置大一些,可以省去ViewHolder绑定的时间,加快布局显示。

5.当两个数据源大部分相似时,使用swapAdapter代替setAdapter。这是因为setAdapter会直接清空RecyclerView上的所有缓存,但是swapAdapter会将RecyclerView上的ViewHolder保存到pool中,这样当数据源相似时,就可以提高缓存的复用率。

优化onBindViewHolder方法

1.在onBindViewHolder方法中,去除冗余的setOnItemClick等事件。因为直接在onBindViewHolder方法中创建匿名内部类的方式来实现setOnItemClick,会导致在RecyclerView快速滑动时创建很多对象。应当把事件的绑定在ViewHolder创建的时候和对应的rootView进行绑定。

2.数据处理与视图绑定分离,去除onBindViewHolder方法里面的耗时操作,只做纯粹的数据绑定操作。当程序走到onBindViewHolder方法时,数据应当是准备完备的,禁止在onBindViewHolder方法里面进行数据获取的操作。

3.有大量图片时,滚动时停止加载图片,停止后再去加载图片。

4.对于固定尺寸的item,可以使用setHasFixedSize避免requestLayout

优化onCreateViewHolder方法

1.降低item的布局层级,可以减少界面创建的渲染时间。

2.Prefetch预取。如果你使用的是嵌套的RecyclerView,或者你自己写LayoutManager,则需要自己实现Prefetch,重写collectAdjacentPrefetchPositions方法。

响应速度优化

响应速度优化的核心思想是避免在主线程中做耗时操作,Android规定,Activity如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件就会出现ANR,而BroadcastReceiver如果10秒,Service时20s当然这是小概率事件,如果在相应时间内未得到反映就会出现ANR。当有耗时操作时,可以单独开启一个线程去操作。

listview优化

listview优化相信大家也都比较熟悉了,也是比较经典的面试题,在这里就不详细赘述了,主要有复用view,首先判断view是否为空,如果不为空直接引用,为空再创建使用ViewHolder类,settag的方式保存布局的控件初始化信息,避免每次都去findviewbyid影响效率

Bitmap优化

其实思想也很简单,那就是采用BitmapFactory.Options来加载所需尺寸的图片。这里假设通过ImageView来显示图片,很多时候ImageView并没有图片的原始尺寸那么大,这个时候把整个图片加载进来后再设给imageView,这显然是没必要的,因为ImageView并没有办法显示原始的图片。通过BitmapFactory.Options就可以按一定的采样率来加载缩小后的图片,将缩小后的图片在ImageView中显示,这样就会降低内存占用从而在一定程度上避免OOM,提高了Bitmap加载时的性能。

线程优化

线程优化的思想是采用线程池,避免程序中存在大量的Thread。线程池可以重用内部的线程,从而避免了线程的创建和销毁所带来的性能开销,同时线程池还能有效地控制线程池的最大并发数,避免大量的线程因互相抢占系统资源从而导致阻塞现象的发生。因此在实际开
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值