天天看点

Android 性能优化:内存优化、布局、电量、流量、启动

一、布局优化

Android中布局优化主要包含以下三个方面:布局层级和测量次数、布局过度绘制、绘制过程

1、布局层级与测量次数

布局层级越多,绘制耗时就会相应增加。考虑使用布局层级比较少的方案.

(1)合理选择父容器

在布局层数相同时,我们优先选择测量次数较少的父容器

通常我们选取的优先级为:FrameLayout、不带Layou_wight的LinearLayuut、RelativeLayout。因为带有Layot_weight的LinearLayout和RelativeLayout会测量两次。

总结来看,首先优先布局层级少的方案,在布局层级相同时,采用测量次数少的。

那么如何分析布局层级呢?

(1)Android Device Monitor

Android studio3.0开始,Google不建议使用它, 因此我们需要手动在sdk目录下的tools中找到它,之后运行你的apk并且选择Hierarchy View,就可以查看对应的层级关系,如下:

Android 性能优化:内存优化、布局、电量、流量、启动

2)Component Tree

在Android studio.3.0之后,我们可以使用Component Tree,它同样提供了查看组件层级的功能,具体如下:

Android 性能优化:内存优化、布局、电量、流量、启动

选择并查看我们的xm布局,点击左下角的design,则可以看到左侧层级关系。

2)标签

除了上面提到的方式外,我们还可以通过使用标签来减少层级和复用组件

(1)include标签

include标签的作用就是可以直接引用已有的布局,而不需要重新写布局。而通常会将include和merge标签相结合使用,下面会介绍merge标签如下:

<?xml version="1.0" encoding="utf-8"?>
 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 
xmlns:tools="http://schemas.android.com/tools"
 
android:id="@+id/activity_main"
 
android:layout_width="match_parent"
 
android:layout_height="match_parent"
 
android:paddingBottom="@dimen/activity_vertical_margin"
 
android:paddingLeft="@dimen/activity_horizontal_margin"
 
android:paddingRight="@dimen/activity_horizontal_margin"
 
android:paddingTop="@dimen/activity_vertical_margin"
 
tools:context="com.jared.layoutoptimise.MainActivity">
 
<include layout="@layout/item_test_linear_layout" />
 
</RelativeLayout>

           

这里include引用的是一个LinearLayout的布局,虽然我们不需要重复写这个布局,但是却增加了层级关系。

(2)merge标签

merge标签通常是作为include标签的辅助扩展,就是为了解决引入include后导致布局层级增加问题,使用merge标签后,引入的布局中的View就会作为父布局的子View。

如下:

<?xml version="1.0" encoding="utf-8"?>
 
<RelativeLyout xmlns:android="http://schemas.android.com/apk/res/android"
 
android:layout_width="match_parent"
 
android:layout_height="match_parent">
 
<ImageView
 
android:id="@+id/iv_image"
 
android:layout_width="wrap_content"
 
android:layout_height="wrap_content"
 
android:layout_margin="10dp"
 
android:src="@mipmap/ic_launcher" />
 
<TextView
 
android:id="@+id/tv_title"
 
android:layout_width="wrap_content"
 
android:layout_height="wrap_content"
 
android:layout_marginLeft="10dp"
 
android:layout_marginTop="16dp"
 
android:layout_toRightOf="@+id/iv_image"
 
android:text="这个是MergeLayout"
 
android:textSize="16sp" />
 
<TextView
 
android:id="@+id/tv_content"
 
android:layout_width="wrap_content"
 
android:layout_height="wrap_content"
 
android:layout_below="@+id/tv_title"
 
android:layout_marginLeft="10dp"
 
android:layout_marginTop="10dp"
 
android:layout_toRightOf="@+id/iv_image"
 
android:text="这个是MergeLayout,这个是MergeLayout"
 
android:textSize="12sp" />
</RelativeLyout>
           

使用include标签引入后,层级关系如下:

Android 性能优化:内存优化、布局、电量、流量、启动

现在我们把标签改为merge,层级关系如下:

Android 性能优化:内存优化、布局、电量、流量、启动

可以明显看到中间少了一层。

3)ViewStub标签

ViewStub继承于View,它是一个轻量级且宽高为0的组件,本身不参与布局和绘制。因此使用它可以做到在不需要的时候不加载,在需要的时候再加载,从而提高性能。

那么如何做到需要的时候显示呢?

可以通过以下两种方式:

setVisiable(View.Visiable)或者findViewById().inflate()

(3)使用ConstaintLayout

ConstaintLayout允许在不使用任何嵌套的情况下创建复杂布局,与RelativeLayout相似,可以依赖兄弟容器和父控件之间的相对关系。常见属性:

app:layout_constraintLeft_toLeftOf=“parent”

app:layout_constraintTop_toTopOf=“parent”

app:layout_constraintLeft_toRightOf="@+id/iv_image"

例如:

<?xml version="1.0" encoding="utf-8"?>
 
<android.support.constraint.ConstraintLayout
 
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:id="@+id/lay_root"
 
android:layout_width="match_parent"
 
android:layout_height="match_parent">
 
<ImageView
 
android:id="@+id/iv_image"
 
android:layout_width="wrap_content"
 
android:layout_height="wrap_content"
 
android:layout_margin="10dp"
 
android:src="@mipmap/ic_launcher"
 
android:layout_marginStart="16dp"
 
app:layout_constraintLeft_toLeftOf="parent"
 
android:layout_marginLeft="16dp"
 
android:layout_marginTop="16dp"
 
app:layout_constraintTop_toTopOf="parent" />
 
<TextView
 
android:id="@+id/tv_title"
 
android:layout_width="0dp"
 
android:layout_height="wrap_content"
 
android:layout_toRightOf="@+id/iv_image"
 
android:text="这个是ConstraintLayout"
 
android:textSize="16sp"
 
app:layout_constraintLeft_toRightOf="@+id/iv_image"
 
android:layout_marginStart="20dp"
 
android:layout_marginLeft="20dp"
 
android:layout_marginTop="20dp"
 
app:layout_constraintTop_toTopOf="parent" />
 
<TextView
 
android:id="@+id/tv_content"
 
android:layout_width="0dp"
 
android:layout_height="wrap_content"
 
android:layout_below="@+id/tv_title"
 
android:layout_toRightOf="@+id/iv_image"
 
android:text="这个是ConstraintLayout,这个是RelativeLayout"
 
android:textSize="12sp"
 
app:layout_constraintTop_toBottomOf="@+id/tv_title"
 
android:layout_marginTop="16dp"
 
app:layout_constraintLeft_toLeftOf="@+id/tv_title" />
 
</android.support.constraint.ConstraintLayout>
           

2、过度绘制

过度绘制其实指的是屏幕内某个像素在同一帧的时间内被绘制了多次。

而在多层次重叠的UI结构里,如果不可见的UI也在绘制的话,就会导致某些像素区域被绘制多次,从而浪费大量的cpu和gpu资源。

关于绘制的原理可以参考https://blog.csdn.net/xsf50717/article/details/78444047,这里不再赘述

目前提供两种方式来检测过度绘制:

手机自带检测工具

手机的开发者模式中会有一项为调试GPU过度绘制>显示GPU过度绘制,设置后,打开任何一个app,就可以看到界面上出现蓝、绿、粉、红四种颜色中的一种或者多种。

蓝色:1次过度绘制

绿色:2次过度绘制

粉丝:3次过度绘制

红色:4次过度绘制

例如:

Android 性能优化:内存优化、布局、电量、流量、启动
  • Android device monotor

    打开Hierarchy Viewer(/'haɪərɑːkɪ/),运行模拟器,打开对应的Activity界面,就可以看到如下:

    Android 性能优化:内存优化、布局、电量、流量、启动
    其中每一个View中,下面三个点依次表示测量、布局、绘制的时间,红点和黄点表示速度慢,而蓝绿则相对好一些。

在了解了如何分析过度绘制后,我们如何去处理过度绘制?

(1)去掉Windwo的默认背景

一般来说我们使用的Activity都会有一些默认的主题,通常这个主题会有一个对应的背景,被DecoreView持有,我们自定义布局时如果又添加了一个背景图或者设置背景色,就会产生一个overdraw,因此可以考虑去掉默认的背景。

我们可以在onCreate()的setContentView之前调用

getWindow().setBackGroundDrawable(null)

或者是在theme中加入windowbackground=“null”

(2)去掉其他不必要的背景

有时候我们为layout设置一个背景,而它的子View也有背景,此时就会造成重叠。针对这种情况我们首先考虑添加背景是否需要,如果需要我们可以考虑通过selector普通状态下设置背景为透明,点击状态下设置背景来减少重绘。

(3)优化onDraw方法

  • 避免在onDraw()方法中分配对象,因为onDraw方法可能被多次调用,这样的话可能会产生很多不必要的对象
  • 使用ClipRect制定个绘制区域

    在使用自定义View时,我们可以利用此方法来指定一块可见区域,用于绘制,其他区域则会被忽略,即只绘制clipRect指定的区域。

    例如在图片层叠时,我们将重叠部分不再绘制,只绘制不重叠部分。直接绘制如下:

    Android 性能优化:内存优化、布局、电量、流量、启动
    可以看到重叠部分出现了过度绘制,而实际上重叠部分我们不需要绘制底层部分,因为我们只能看到上面的图层,因此我们可以利用clipRect()来指定区域。如下:
@Override
 
protected void onDraw(Canvas canvas) {
 
super.onDraw(canvas);
 
canvas.save();
 
int bits = mBitmaps.length;
 
for (int i = 0; i < bits; i++) {
 
Bitmap bitmap = mBitmaps[i];
 
int bitW = bitmap.getWidth();
 
int bitH = bitmap.getHeight();
 
if (i != 0) {
 
canvas.translate(bitW / 2, 0);//每绘制完图片时,画布向前平移width/2的长度
 
}
 
canvas.save();
 
if (i != bits - 1) {
 
canvas.clipRect(0, 0, bitW / 2, bitH);//选择绘制区域,每次只绘制图片的一半
 
}
 
canvas.drawBitmap(bitmap, 0, 0, null);
 
canvas.restore();
 
}
 
canvas.restore();
 
}
           

4)标签

也就是前面提到的include、merge、viewStub标签

二、内存优化

1、防止内存泄漏,还有些代码时要注意防止内存泄漏,还可以使用Leakcancary内存检测工具。

2、合理使用如数组、链表、队列、栈、树、哈希表等数据结构。在这说一下,推荐两个安卓常用的SparseArray和ArrayMap,

他们相比hashmap比较节省内存,在1000以下,性能上的差异可以忽略。

3、用int或者字符串常量代替枚举,枚举太占内存,大概是int的2倍。

内存优化之图片优化

1、把图片素材放在合适的目录下

2、bitmap优化,就是加载图片时可以调用BitmapFactory.Options来按照一定采样率来加载所需的图片大小。

3、用Glide、picisoo等三方框架加载图片。
           

三、电量优化

耗电的原因:1、大数据的传输 2、解析大量的文本数据 3、不停的在网络间切换

解决方案:

1、先查看是否处于网络连接状态,如果没有连接成功,就不要执行响应的程序。

    2、使用高效的数据格式和解析方法,如json

    3、在进行大数据请求时,使用GZip压缩,它会大大减少文本文件的体积,从而使数据的传输效率变高

    在开发中,也要灵活的判断当前电量,如果电量低的话,就减少一些更新的操作,如果充电时或者电量充足时

    就加快App更新速度
           

四、流量优化

1、同步数据尽量在wifi下

2、接口能合一则合,产品设计简单极致

3、做好本地数据缓存,以及和服务端的同步机制;例如get请求和cache-control

4、webview做好本地缓存,加载时优先加载本地资源,当然也需要服务端支持

修改http服务器中的配置,使其支持text/cache-manifest,我使用的是apache服务器,是windows版本的,在apache的conf文件夹中找到mime.types文件,打开后在文件的最后加上“text/cache-manifest mf manifest”,重启服务器

5、压缩H5的图片大小,以及改变图片格式为webp,或者把背景图改为jpg,将纯色图用xml来写。

五、启动优化

1、在Application的构造器方法、attachBaseContext()、onCreate()方法中不要进行耗时操作的初始化,一些数据预取放在异步线程中,可以采取Callable实现。

2、对于sp的初始化,因为sp的特性在初始化时候会对数据全部读出来存在内存中,所以这个初始化放在主线程中不合适,反而会延迟应用的启动速度,对于这个还是需要放在异步线程中处理。

3、对于MainActivity,由于在获取到第一帧前,需要对contentView进行测量布局绘制操作,尽量减少布局的层次,考虑StubView的延迟加载策略,当然在onCreate、onStart、onResume方法中避免做耗时操作。

遵循上面三种策略可明显提高app启动速度。

继续阅读