Android 性能优化指南

2020-07-22  本文已影响0人  怡红快绿

一、布局优化

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

简单介绍一下<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 标签提取重复的布局有两点好处:

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"/>

加载ViewStub 中的布局有两种:

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

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

二、View绘制优化

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

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

三、避免内存泄漏

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

四、RecyclerView优化

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);
        }
    }
}
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 线程池的实现和分类

六、其他优化建议

上一篇 下一篇

猜你喜欢

热点阅读