性能优化

View布局优化

布局优化首先我们应该做的就是尽可能的减少布局文件的层级,当布局的层级减少了,那绘制的性能自然就提高了,同时还可以使用下面的方式来优化我们的布局。
1.优先选择高性能的ViewGroup
android中界面布局使用android中提供的控件就可以,但是在使用的时候可以根据场景择优选择所需的控件,在选择ViewGroup的时候,可以优先选择性能较高的FrameLayout和LinearLayout,因为RelativeLayout较为费时,但是如果布局需要进行嵌套,可以考虑使用RelativeLayout来实现布局,以达到减少布局层级的目的。
2.可以使用TextView的leftDrawabel/rightDrawable代替ImageView+TextView布局,减少View的数量
3.使用include和merge标签重复利用布局,同时减少嵌套

  1. 使用include标签我们可以将公共的布局抽取出来,这样公共的布局我们不用总是重新再写一遍,同时include标签只支持 android:layout_ 开头的属性和android:id 属性,其他的属性不支持,同时在使用该属性的时候,如果使用了android:layout_*开头的其他属性,那么layout_width和layout_height属性必须同时存在,否则设置的android:layout_*开头的属性不起作用。
  2. 使用merge标签可以用来减少布局的层级,当使用include标签引用布局的时候,如果被引用布局的根布局和include标签的父布局为同一布局,就可以使用merge标签替换被引用布局的根布局标签。
  • 自己在尝试的过程中发现,使用include标签时,如果被引用布局的根元素使用merge标签,include属性设置android:layout_属性将不起作用,而需要在merge标签内设置include属性,这样include标签的android:layout_属性才会起作用。(此处为自己通过demo简单实践后得到的,如果有错误还请指正)

不起作用的写法:
layout布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".LayoutPerformanceActivity">

    <!--注意layout前面没android开头的命名空间-->
    <include
        android:id="@+id/reused_image"
        layout="@layout/commonimageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"/>
</LinearLayout>

被引用布局

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

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher_round" />
</merge>

运行结果:layout_margin属性不起作用

  • 起作用的写法
    修改被引用布局结构,在merge标签中使用include标签引用布局
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <include
        android:id="@+id/reused_image"
        layout="@layout/stub_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp" />
</merge>

stub_layout布局文件仅为一个图片:

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@mipmap/ic_launcher" />

运行后查看在merge里面的include使用layout_margin属性起作用了。。
3.使用ViewStub标签进行按需加载
ViewStub继承了View,自身不参与任何布局和绘制过程,当我们在开发中,一些布局在初始化时没有必要一起进行初始化,这时就可以使用ViewStub来按需加载该布局,因为当view设置为Gone或者Invisiable的时候,还是会占资源,具体参见(Android 使用View Gone 与 ViewStub的区别
但是需要注意的是ViewStub的引用布局不支持merge标签
ViewStub使用方式

    <ViewStub
        android:id="@+id/stub_import"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inflatedId="@+id/stub_import_view"
        android:layout="@layout/stub_import" />

android:id指定的即为ViewStub的id,当ViewStub被替换后,加载后得到的布局的Id即为android:inflatedId指定的id。
加载方式:

// mViewStub为 mViewStub = findViewById(R.id.stub_import);
mViewStub.setVisibility(View.VISIBLE);

代码:include标签和merge标签使用以及ViewStub标签使用

App绘制耗时分析工具Hierarchy Viewer

分析界面加载耗时情况时,可以使用Hierarchy Viewer工具去分析,
使用HierarchyViewer工具

查看过度绘制Overdraw

在"调试GPU过度绘制"中开启过度绘制调试:

Overdraw:用来描述一个像素在屏幕上重绘了多少次

在这里插入图片描述
原色: 没有overdraw
蓝色: 1次overdraw
绿色: 2次overdraw
粉色: 3次overdraw
红色: 4次及4次以上的overdraw

一般来说, 蓝色是可接受的, 是性能优的.

查看我们的代码如果有过度绘制的话,我们尽量优化代码减少过度绘制,以优化我们的布局。

硬件加速绘制和HARDWARE_LAYER

什么是硬件加速绘制?

实际上应该叫 GPU 加速,软硬件加速的区别主要是图形的绘制究竟是 GPU 来处理还是 CPU,如果是 GPU,就认为是硬件加速绘制,反之,则是软件绘制

如果TargetAPI的级别在14及以上,硬件加速是默认开启的。在判断View是否使用硬件加速绘制的时候,可以使用下面的方式去判断:

 chat_wv.post(new Runnable() {
      @Override
      public void run() {
        Log.e("isHardwareAccelerated", ""+chat_wv.isHardwareAccelerated());
      }
    });

如果在创建View之后就直接使用isHardwareAccelerated()方法去判断,这样会得到false,因为view只是刚创建。
使用硬件加速的情况下绘制效率要优于软件加速,但同时也会增加内存消耗。
什么是HARDWARE_LAYER?
首先Layer的概念主要是针对View的:

LAYER_TYPE_NONE: The view is rendered normally and is not backed by an off-screen buffer. This is the default behavior.
LAYER_TYPE_HARDWARE: The view is rendered in hardware into a hardware texture if the application is hardware accelerated. If the application is not hardware accelerated, this layer type behaves the same as LAYER_TYPE_SOFTWARE.
LAYER_TYPE_SOFTWARE: The view is rendered in software into a bitmap.

查看官方解释,Hardware layer表示拥有一个在硬件上渲染的缓冲区,当然只有开启硬件加速才会有该缓冲区,所以Hardware layer依赖硬件加速打开,当硬件加速没有打开的时候,使用Hardware layer效果和LAYER_TYPE_SOFTWARE一样。
使用Hardware layer的作用:
hardware layer 可用于将复杂视图树缓存到纹理中,并降低绘制操作的复杂性。 例如,在使用旋转动画时,可以使用硬件层仅渲染视图树一次,不用反复渲染,提高效率。

    view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
    animator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            view.setLayerType(View.LAYER_TYPE_NONE, null);
        }
    });
    animator.start();

使用HardwareLayer的时候,需要在使用完成后将layer type设置为LAYER_TYPE_NONE,这样才能释放HardwareLayer占用的内存。
在执行属性动画( alpha \ translation \ scale \ rotation )的时候,只是更新View的Property,每一帧不会去销毁和重建缓存,因此设置为hardware layer对性能有很大的提升。但是如果在onAnimationUpdate中做了添加删除view的操作,或者一直invalidate(),使得视图反复刷新,反而会使得动画性能变差。
查看动画帧率效果的时候可以使用下面的命令来获取App的帧率信息
adb shell dumpsys gfxinfo 包名
参考:Android 中的 Hardware Layer 详解

异步加载Bitmap

加载bitmap的时候,如果bitmap加载的时候不是从内存中实时读取的话,那么这个时候就不应该在主线程中进行加载,因为磁盘读写速度,CPU处理能力,网络环境等都没办法确保图片可以实时加载,从而造成卡顿甚至是ANR(activity位于前台5s,广播10s),
下面例子加载的时候使用AsyncTask:

使用AsyncTask的时候,实现doInBackground()方法,该方法会在子线程中执行,我们可以在该方法中执行耗时操作,但是execute的调用必须放到主线程中。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        // WeakReference用来防止内存泄漏
        private final WeakReference<ImageView> imageViewReference;
        private int data = 0;

        public BitmapWorkerTask(ImageView imageView) {
            // Use a WeakReference to ensure the ImageView can be garbage collected
            imageViewReference = new WeakReference<ImageView>(imageView);
        }

        // Decode image in background.
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(getResources(), data, 100, 100);
        }

        // Once complete, see if ImageView is still around and set bitmap.
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                // 如果imageView被GC回收了就不执行
                if (imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }

-----------------------------------------------------------------------------

// 使用
public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

上面的代码使用WeakReference来引用ImageView对象,这样可以防止内存泄漏。Android中的各种引用的回收情况如下:

强引用:当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用
软引用: 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象
弱引用:弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

上面的代码完成了实现了图片的异步加载。但是由于Android中的控件会有一套View的回收复用机制,所以这里需要处理View复用的情况:

  1. 自定义图片加载的Task.
  2. 自定义一个Drawable,包含一个默认的Bitmap和一个Task,默认的Bitmap用于初始时的显示,Task用于加载图片并显示.
  3. 在每次绑定childView和Task的时候,由于childView可能是由之前的View复用而来,应此先判断该View是否已经绑定了Task,若有则取消重新绑定,若无则直接绑定.
  4. 将childView和要加载的图片的Task绑定.

1.自定义的图片加载器子线程加载的代码见上面的BitmapWorkerTask,只不过在onPostExecute时我们需要判断线程是否cancel,以及imageView是否被复用和回收。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            // 判断非空
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

2.自定义Drawable,Drawable中存储当前加载图片的BitmapWorkerTask的弱引用:

static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

获取View绑定的WorkerTask,通过自定义过的Drawable中存储的Task,可以获取到WorkerTask。

private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
   if (imageView != null) {
       final Drawable drawable = imageView.getDrawable();
       if (drawable instanceof AsyncDrawable) {
           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
           return asyncDrawable.getBitmapWorkerTask();
       }
    }
    return null;
}

判断是否取消ImageView绑定的Task,注意下面的data只是一个标识符,用于判断该Task是否是所需要的Task,如果是自己当前需要加载的资源的Task,就没必要cancel了,因为当前的imageview加载的就是需要的资源

public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        // If bitmapData is not yet set or it differs from the new data
        if (bitmapData == 0 || bitmapData != data) {
            // Cancel previous task
            bitmapWorkerTask.cancel(true);
        } else {
            // The same work is already in progress
            return false;
        }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true;
}

入参的时候传入两个参数:ImageView和data,防止图片加载错乱。

public void loadBitmap(int resId, ImageView imageView) {
	// cancelPotentialWork判断是否需要再次加载。
    if (cancelPotentialWork(resId, imageView)) {
        // 如果取消了就重新创建一个新的Task
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawable asyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(resId);
    }
}

参考:
DisplayingBitmaps
https://www.jianshu.com/p/4b6f57646745

AsyncLayoutInflater异步加载布局

官方链接
使用AsyncLayoutInflater可以在子线程中加载我们的布局,这样不会影响主线程ui。
代码中我们使用以下方式进行使用

        new AsyncLayoutInflater(this).inflate(R.layout.activity_async_load, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
            @Override
            public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
                setContentView(R.layout.activity_main);
                Log.i(TAG, "onInflateFinished: ");
            }
        });

https://www.jianshu.com/p/f0c0eda06ae4
使用的时候我们需要注意以下几点(官方文档中有说明)

  1. 使用异步 inflate,那么需要这个 layout 的 parent 的 generateLayoutParams 函数是线程安全的;
  2. 所有构建的 View 中必须不能创建 Handler 或者是调用 Looper.myLooper;(因为是在异步线程中加载的,异步线程默认没有调用 Looper.prepare );
  3. 异步转换出来的 View 并没有被加到 parent view中,AsyncLayoutInflater 是调用了 LayoutInflater.inflate(int, ViewGroup, false),因此如果需要加到 parent view 中,就需要我们自己手动添加;
  4. AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;
  5. 不支持加载包含 Fragment 的 layout;
  6. 如果 AsyncLayoutInflater 失败,那么会自动回退到UI线程来加载布局;
    使用NinePatch
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值