美文网首页
Android 性能优化指南

Android 性能优化指南

作者: 怡红快绿 | 来源:发表于2020-07-22 14:58 被阅读0次

一、布局优化

Android 系统对View的绘制是一层一层进行的,层数越多,绘制过程就会越复杂。所以布局优化的一个主要手段就是减少布局的嵌套层级。减少布局的层级可以从以下几个方面入手:

  • 删除布局中无用的层级和控件
  • 根据场景使用最合适的布局结构
  • 在不增加层级的前提下,优先使用LinearLayout而不是RelativeLayout,这是因为RelativeLayout 的绘制过程更复杂
  • 使用标签<include>、<merge>减少重复的布局,减少UI的层级;使用ViewStub 实现控件的按需加载

简单介绍一下<include>、<merge>和ViewStub的使用

1、<include>标签

使用场景:如果两个页面存在相同的重复布局,那这部分重复的布局就应该被提取出来作为一个单独的布局文件供其他布局文件使用。

例如通用的标题栏title_bar.xml:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/titleBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        style="@style/ToolbarStyle"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="@drawable/fanhui" />

    <TextView
        android:id="@+id/tvLine1"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_1"
        android:background="@color/gray"
        app:layout_constraintTop_toBottomOf="@id/toolbar" />
</LinearLayout>

在其他布局内使用include 标签直接引用该布局文件。

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity.MainActivity">
    <include
        android:id="@+id/titlebar_layout"
        layout="@layout/title_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>

如果布局文件和include都指定了id属性,则以include 指定的id为准。

使用include 标签提取重复的布局有两点好处:

  • 避免产生大量相同的布局代码
  • 如果需要更改标题栏样式,只需修改include 布局即可

2、<merge>标签

使用场景:如果被包含的布局文件的根布局是多余的,那么可以使用merge 标签替换根布局来达到减少层级的目的。举个例子:

重复布局 include.xml

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

布局文件parent.xml

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

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

</LinearLayout>

我们可以看到,被包含的布局和它的父布局都是使用竖直的LinearLayout,因此被包含的布局中的LinearLayout是多余的,这个时候就可以使用merge 标签代替LinearLayout。即:

优化后的include.xml

<merge xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</merge>

3、ViewStub 控件

和上面两个标签不同,ViewStub本身就是一个View,默认情况下它是不可见的invisible、宽高都为0的特殊View,所以它本身不会参与布局的绘制过程。

它的主要作用是根据需要加载特殊的布局内容,这样可以避免在界面初始化过程直接绘制所有的控件导致卡顿,提高程序的初始化效率。

例如网络异常时的提示布局,应该在网络请求异常的时候才初始化显示在界面上。

<ViewStub
    android:id="@+id/network_error"
    android:inflatedId="@+id/id_network_error"
    android:layout="@layout/layout_network_error"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
  • layout_network_error 指定显示的布局文件
  • id_network_error 布局文件的根元素id
  • network_error ViewStub自身的id

加载ViewStub 中的布局有两种:

第一种:((ViewStub)viewStub).setVisibility(View.VISIBLE);
第二种:((ViewStub)viewStub).inflate();

只要调用了这两个方法中的其中一个,ViewStub就会被它的内部布局替换掉,ViewStub也就不再是整个布局的一部分。

二、View绘制优化

正常情况下,Android系统每隔16ms会对UI进行一次渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps。

为了保证在16ms内完成绘制任务,自定义View时,尽量避免在onDraw() 方法内执行耗时操作。

  • 不要在onDraw 方法内创建局部对象,因为onDraw 方法可能会被频繁调用,这样就可能会产生大量临时对象,从而导致系统频繁GC造成程序卡顿。
  • 不要在onDraw 方法内执行耗时操作,如果在下一次渲染到来之前绘制任务还未完成,就会发生掉帧。

三、避免内存泄漏

Android中常见的内存泄漏 & 解决方案

四、RecyclerView优化

  • onBindViewHolder() 方法是在主线程进行的,因此它的主要任务是负责绑定数据,尽量避免在方法内部执行其他耗时操作
  • 如果列表上只有小部分数据需要刷新,尽量使用局部刷新的方式,而不是任何时候都全部刷新。(利用onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads)的payloads参数来完成局部刷新)
  • 简化ItemView的布局层级和组成结构,重复的布局可以尝试使用自定义View的方式替代。
  • 如果ItemView高度是固定的,可以调用RecyclerView.setHasFixedSize(true); 方法。当hasFixedSize设置为true,适配器内容的变化不会导致整个RecyclerView的尺寸发生变化。这样可以减少因重复计算每个Item的高度而造成的性能损耗。
  • 如果RecyclerView的ItemView也是RecyclerView(列表里面嵌套列表),并且子RecyclerView具有相同的Adapter,那么可以为子RecyclerView设置RecyclerView.setRecycledViewPool(pool)来共用一个RecycledViewPool,这样子RecyclerView对ItemView的回收和重用都可以在同一个对象池进行。
class ParentAdapter extends RecyclerView.Adapter<ParentAdapter.PViewHolder> {

    RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();

    @NonNull
    @Override
    public PViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        RecyclerView childRecyclerView = new RecyclerView(parent.getContext());
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(parent.getContext());
        linearLayoutManager.setRecycleChildrenOnDetach(true); //子View在detach之后被重用
        childRecyclerView.setLayoutManager(linearLayoutManager);
        childRecyclerView.setHasFixedSize(true); //item高度固定
        childRecyclerView.setRecycledViewPool(recycledViewPool); //共用RecycledViewPool对象池
        return new PViewHolder(childRecyclerView);
    }

    @Override
    public void onBindViewHolder(@NonNull PViewHolder holder, int position, @NonNull List<Object> payloads) {
        super.onBindViewHolder(holder, position, payloads);
        //payloads不为空时局部刷新列表
    }

    ……

    class PViewHolder extends RecyclerView.ViewHolder {

        public PViewHolder(@NonNull RecyclerView itemView) {
            super(itemView);
        }
    }
}
  • 如果是一个图片列表,应该考虑当列表正在滑动时暂停图片的加载,等到滑动停止时才开始加载图片。可以通过addOnScrollListener(@NonNull OnScrollListener listener)方法监听列表的滑动状态。
childRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        switch (newState) {
            case RecyclerView.SCROLL_STATE_IDLE:
                //停止滑动
                break;
            case RecyclerView.SCROLL_STATE_DRAGGING:
                //正在拖动
                break;
            case RecyclerView.SCROLL_STATE_SETTLING:
                //惯性滑动
                break;
        }
    }

    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
    }
});

五、线程管理优化

为了不影响主线程运行,Android应用程序中通常都会开启新线程去执行耗时任务,任务执行完毕后再把结果传递给主线程展示在UI上。

我们使用线程池来管理线程,线程池可以重用内部的线程,从而避免大量线程的创建和销毁带来的性能开销,同时也能控制线程的最大并发数,防止大量的线程互相抢占系统资源导致系统卡顿。因此我们应该尽量使用线程池来创建新线程,而不是直接创建一个Thread对象来执行任务。

关于线程池的相关知识可以参考Android 线程池的实现和分类

六、其他优化建议

相关文章

  • Awesome Extra

    性能优化 性能优化模式 常见性能优化策略的总结 Spark 性能优化指南——基础篇 Spark 性能优化指南——高...

  • Android优化文章精选

    Android性能优化典范 Android性能优化典范 - 第1季Android性能优化之渲染篇Android性能...

  • spark性能调优

    [Spark性能优化指南——基础篇][Spark性能优化指南——高级篇]

  • Android性能优化(下)

    Android性能优化 内存泄漏和性能优化方式Android性能优化(上)数据库优化和网络优化Android性能优...

  • [笔记]Android性能优化 中

    [笔记]Android性能优化 上[笔记]Android性能优化 中[笔记]Android性能优化 下 7.And...

  • [笔记]Android性能优化 下

    [笔记]Android性能优化 上[笔记]Android性能优化 中[笔记]Android性能优化 下 8.And...

  • [笔记]Android性能优化 上

    [笔记]Android性能优化 上[笔记]Android性能优化 中[笔记]Android性能优化 下 说明 这篇...

  • Android 性能优化系列视频(三)

    Android 性能优化系列视频如下 Android 性能优化系列视频(四)Android 性能优化系列视频(五...

  • Android 性能优化

    Android性能优化典范 - 第1季 Android性能优化典范 - 第2季 Android性能优化典范 - 第...

  • 收集_性能优化

    Android性能优化(一)之启动加速35%Android性能优化(二)之布局优化面面观Android性能优化(三...

网友评论

      本文标题:Android 性能优化指南

      本文链接:https://www.haomeiwen.com/subject/lbuekktx.html