上一篇关于主要关于编码技巧,这一篇主要关于布局。

布局是Android应用里直接影响用户体验的一个关健部分。如果布局设计的不合理,可能导致你的应用大量的占用内存,出现假死等情况。

布局性能优化

Hierarchy Viewer——布局分析工具

概述

Android SDK提供了一个工具帮助你分析你的Layouts的性能问题——Hierarchy Viewer

结合这个工具同时查看本文,你能实现滑动流畅、占用内存最小的用户界面。

可以通过以下数据优化布局:
渲染出该布局的一个完整的item所需的时间

  • Measure:(测量)
  • Layout:(布局)
  • Draw:(绘制)

单位均为ms

使用

HierarchyViewer工具的jar包在skd/tools/目录下。

原本tools下应有个hierarchyviewer.bat。

记住它的名字:hierarchyviewer2lib-26.0.0-dev

好吧不用记,终端好像不能用命令打开了的样子...

The standalone version of hieararchyviewer is deprecated.
Please use Android Device Monitor (tools/monitor.bat) instead.

反正就是用Android Device Monitor来打开,Android Device Monitor打开的方式挺多的,

  1. 终端输入monitor
  2. 直接到目录打开bat文件
  3. 在Android studio菜单栏tools里去找 反正我是没找到
  4. 右上角菜单栏里找 反正我也是没找到

试图打开Android Device Monitor 遇到了如下错误

fileC:\Users\lin22AppData\LocalAndroid\Sdk\tools\lib\monitor-x86_64\configurati on\1556360813301.log

日志文件的话太长了....贴上来直接就说博客内容过长了 orz

网上资料大概分两种

  1. JDK版本不对
  2. 右键管理员权限打开,我反正是不管用

想想应该是版本问题,看了看我的 “up-to-date”版本的jdk,不禁***落泪....


再想想monitor既然已经被弃用了,使用Profile代替

However, most components of the Android Device Monitor are deprecated in favor of updated tools available in Android Studio 3.0 and higher.

然后在Android Studio 3.1之后,Hierarchy Viewer也已经移并不在开发使用Layout Inspector代替


重用布局和布局内嵌

为了提高你的Layouts的复用性,你也可以使用<include/>和<merge/>标签内嵌一个布局到另一个布局里。
布局重用是十分强大的,能让你产生可重用的复杂布局。

<include/>

比如:定义了一个title_bar——文件名为(title_bar.xml),被包含在每个activity的布局文件里。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_orange_light">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher" />
</FrameLayout>

然后在其他布局文件里就可以通过include导入如👇

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <include layout="@layout/title_bar"/>
    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/listView" />

</android.support.constraint.ConstraintLayout>

在<include/>标签里,你也能重写所有的layout参数(任何android:layout_*属性)

然而,如果你想要用<include/>标签重写布局属性,为了其他的布局属性生效,你必须同时重写布局宽高两个属性

    <include layout="@layout/title_bar"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        />

<merge/>

某些时候,自定义可重用的布局包含了过多的层级标签,比如我们需要在LinearLayout里面嵌入一个可重用的组件,而恰恰这个自定义的可重用的组件根节点也是LinearLayout,这样就多了一层没有用的嵌套,这样无疑只会拖慢程序速度。

而这个时候如果我们使用merge标签就可以避免那样的问题。


使用使用<include/>包含上面的布局的时候,系统会自动忽略merge层级,而把两个button直接放置与include平级

 

如👇两个文件,title_bar被包含在LinerLayout,则它原本xml文件内外部的FrameLayout就显得浪费了

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <include layout="@layout/title_bar"/>
    </LinearLayout>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/listView" />

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_orange_light">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher" />
</FrameLayout>

所以将👆的FrameLayout改为merge,系统就会忽略merge这个标签,相当于直接放了一个ImageView到主布局的LinearLayout

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

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

 当然设置的背景色也会对应的失效  有需要的话需要移动出去 :)

在需要时加载视图

有时你的布局可能需要一些复杂却又很少被用到的视图。

无论他们是item详情、进度指示器,或撤销的消息,你都可以在需要的时候加载这些视图,来减少内存使用量并加快渲染速度。

通过定义一个ViewStub来实现👆

  • ViewStub是一个没有尺寸大小并且不会在布局中嵌套或渲染任何东西的轻量级的视图。
  • 因此在视图层次展现或隐藏它的代价非常小。
  • 每一个ViewStub仅仅需要包含android:layout属性来展现指定的布局。

注意:

  • inflate()方法在视图渲染完毕后便直接展示该已渲染的视图View。因此,如果你需要和布局交互的话,不需要在调用findViewById()方法
  • 一个ViewStub是可见的后渲染完毕,该元素便不再是视图层次的一部分。它被已渲染的布局途欢,并且该布局的根视图的id是在ViewStub中被android:infatedId属性指定的id。(在android:id指定的id只有在这个ViewStub布局是可见、渲染完毕才是有效的)
  • ViewStub的一个缺点是,目前它在要渲染的布局中并不支持<merge/>标签

👇下面提供一个完整的测试案例

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <Button
        android:id="@+id/button_back"
        android:text="返回"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
         />

    <Button
        android:id="@+id/button_ok"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:text="完成" />

</android.support.constraint.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:background="@android:color/holo_orange_light">
        <ViewStub
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout="@layout/inc"
            android:id="@+id/stub_import"/>
        <include layout="@layout/title_bar"/>
    </LinearLayout>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/listView" />

</LinearLayout>
package com.example.a4_27layout_optimize;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewStub;
import android.widget.ImageView;
//imageview为title_bar里的imageview

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = findViewById(R.id.imageView);
        imageView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId()==R.id.imageView){
            ViewStub vs = findViewById(R.id.stub_import);
            vs.setVisibility(View.VISIBLE);
            //或
            //View v = vs.inflate();
        }
    }
}

 

效果如👇  点击图片才会去显示两个按钮