中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

使用android布局如何進行優化

發布時間:2022-02-25 14:38:41 來源:億速云 閱讀:128 作者:小新 欄目:開發技術

這篇文章給大家分享的是有關使用android布局如何進行優化的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

前言

Android的繪制優化其實可以分為兩個部分,即布局(UI)優化和卡頓優化,而布局優化的核心問題就是要解決因布局渲染性能不佳而導致應用卡頓的問題,所以它可以認為是卡頓優化的一個子集。

為什么要進行布局優化?

為什么要進行布局優化?答案是顯而易見的,如果布局嵌套過深,或者其他原因導致布局渲染性能不佳,可能會導致應用卡頓 那么布局到底是如何導致渲染性能不佳的呢?首先我們應該了解下android繪制原理與布局加載原理

android繪制原理

Android的屏幕刷新中涉及到最重要的三個概念(為便于理解,這里先做簡單介紹)

  • CPU:執行應用層的measure、layout、draw等操作,繪制完成后將數據提交給GPU

  • GPU:進一步處理數據,并將數據緩存起來

  • 屏幕:由一個個像素點組成,以固定的頻率(16.6ms,即1秒60幀)從緩沖區中取出數據來填充像素點

總結一句話就是:CPU 繪制后提交數據、GPU 進一步處理和緩存數據、最后屏幕從緩沖區中讀取數據并顯示

使用android布局如何進行優化

雙緩沖機制

看完上面的流程圖,我們很容易想到一個問題,屏幕是以16.6ms的固定頻率進行刷新的,但是我們應用層觸發繪制的時機是完全隨機的(比如我們隨時都可以觸摸屏幕觸發繪制). 如果在GPU向緩沖區寫入數據的同時,屏幕也在向緩沖區讀取數據,會發生什么情況呢?有可能屏幕上就會出現一部分是前一幀的畫面,一部分是另一幀的畫面,這顯然是無法接受的,那怎么解決這個問題呢?

所以,在屏幕刷新中,Android系統引入了雙緩沖機制

GPU只向Back Buffer中寫入繪制數據,且GPU會定期交換Back Buffer和Frame Buffer,交換的頻率也是60次/秒,這就與屏幕的刷新頻率保持了同步。

使用android布局如何進行優化

雖然我們引入了雙緩沖機制,但是我們知道,當布局比較復雜,或設備性能較差的時候,CPU并不能保證在16.6ms內就完成繪制數據的計算,所以這里系統又做了一個處理。當你的應用正在往Back Buffer中填充數據時,系統會將Back Buffer鎖定。如果到了GPU交換兩個Buffer的時間點,你的應用還在往Back Buffer中填充數據,GPU會發現Back Buffer被鎖定了,它會放棄這次交換。

這樣做的后果就是手機屏幕仍然顯示原先的圖像,這就是我們常常說的掉幀

布局加載原理

由上面可知,導致掉幀的原因是CPU無法在16.6ms內完成繪制數據的計算。而之所以布局加載可能會導致掉幀,正是因為它在主線程上進行了耗時操作,可能導致CPU無法按時完成數據計算

布局加載主要通過setContentView來實現,我們就不在這里貼源碼了,一起來看看它的時序圖

使用android布局如何進行優化

我們可以看到,在setContentView中主要有兩個耗時操作

  • 解析xml,獲取XmlResourceParser,這是IO過程

  • 通過createViewFromTag,創建View對象,用到了反射

以上兩點就是布局加載可能導致卡頓的原因,也是布局的性能瓶頸

獲取布局文件加載耗時的方法

我們如果需要優化布局卡頓問題,首先最重要的就是:確定定量標準 所以我們首先介紹幾種獲取布局文件加載耗時的方法

常規獲取

首先介紹一下常規方法

val start = System.currentTimeMillis()
setContentView(R.layout.activity_layout_optimize)
val inflateTime = System.currentTimeMillis() - start

這種方法很簡單,因為setContentView是同步方法,如果想要計算耗時,直接將前后時間計算相減即可得到結果了

AOP(Aspectj,ASM)

上面的方式雖然簡單,但是卻不夠優雅,同時代碼有侵入性,如果要對所有Activity測量時,就需要在基類中復寫相關方法了,比較麻煩了 下面介紹一種AOP的方式計算耗時

    @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();
        }
        Log.i("aop inflate",name + " cost " + (System.currentTimeMillis() - time));
    }

上面用的Aspectj,比較簡單,上面的注解的意思是在setContentView方法執行內部去調用我們寫好的getSetContentViewTime方法 這樣就可以獲取相應的耗時 我們可以看下打印的日志

I/aop inflate: AppCompatActivity.setContentView(..) cost 69
I/aop inflate: AppCompatActivity.setContentView(..) cost 25

這樣就可以實現無侵入的監控每個頁面布局加載的耗時 具體源碼可見文末

獲取任一控件耗時

有時為了更精確的知道到底是哪個控件加載耗時,比如我們新添加了自定義View,需要監控它的性能 我們可以利用setFactory2來監聽每個控件的加載耗時 首先我們來回顧下setContentView方法

    public final View tryCreateView(@Nullable View parent, @NonNull String name,
        ...
        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;
        }
        ...
        return view;
    }

在真正進行反射實例化xml結點前,會調用mFactory2的onCreateView方法 這樣如果我們重寫onCreateView方法,在其前后加上耗時統計,即可獲取每個控件的加載耗時

    private fun initItemInflateListener(){
        LayoutInflaterCompat.setFactory2(layoutInflater, object : Factory2 {
            override fun onCreateView(
                parent: View?,
                name: String,
                context: Context,
                attrs: AttributeSet
            ): View? {
                val time = System.currentTimeMillis()
                val view = delegate.createView(parent, name, context, attrs)
                Log.i("inflate Item",name + " cost " + (System.currentTimeMillis() - time))
                return view
            }

            override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
                return null
            }
        })
    }

如上所示:真正的創建View的方法,仍然是調用delegate.createView,我們只是其之前與之后做了埋點 注意,initItemInflateListener需要在onCreate之前調用 這樣就可以比較方便地實現監聽每個控件的加載耗時

布局加載優化的一些方法介紹

布局加載慢的主要原因有兩個,一個是IO,一個是反射 所以我們的優化思路一般有兩個

  1. 側面緩解(異步加載)

  2. 根本解決(不需要IO,反射過程,如X2C,Anko,Compose等)

AsyncLayoutInflater方案

AsyncLayoutInflater 是來幫助做異步加載 layout 的,inflate(int, ViewGroup, OnInflateFinishedListener) 方法運行結束之后 OnInflateFinishedListener 會在主線程回調返回 View;這樣做旨在 UI 的懶加載或者對用戶操作的高響應。

簡單的說我們知道默認情況下 setContentView 函數是在 UI 線程執行的,其中有一系列的耗時動作:Xml的解析、View的反射創建等過程同樣是在UI線程執行的,AsyncLayoutInflater 就是來幫我們把這些過程以異步的方式執行,保持UI線程的高響應。

使用如下:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new AsyncLayoutInflater(AsyncLayoutActivity.this)
                .inflate(R.layout.async_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
                    @Override
                    public void onInflateFinished(View view, int resid, ViewGroup parent) {
                        setContentView(view);
                    }
                });
        // 別的操作
    }

這樣做的優點在于將UI加載過程遷移到了子線程,保證了UI線程的高響應 缺點在于犧牲了易用性,同時如果在初始化過程中調用了UI可能會導致崩潰

X2C方案

X2C是掌閱開源的一套布局加載框架 它的主要是思路是在編譯期,將需要翻譯的layout翻譯生成對應的java文件,這樣對于開發人員來說寫布局還是寫原來的xml,但對于程序來說,運行時加載的是對應的java文件。這就將運行時的開銷轉移到了編譯時 如下所示,原始xml文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:paddingLeft="10dp">

  <include
      android:id="@+id/head"
      layout="@layout/head"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerHorizontal="true" />

  <ImageView
      android:id="@+id/ccc"
      
      android:layout_below="@id/head" />
</RelativeLayout>

X2C 生成的 Java 文件

public class X2C_2131296281_Activity_Main implements IViewCreator {
  @Override
  public View createView(Context ctx, int layoutId) {
        Resources res = ctx.getResources();

        RelativeLayout relativeLayout0 = new RelativeLayout(ctx);
        relativeLayout0.setPadding((int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,res.getDisplayMetrics())),0,0,0);

        View view1 =(View) new X2C_2131296283_Head().createView(ctx,0);
        RelativeLayout.LayoutParams layoutParam1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        view1.setLayoutParams(layoutParam1);
        relativeLayout0.addView(view1);
        view1.setId(R.id.head);
        layoutParam1.addRule(RelativeLayout.CENTER_HORIZONTAL,RelativeLayout.TRUE);

        ImageView imageView2 = new ImageView(ctx);
        RelativeLayout.LayoutParams layoutParam2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,(int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,1,res.getDisplayMetrics())));
        imageView2.setLayoutParams(layoutParam2);
        relativeLayout0.addView(imageView2);
        imageView2.setId(R.id.ccc);
        layoutParam2.addRule(RelativeLayout.BELOW,R.id.head);

        return relativeLayout0;
  }
}

使用時如下所示,使用X2C.setContentView替代原始的setContentView即可

// this.setContentView(R.layout.activity_main);
X2C.setContentView(this, R.layout.activity_main);

X2C優點

  1. 在保留xml的同時,又解決了它帶來的性能問題

  2. 據X2C統計,加載耗時可以縮小到原來的1/3

X2C問題

  1. 部分屬性不能通過代碼設置,Java不兼容

  2. 將加載時間轉移到了編譯期,增加了編譯期耗時

  3. 不支持kotlin-android-extensions插件,犧牲了部分易用性

Anko方案

Anko是JetBrains開發的一個強大的庫,支持使用kotlin DSL的方式來寫UI,如下所示

class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        MyActivityUI().setContentView(this)
    }
}

class MyActivityUI : AnkoComponent<MyActivity> {
    override fun createView(ui: AnkoContext<MyActivity>) = with(ui) {
        verticalLayout {
            val name = editText()
            button("Say Hello") {
                onClick { ctx.toast("Hello, ${name.text}!") }
            }
        }
    }
}

如上所示,Anko使用kotlin DSL實現布局,它比我們使用Java動態創建布局方便很多,主要是更簡潔,它和擁有xml創建布局的層級關系,能讓我們更容易閱讀 同時,它去除了IO與反射過程,性能更好,以下是Anko與XML的性能對比

使用android布局如何進行優化

不過由于AnKo已經停止維護了,這里不建議大家使用,了解原理即可 AnKo建議大家使用Jetpack Compose來替代使用

Compose方案

Compose 是 Jetpack 中的一個新成員,是 Android 團隊在2019年I/O大會上公布的新的UI庫,目前處于Beta階段 Compose使用純kotlin開發,使用簡潔方便,但它并不是像Anko一樣對ViewGroup的封裝 Compose 并不是對 View 和 ViewGroup 這套系統做了個上層包裝來讓寫法更簡單,而是完全拋棄了這套系統,自己把整個的渲染機制從里到外做了個全新的。

可以確定的是,Compose是取代XML的官方方案

Compose的主要優點就在于它的簡單好用,具體來說就是兩點

  1. 它的聲明式 UI

  2. 去掉了 xml,只使用 Kotlin 一種語言

由于本文并不是介紹Compose的,所以就不繼續介紹Compose了,總得來說,Compose是未來android UI開發的方向,讀者可以自行查閱相關資料

一些常規優化手段

上面介紹了一些改動比較大的方案,其實我們在實際開發中也有些常規的方法可以優化布局加載 比如優化布局層級,避免過度繪制等,這些簡單的手段可能正是可以應用到項目中的

優化布局層級及復雜度

  1. 使用ConstraintLayout,可以實現完全扁平化的布局,減少層級

  2. RelativeLayout本身盡量不要嵌套使用

  3. 嵌套的LinearLayout中,盡量不要使用weight,因為weight會重新測量兩次

  4. 推薦使用merge標簽,可以減少一個層級

  5. 使用ViewStub延遲加載

避免過度繪制

  1. 去掉多余背景色,減少復雜shape的使用

  2. 避免層級疊加

  3. 自定義View使用clipRect屏蔽被遮蓋View繪制

感謝各位的閱讀!關于“使用android布局如何進行優化”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

巴林左旗| 双柏县| 米林县| 达拉特旗| 木里| 清镇市| 宝丰县| 荣成市| 海淀区| 彭泽县| 连山| 宣城市| 岳普湖县| 栾川县| 禄劝| 永川市| 灵武市| 江阴市| 新丰县| 白水县| 洛阳市| 万安县| 荥阳市| 郯城县| 长春市| 临桂县| 铜鼓县| 商都县| 松原市| 阿城市| 措美县| 兴安县| 台东县| 寿光市| 通许县| 桑日县| 衢州市| 名山县| 布尔津县| 昆山市| 乐昌市|