IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android性能分析与优化学习(四)布局优化 -> 正文阅读

[移动开发]Android性能分析与优化学习(四)布局优化

?

一、绘制原理

cpu负责计算显示内容
GPU负责棚格化(UI元素绘制到屏幕上)

16ms发出VSync信号触发UI渲染
大多数的Android设备屏幕刷新频率:60Hz

二、优化工具

(1)Systrace
a) 关注Frames
b) 正常:绿色原点,丢帧:黄色或红色
c) Alerts栏

(2)Layout Inspector
a)AndroidStudio自带工具

b)查看视图层次结构

(3)Choreographer
a)是一个类库,android自带的。

b)可以获取FPS,具备实时性

c)可以带到线上。

Choregrapher.getInstance().postFrameCallback

Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                Log.e("TAG", "starTime=" + starTime + ", frameTimeNanos=" + frameTimeNanos + ", frameDueTime=" + (frameTimeNanos - starTime) / 1000000);
            }
        });

输出

starTime=1757438563915168, frameTimeNanos=1757438654798396, frameDueTime=90

三、布局加载原理

从setContentView开始

@Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }


public abstract void setContentView(@LayoutRes int resId);

@Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

LayoutInflater是真正对resId加载,进入inflate方法

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}


public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    ...
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

@NonNull
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
    return loadXmlResourceParser(id, "layout");
}

XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
        throws NotFoundException {
   ...
            return impl.loadXmlResourceParser(value.string.toString(), id,
                    value.assetCookie, type);
        ...
}

loadXmlResourceParser加载指定文件的 XML 解析器。返回XmlResourceParser。
回到LayoutInflater的inflate方法,找到createViewFromTag方法,根据标签创建view,进入tryCreateView方法

        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);
        }

mPrivateFactory只用于fragment标签, 如果view == null

			if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

进入createView方法,里面是反射创建View

            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, 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 = Class.forName(prefix != null ? (prefix + name) : name, false,
                                mContext.getClassLoader()).asSubclass(View.class);

                ...
            try {
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) {
                    // Use the same context when inflating ViewStub later.
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                return view;

在这里插入图片描述
(1)布局文件读取慢:IO过程
(2)创建View慢:反射(比new慢3倍)
LayoutInflater.Factory:
LayoutInflater创建view的一个Hook(挂钩)
定制创建view的过程:例如全局替换自定义Textview等
LayoutInflater包含Factory和Factory2两个
Factory和Factory2:Factory2继承自Factory,并且多了一个参数parent

四 优雅获取界面布局耗时

背景:获取每个界面加载耗时
实现:覆写方法、手动埋点

Aop实现:

//切面点:Activity的setContentView
    @Around("execution(* android.app.Activity.setContentView(..))")
    public void getSetContentViewTime(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.toShortString();
        long time = System.currentTimeMillis();
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        LogUtils.i(name + " cost " + (System.currentTimeMillis() - time));
    }

获取任一控件的耗时

低侵入性、使用LayoutInflater.Factory

        //带有Compat表示的是兼容类,一般都有比较好的兼容性
        LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                //替换示例:将布局中的某一个控件替换成我们自定义的控件(如Textview)伪代码如下
                if (TextUtils.equals(name, "TextView")) {
                    // 生成自定义TextView,然后将Textview return回去
                }
                
                //每个控件的耗时    可以将此方法放入base类onCreate中  必须在super.onCreate(savedInstanceState)之前设置才有效
                long time = System.currentTimeMillis();
                View view = getDelegate().createView(parent, name, context, attrs);
                LogUtils.i(name + " cost " + (System.currentTimeMillis() - time));
                return view;
            }

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                return null;
            }
        });

五、异步Inflate实战

1、优化布局加载:异步AsyncLayoutInflater
在WorkThread加载布局,(原生使用的办法是在UI线程中加载布局),(2)加载结束后,回调主线程。
优点:节约主线程的时间。
使用 AsyncLayoutInflater 类实现步骤:

implementation 'com.android.support:asynclayoutinflater:28.0.0'
// 在Activity中的onCreate()方法中,屏蔽下面语句
setContentView(R.layout.activity_main);
 
// 然后,添加如下代码
        new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
            @Override
            public void onInflateFinished(@NonNull View view, int i, @Nullable ViewGroup viewGroup) {
                setContentView(view);
                mRecyclerView = findViewById(R.id.recycler_view);
                mRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
                mRecyclerView.setAdapter(mNewsAdapter);
                mNewsAdapter.setOnFeedShowCallBack(MainActivity.this);
            }
        });

?侧面缓解卡顿
使用了AsyncLayoutInflater就失去了向下兼容的特性,不能设置 LayoutInflater.Factory(自定义解决)
注意view中不能有依赖主线程的操作
这个方法只是缓解的作用,并没有从根本上解决(IO操作 和 反射)。

2、X2C介绍:
保留xml优点,解决性能问题(开发人员写xml,加载java代码),原理:通过APT编译期翻译xml为java代码
优点:本质上解决了性能问题,(不会IO操作,同时使用new创建)
缺点:引入新问题,不便于开发,可维护性差。(XML的优点是方便预览,维护性好)
部分属性Java不支持
失去了系统的兼容(AppCompat)如:TextView、ImageView系统在高版本向低版本有兼容
x2c使用:

依赖:
annotationProcessor 'com.zhangyue.we:x2c-apt:1.1.2'
implementation 'com.zhangyue.we:x2c-lib:1.0.6'
    
使用方式:    
@Xml(layouts = "activity_main")//添加Xml注解标明使用布局
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.AppTheme);
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        //使用X2C.setContentView,传入上下文和布局文件
        X2C.setContentView(MainActivity.this, R.layout.activity_main);
    }
}

六、视图绘制优化实战

布局绘制回顾:

测量:确定大小(遍历视图树,确认viewgroup和view元素的大小)
布局:确定位置(遍历视图树,每个viewgroup根据测量阶段的大小确认自己的位置)
绘制:绘制视图(视图树中的每个对象都会创建一个canvas对象,向GPU发送绘制命令)

性能瓶颈:

每个阶段耗时
自顶向下的遍历
触发多次(如嵌套RelativeLayout)

减少布局层级和复杂度:

准则:减少view树层级、宽而浅,避免窄而深
ConstraintLayout:实现几乎完全扁平化布局、构建复杂布局性能更高、具有RelativeLayout和LinearLayout特性
其他:不嵌套使用elativeLayout、不在嵌套LinearLayout中使用weight、merge标签可减少一个层级(只能用于根view)

避免过度绘制

一个像素最好只被绘制一次、调试GPU过度绘制、蓝色可接受
方法:
去掉多余背景色,减少复杂shape使用
避免层级叠加(控件不要重叠)
自定义view使用clipRect屏蔽被遮盖View绘制

    //自定义view的onDraw优化方法
    /**
     * Custom implementation to do drawing in this view. Waits for the AsyncTasks to fetch
     * bitmaps for each Droid and populate mDroidCards, a list of DroidCard objects. Then, draws
     * overlapping droid cards.
     */
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // Don't draw anything until all the Asynctasks are done and all the DroidCards are ready.
        if (mDroids.length > 0 && mDroidCards.size() == mDroids.length) {
            // Loop over all the droids, except the last one.
            int i;
            for (i = 0; i < mDroidCards.size() - 1; i++) {

                mCardLeft = i * mCardSpacing;
                canvas.save();
                // 指定绘制区域
                
                canvas.clipRect(mCardLeft,0,mCardLeft+mCardSpacing,mDroidCards.get(i).getHeight());

                // Draw the card. Only the parts of the card that lie within the bounds defined by
                // the clipRect() get drawn.
                drawDroidCard(canvas, mDroidCards.get(i), mCardLeft, 0);

                canvas.restore();
            }

            // Draw the final card. This one doesn't get clipped.
            drawDroidCard(canvas, mDroidCards.get(mDroidCards.size() - 1),
                    mCardLeft + mCardSpacing, 0);
        }

        // Invalidate the whole view. Doing this calls onDraw() if the view is visible.
        invalidate();
    }

其他

ViewStub:高效占位符、延迟初始化(没有测量和布局的过程)
onDraw中避免:创建大量对象、耗时操作

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-07 12:12:20  更:2021-08-07 12:13:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 11:51:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码