zoukankan      html  css  js  c++  java
  • Android 性能优化---布局优化

    Android 性能优化---布局优化

    Android 布局绘制原理

    布局加载过程

    setContentView() --> inflate() -- > getLayout()(I/O操作) --> createViewFromTag() --> mFactory2/mFactory -- > onCreateView()(反射)

    先看看源码
    从setContentView(R.layout.activity_main);进入
    public void setContentView(int resId) {
    this.ensureSubDecor();
    ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
    contentParent.removeAllViews();
    LayoutInflater.from(this.mContext).inflate(resId, contentParent);
    this.mOriginalWindowCallback.onContentChanged();
    }

    接着进入到inflate();
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
    Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" ("
    + Integer.toHexString(resource) + ")");
    }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    

    这里主要是通过getLayout来获取到XmlResourceParser ,而这里面是进行大量的I/O操作
    继续进入到inflate();这里方法比较多,就拿关键部分代码

    这里面有一个createViewFromTag();根据命名可以知道,应该是根据标签来创建对应的view

    ...
    //省略若干
    try {
                View view;
                if (mFactory2 != null) {
                    view = mFactory2.onCreateView(parent, name, context, attrs);
                } else if (mFactory != null) {
                    view = mFactory.onCreateView(name, context, attrs);
                } else {
                    view = null;
                }
    
                if (view == null && mPrivateFactory != null) {
                    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
                }
    
                if (view == null) {
                    final Object lastContext = mConstructorArgs[0];
                    mConstructorArgs[0] = context;
                    try {
                        if (-1 == name.indexOf('.')) {
                            view = onCreateView(parent, name, attrs);
                        } else {
                            view = createView(name, null, attrs);
                        }
                    } finally {
                        mConstructorArgs[0] = lastContext;
                    }
                }
    
                return view;
                ...
    //省略若干
    

    从这里面可以知道主要是通过mFactory2 或者mFactory来创建,进入onCreateView();

    ....//省略若干
    public final View createView(String name, String prefix, AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
            Constructor<? extends View> constructor = sConstructorMap.get(name);
            if (constructor != null && !verifyClassLoader(constructor)) {
                constructor = null;
                sConstructorMap.remove(name);
            }
            Class<? extends View> clazz = null;
    
            try {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
    
                if (constructor == null) {
                    // Class not found in the cache, see if it's real, and try to add it
                    clazz = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);
    
                    if (mFilter != null && clazz != null) {
                        boolean allowed = mFilter.onLoadClass(clazz);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    }
                    constructor = clazz.getConstructor(mConstructorSignature);
                    constructor.setAccessible(true);
                    sConstructorMap.put(name, constructor);
                } else {
                    // If we have a filter, apply it to cached constructor
                    if (mFilter != null) {
                        // Have we seen this name before?
                        Boolean allowedState = mFilterMap.get(name);
                        if (allowedState == null) {
                            // New class -- remember whether it is allowed
                            clazz = mContext.getClassLoader().loadClass(
                                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
    
                            boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                            mFilterMap.put(name, allowed);
                            if (!allowed) {
                                failNotAllowed(name, prefix, attrs);
                            }
                        } else if (allowedState.equals(Boolean.FALSE)) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    }
                }
    ....//省略若干
    

    从这里就可以知道这里创建View的时候,是通过反射来进行的。
    上面大概的流程,出现了两个可以优化的点,一个是I/O操作,一个是反射,如果一个布局文件嵌套很深,很复杂,这两个操作是相当的耗时的。那具体怎么优化呢,我们后面会说到。

    CPU

    CPU主要是负责计算显示的内容

    GPU

    GPU主要是负责栅格化,(UI元素绘制到屏幕上)

    常用的优化工具

    Systrace

    systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。在Android平台中,它主要由3部分组成:

    内核部分:Systrace利用了Linux Kernel中的ftrace功能。所以,如果要使用systrace的话,必须开启kernel中和ftrace相关的模块。

    数据采集部分:Android定义了一个Trace类。应用程序可利用该类把统计信息输出给ftrace。同时,Android还有一个atrace程序,它可以从ftrace中读取统计信息然后交给数据分析工具来处理。

    数据分析工具:Android提供一个systrace.py(python脚本文件,位于Android SDK目录/sdk/platform-tools/systrace中,其内部将调用atrace程序)用来配置数据采集的方式(如采集数据的标签、输出文件名等)和收集ftrace统计数据并生成一个结果网页文件供用户查看。

    简单来说,当机器以60帧/秒显示(也就是16.6 ms),用户会感觉机器会流畅。如果出现显示时出现丢帧的情况,就需要知道系统在做什么?

    Systrace 是用来收集系统和应用的数据信息和一些中间生成数据的细节,在Android 4.1系统和4.1之后的系统。
    Systrace在一些分析显示的问题上特别有用,如应用画图慢,显示动作或动画时变形。

    Systrace的使用

    很多网上的文章说通过Tools -> Android -> Android Device Monitor 来打开这个Systrace,我没有具体去了解,Android studio从哪个版本开始,已经没法从Tools中打开了,我是通过以下方式打开的:
    找到自己的SDK文件夹,可以看AS中的SDK路径

    接着

    接着

    接着

    打开即可

    然后选择对应的进程

    生成对应的html文件即可
    生成对应的html文件在goole浏览器打开

    Systrace 可以分析内存,卡顿,界面耗时等情况,这里主要说一下界面耗时分析方式
    基本操作:
    'w'按键:放大
    's'按键:缩小
    'd'按键:向右平移
    'a'按键:向左平移
    ‘m’按钮,查看对应的控件渲染耗时时间

    颜色区分:绿色表示正常,红色或者黄色表示丢帧,需要我们去优化,而出现红色或者黄色,则可以通过Alerts来查看详情。

    Layout Inspector

    布局的嵌套,我们可以通过Layout Inspector来进行查看
    在Android studio中通过Tools ---> Layout Inspector进行打开

    可以通过View Tree来查看布局的嵌套情况。

    获取界面布局耗时

    常规方式,手动埋点

    这种方式比较简单,就是在setContentView();前后添加日志

    AOP/ArtHook方式

    ARTHook这种方式在前面启动优化已经介绍过了,可以回头看看 [https://www.cnblogs.com/huangjialin/p/13292042.html]

    布局优化方式

    AsyncLayoutInflater

    AsyncLayoutInflater 这是谷歌提供的一个异步加载布局的一个类,使用这个方法,只是一个缓解,并没有从根本上解决耗时问题。
    具体的,大家可以去深入了解
    缺陷:
    1、不能设置LayoutInflater.Factory()
    2、注意View中不能有依赖主线程的操作。
    其实我个人认为,这种方式并不好,由于是异步加载布局,那么如果主线程中或者在onResume();方法中有用到某个控件,那都是会出问题。(如果大家有什么好的想法,也可以评论)

    X2C 框架

    前面我们看了布局的加载过程可以知道,解析布局出现耗时,是由于布局过于复杂,导致有大量的I/O操作和反射操作,这是两个耗时的点。那么如果不直接在java文件写布局呢,是不是就避免了这些I/O和反射操作呢?可以的,但是在java文件上写布局,又带来一个问题,就是可维护性问题。
    下面介绍这个框架,就是可以解决上面两个问题的
    X2C框架
    1、保留XML的优点,解决了其性能问题
    2、原理:APT编译期翻译XML为java代码
    使用方式
    添加依赖

    dependencies {
        annotationProcessor 'com.zhangyue.we:x2c-apt:1.1.2'
        implementation 'com.zhangyue.we:x2c-lib:1.0.6'
    }
    

    在对应的activity中添加注解

    将onCreate();方法中的setContentView(R.layout.activity_test);更换为X2C.setContentView(TestActivity.this, R.layout.activity_test);

    编译后可以在build -- generated -- ap_generated_sources -- debug -- 中查看生产对应的文件。
    这个框架可以在编译期间帮我们将布局xml文件动态生成java文件,避免了前面说的解析布局文件出现的I/O和反射导致耗时的问题。

    X2C框架:https://github.com/iReaderAndroid/X2C/blob/master/README_CN.md

    布局优化层级及复杂度

    1、使用ConstraintLayout布局,减少层级嵌套。
    2、不嵌套使用RelativeLayout,RelativeLayout和LinearLayout相比,LinearLayout性能更好,由于RelativeLayout是相对布局,确定位置的需要测量两次,性能较差。
    3、不在嵌套LinearLayout中使用weight,LinearLayout中使用weight在onMeasure阶段会绘制两次,嵌套中使用,性能更差。
    4、使用meger标签减少层级

    避免过度绘制

    1、去掉多余背景色,减少复杂shape使用
    2、避免层级叠加

    其他

    1、viewstub:高效占位符,延迟初始化
    2、onDraw()避免创建大对象,耗时操作

  • 相关阅读:
    Vue Cli 3.x项目如何部署到IIS子站点下
    IntelliJ IDEA 2018.3.2无法正常输入字符问题解决方案
    解决macOS git clone Azure DevOps提示身份认证失败问题
    JDBC driver for MySQL连接提示"The connection property 'zeroDateTimeBehavior' acceptable values are: 'CONVERT_TO_NULL', 'EXCEPTION' or 'ROUND'. The value 'convertToNull' is not acceptable."解决方案
    dbeaver导出MySQL的架构提示"IO Error: Utility 'mysqldump.exe' not found in client home 'MySQL Connector/Net"解决方案
    Git 常用命令和 Git Flow 梳理
    gitflow工作流程基本命令使用
    RocketMQ学习分享
    解决docker中使用nginx做负载均衡时并发过高时的一些问题
    Tomcat中session共享问题的简单解决办法
  • 原文地址:https://www.cnblogs.com/huangjialin/p/13353541.html
Copyright © 2011-2022 走看看