<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android高手筆記-屏幕適配 & UI優(yōu)化

          共 22159字,需瀏覽 45分鐘

           ·

          2022-07-11 13:17

          屏幕與適配

          • 由于Android碎片化嚴重,屏幕分辨率千奇百怪,而想要在各種分辨率的設備上顯示基本一致的效果,適配成本越來越高;

          • 屏幕適配究其根本只有兩個問題:

            1. 在不同尺寸及分辨率上UI的一致(影響著用戶體驗);

            2. 從效果圖到UI界面代碼的轉(zhuǎn)化效率(影響著開發(fā)效率);

          適配方式:

          px

          • 在標識尺寸時,Android官方并不推薦使用px(像素),因為不同分辨率的屏幕上,同樣像素大小的控件,在分辨率越高的手機上UI顯示效果越??;(因為分辨率越高單位尺寸內(nèi)容納的像素數(shù)越多)

          dp

          • 所以官方推薦使用dp作為尺寸單位來適配UI,dp在不同分辨率和尺寸的手機上代表了不同的真實像素;(px和dp的轉(zhuǎn)化關系:px=dp*(dpi/160),其中dpi是像素密度,是系統(tǒng)軟件上指定的單位尺寸的像素數(shù)量,往往是寫在系統(tǒng)出廠配置文件的一個固定值,這和屏幕硬件的ppi(物理像素密度)是不同的,是參考了物理像素密度后,人為指定的一個值,保證了某個區(qū)間內(nèi)的物理像素密度在軟件上都使用同一個值,有利于UI適配的簡化)這就是最原始的Android適配方案:dp+自適應布局和weight比例布局,基本可以解決不同手機上的適配問題;

          • 但是這種方案有兩個缺陷:

            1. 只能適配大部分手機(也做不到和效果圖的完全一致),某些特殊機型仍需單獨適配(比如同樣1920*1080的手機的dpi卻可能不同);

            2. 設計稿到布局代碼的實現(xiàn)效率低(設計稿的寬高和手機屏幕的寬高不同,px和dp間的轉(zhuǎn)換,往往需要百分比或估算等,會極大地拉低開發(fā)效率);

          寬高限定符適配

          • 窮舉市面上所有的Android手機的寬高像素值,設定一個基準的分辨率(最好和設計稿的寬高一致),其他分辨率根據(jù)這個基準分辨率來計算,生成對應的dimens文件(可通過java、python腳本實現(xiàn)自動生成),放在不同的尺寸文件夾(values)內(nèi)部;(插圖)如480320為基準,對于800480的dimens文件:x1=(480/320)*1=1.5px; x2=(480/320)2=3px;但是這種方案也有個致命缺陷,需要精準命中才能適配,如1440750的手機如果找不到對應的尺寸文件夾就只能用統(tǒng)一默認的dimens文件,UI就可能變形,而Android手機廠商眾多,機型更是不可枚舉,所以容錯機制很差;

          鴻洋的AndroidAutoLayout適配方案等動態(tài)計算UI適配框架

          • 鴻洋的適配方案也來自于寬高限定符方案的啟發(fā)(目前已經(jīng)停止維護);因為框架要在運行時會在onMeasure里面做變換,我們自定義的控件可能會被影響或限制,可能有些特定的控件,需要單獨適配,這里面可能存在的暗坑是不可預見的;整個適配工作是有框架完成的,而不是系統(tǒng)完成的,一旦使用這個框架,未來一旦遇到很難解決的問題,替換起來是非常麻煩的,而且項目一旦停止維護,后續(xù)的升級就只能靠你自己了;

          smallestWidth適配 或者叫sw限定符適配

          • 指的是Android會識別屏幕可用高度和寬度的較小者的dp值(其實就是手機的寬度值),然后根據(jù)識別到的結果去資源文件中尋找對應限定符的文件夾下的資源文件。這種機制和上文提到的寬高限定符適配原理上是一樣的,都是系統(tǒng)通過特定的規(guī)則來選擇對應的文件舉個例子,小米5的dpi是480,橫向像素是1080px,根據(jù)px=dp(dpi/160),橫向的dp值是1080/(480/160),也就是360dp,系統(tǒng)就會去尋找是否存在value-sw360dp的文件夾以及對應的資源文件;(插圖)smallestWidth限定符適配和寬高限定符適配最大的區(qū)別在于,有很好的容錯機制,如果沒有value-sw360dp文件夾,系統(tǒng)會向下尋找,比如離360dp最近的只有value-sw350dp, 那么Android就會選擇value-sw350dp文件夾下面的資源文件,這個特性就完美的解決了 上文提到的寬高限定符的容錯問題。(通過java、python腳本實現(xiàn)自動生成dimens文件)(插圖) (這種方案的優(yōu)勢是穩(wěn)定性,不會有暗坑)

          • smallestWidth適配方案有一個小問題,那就是它是在Android 3.2 以后引入的,Google的本意是用它來適配平板的布局文件(但是實際上顯然用于diemns適配的效果更好)所以這種方案支持的最小版本就是Android3.2了;

          • 還有一個缺陷就是多個dimens文件可能導致apk變大,根據(jù)生成的dimens文件的覆蓋范圍和尺寸范圍,apk可能會增大300kb-800kb左右;

          • 糗百的拉丁吳大佬生成好的文件https://github.com/ladingwu/dimens_sw

          • 所有的適配方案都不是用來取代match_parent,wrap_content的,而是用來完善他們的;

          今日頭條適配方案

          • 通過修改density(density = dpi / 160)值,強行把所有不同尺寸分辨率的手機的寬度dp值改成一個統(tǒng)一的值,這樣就解決了所有的適配問題這個方案侵入性很低,而且也沒有涉及私有API,只是對老項目是不太友好;

          • 如果我們想在所有設備上顯示完全一致,其實是不現(xiàn)實的,因為屏幕高寬比不是固定的,16:9、4:3甚至其他寬高比層出不窮,寬高比不同,顯示完全一致就不可能了。但是通常下,我們只需要以寬(支持上下滑動的頁面)或高(不支持上下滑動的頁面)一個維度去適配

          • 通過閱讀源碼,我們可以得知,density 是 DisplayMetrics 中的成員變量,而 DisplayMetrics 實例通過 Resources#getDisplayMetrics 可以獲得,而Resouces通過Activity或者Application的Context獲得;DisplayMetrics 中和適配相關的幾個變量:

            1. DisplayMetrics.density 就是上述的density;

            2. DisplayMetrics.densityDpi 就是上述的dpi;

            3. DisplayMetrics#scaledDensity 字體的縮放因子,正常情況下和density相等,但是調(diào)節(jié)系統(tǒng)字體大小后會改變這個值;

          • 布局文件中dp的轉(zhuǎn)換,最終都是調(diào)用TypedValue#applyDimension(int unit, float value,DisplayMetrics metrics) (插圖)來進行轉(zhuǎn)換,方法中用到的DisplayMetrics正是從Resources中獲得的;再看看圖片的decode,BitmapFactory#decodeResourceStream方法(插圖),也是通過 DisplayMetrics 中的值來計算的;因此,想要滿足上述需求,我們只需要修改 DisplayMetrics 中和 dp 轉(zhuǎn)換相關的變量即可;所以得到了下面適配方案:假設設計圖寬度是360dp,以寬維度來適配,那么適配后的 density = 設備真實寬(單位px) / 360,接下來只需要把我們計算好的 density 在系統(tǒng)中修改下即可,同時在 Activity#onCreate 方法中調(diào)用下但是會有字體過小的現(xiàn)象,原因是在上面的適配中,我們忽略了DisplayMetrics#scaledDensity的特殊性,將DisplayMetrics#scaledDensity和DisplayMetrics#density設置為同樣的值,從而某些用戶在系統(tǒng)中修改了字體大小失效了,但是我們還不能直接用原始的scaledDensity,直接用的話可能導致某些文字超過顯示區(qū)域,因此我們可以通過計算之前scaledDensity和density的比獲得現(xiàn)在的scaledDensity;但是測試后發(fā)現(xiàn)另外一個問題,就是如果在系統(tǒng)設置中切換字體,再返回應用,字體并沒有變化。于是還得監(jiān)聽下字體切換,調(diào)用 Application#registerComponentCallbacks 注冊下onConfigurationChanged 監(jiān)聽即可;

          • 可以參考https://github.com/Blankj/AndroidUtilCode,https://github.com/JessYanCoding/AndroidAutoSize這兩個開源庫;

          UI優(yōu)化

          CPU 與 GPU

          • Android的繪制實現(xiàn)主要是借助CPU與GPU結合刷新機制共同完成的。

          • 除了屏幕,UI 渲染還依賴兩個核心的硬件:CPU 與 GPU。

          • UI 組件在繪制到屏幕之前,都需要經(jīng)過 Rasterization(柵格化)操作,而柵格化操作又是一個非常耗時的操作。GPU(Graphic Processing Unit )也就是圖形處理器,它主要用于處理圖形運算,可以幫助我們加快柵格化操作。

          • CPU軟件繪制使用的是 Skia 庫,它是一款能在低端設備如手機上呈現(xiàn)高質(zhì)量的 2D 跨平臺圖形框架,類似 Chrome、Flutter 內(nèi)部使用的都是 Skia 庫;

          OpenGL 與 Vulkan

          • 對于硬件繪制,我們通過調(diào)用 OpenGL ES 接口利用 GPU 完成繪制。OpenGL是一個跨平臺的圖形 API,它為 2D/3D 圖形處理硬件指定了標準軟件接口。而 OpenGL ES 是 OpenGL 的子集,專為嵌入式設備設計。

          • Android 7.0 把 OpenGL ES 升級到最新的 3.2 版本同時,還添加了對Vulkan的支持。Vulkan 是用于高性能 3D 圖形的低開銷、跨平臺 API。相比 OpenGL ES,Vulkan 在改善功耗、多核優(yōu)化提升繪圖調(diào)用上有著非常明顯的優(yōu)勢。

          • 把應用程序圖形渲染過程當作一次繪畫過程,那么繪畫過程中 Android 的各個圖形組件的作用是:

          1. 畫筆:Skia 或者 OpenGL。我們可以用 Skia 畫筆繪制 2D 圖形,也可以用 OpenGL 來繪制 2D/3D 圖形。正如前面所說,前者使用 CPU 繪制,后者使用 GPU 繪制。
          2. 畫紙:Surface。所有的元素都在 Surface 這張畫紙上進行繪制和渲染。在 Android 中,Window View 的容器,每個窗口都會關聯(lián)一個 Surface
          WindowManager 則負責管理這些窗口,并且把它們的數(shù)據(jù)傳遞給 SurfaceFlinger。
          3. 畫板:Graphic Buffer。Graphic Buffer 緩沖用于應用程序圖形的繪制,在 Android 4.1 之前使用的是雙緩沖機制;在 Android 4.1 之后,使用的是三緩沖機制。
          4. 顯示:SurfaceFlinger。它將 WindowManager 提供的所有 Surface,通過硬件合成器 Hardware Composer 合成并輸出到顯示屏。

          Android 渲染的演進

          1. 在 Android 3.0 之前,或者沒有啟用硬件加速時,系統(tǒng)都會使用軟件方式來渲染 UI;

          2. Androd 3.0 開始,Android 開始支持硬件加速;

          3. Android 4.0 時,默認開啟硬件加速;

          4. Android 4.1:

            1. 開啟了Project Butter: 主要包含兩個組成部分,一個是 VSYNC,一個是 Triple Buffering。VSYNC信號:對于 Android 4.0,CPU 可能會因為在忙別的事情,導致沒來得及處理 UI 繪制。為解決這個問題,Project Buffer 引入了VSYNC,它類似于時鐘中斷。每收到 VSYNC 中斷,CPU 會立即準備 Buffer 數(shù)據(jù),由于大部分顯示設備刷新頻率都是 60Hz(一秒刷新 60 次),也就是說一幀數(shù)據(jù)的準備工作都要在 16ms 內(nèi)完成。三緩沖機制 Triple Buffering:Android 4.1 之前,Android 使用雙緩沖機制,CPU、GPU 和顯示設備都能使用各自的緩沖區(qū)工作,互不影響

            2. Android 4.1還新增了 Systrace 性能數(shù)據(jù)采樣和分析工具。

            3. Tracer for OpenGL ES 也是 Android 4.1 新增加的工具,它可逐幀、逐函數(shù)的記錄 App 用 OpenGL ES 的繪制過程。它提供了每個 OpenGL 函數(shù)調(diào)用的消耗時間,所以很多時候用來做性能分析。但因為其強大的記錄功能,在分析渲染問題時,當 Traceview、Systrace 都顯得棘手時,還找不到渲染問題所在時,此時這個工具就會派上用場了。

          5. Android 4.2,系統(tǒng)增加了檢測繪制過度工具;

          6. Android 5.0:RenderThread:

            • 經(jīng)過 Project Butter 黃油計劃之后,Android 的渲染性能有了很大的改善。但是不知道你有沒有注意到一個問題,雖然我們利用了 GPU 的圖形高性能運算,但是從計算 DisplayList,到通過 GPU 繪制到 Frame Buffer,整個計算和繪制都在 UI 主線程中完成。

            • Android 5.0 引入了兩個比較大的改變。一個是引入了 RenderNode 的概念,它對 DisplayList 及一些 View 顯示屬性做了進一步封裝。另一個是引入了 RenderThread,所有的 GL 命令執(zhí)行都放到這個線程上,渲染線程在 RenderNode 中存有渲染幀的所有信息,可以做一些屬性動畫,這樣即便主線程有耗時操作的時候也可以保證動畫流暢。

            • 還可以開啟 Profile GPU Rendering 檢查。

          7. Android 6.0 ,在 gxinfo 添加了更詳細的信息;

          8. 在 Android 7.0 又對 HWUI 進行了一些重構,而且支持了 Vulkan;

          9. 在 Android P 支持了 Vulkun 1.1。

          UI 渲染測量

          • 測試工具:Profile GPU Rendering 和 Show GPU Overdraw。

          • 問題定位工具:Systrace 和 Tracer for OpenGL ES

          • Layout Inspector: AndroidStudio自帶的工具,它的主要作用就是用來查看視圖層級結構的,開啟路徑如下: 點擊Tools工具欄 ->第三欄的Layout Inspector -> 選中當前的進程;

          • Choreographer:用來獲取FPS的,并且可以用于線上使用,具備實時性,但是僅能在Api 16之后使用,具體的調(diào)用代碼如下:

            private long mStartFrameTime = 0;
            private int mFrameCount = 0;

            /**
            * 單次計算FPS使用160毫秒
            */

            private static final long MONITOR_INTERVAL = 160L;
            private static final long MONITOR_INTERVAL_NANOS = MONITOR_INTERVAL * 1000L * 1000L;

            /**
            * 設置計算fps的單位時間間隔1000ms,fps/s
            */

            private static final long MAX_INTERVAL = 1000L;

            @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
            private void getFPS() {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            return;
            }
            Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
            if (mStartFrameTime == 0) {
            mStartFrameTime = frameTimeNanos;
            }
            long interval = frameTimeNanos - mStartFrameTime;
            if (interval > MONITOR_INTERVAL_NANOS) {
            double fps = (((double) (mFrameCount * 1000L * 1000L)) / interval) * MAX_INTERVAL;
            // log輸出fps
            LogUtils.i("當前實時fps值為: " + fps);
            mFrameCount = 0;
            mStartFrameTime = 0;
            } else {
            ++mFrameCount;
            }

            Choreographer.getInstance().postFrameCallback(this);
            }
            });
            }
            • 我們需要排除掉頁面沒有操作的情況,即只在界面存在繪制的時候才做統(tǒng)計。我們可以通過 addOnDrawListener 去監(jiān)聽界面是否存在繪制行為: getWindow().getDecorView().getViewTreeObserver().addOnDrawListener

            • Choreographer.getInstance().postFrameCallback();

            • 使用Choreographer獲取FPS的完整代碼如下

          • 在 Android Studio 3.1 之后,Android 推薦使用Graphics API Debugger(GAPID)來替代 Tracer for OpenGL ES 工具。GAPID 可以說是升級版,它不僅可以跨平臺,而且功能更加強大,支持 Vulkan 與回放。

          • 通過上面的幾個工具,我們可以初步判斷應用 UI 渲染的性能是否達標,例如是否經(jīng)常出現(xiàn)掉幀、掉幀主要發(fā)生在渲染的哪一個階段、是否存在 Overdraw 等。

          • 雖然這些圖形化界面工具非常好用,但是它們難以用在自動化測試場景中,那有哪些測量方法可以用于自動化測量 UI 渲染性能呢?

          1. gfxinfo

          • gfxinfo可以輸出包含各階段發(fā)生的動畫以及幀相關的性能信息,具體命令如下:

            • adb shell dumpsys gfxinfo 包名

          • 除了渲染的性能之外,gfxinfo 還可以拿到渲染相關的內(nèi)存和 View hierarchy 信息。在 Android 6.0 之后,gxfinfo 命令新增了 framestats 參數(shù),可以拿到最近 120 幀每個繪制階段的耗時信息: adb shell dumpsys gfxinfo 包名 framestats

          2. SurfaceFlinger

          • 除了耗時,我們還比較關心渲染使用的內(nèi)存。可以通過下面的命令拿到系統(tǒng) SurfaceFlinger 相關的信息:adb shell dumpsys SurfaceFlinger

          獲取界面布局耗時

          1. 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();
          }
          LogHelper.i(name + " cost " + (System.currentTimeMillis() - time));
          }
          1. LayoutInflaterCompat.setFactory2

          • 上面我們使用了AOP的方式監(jiān)控了Activity的布局加載耗時,那么,如果我們需要監(jiān)控每一個控件的加載耗時,該怎么實現(xiàn)呢?

          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {

          // 使用LayoutInflaterCompat.Factory2全局監(jiān)控Activity界面每一個控件的加載耗時,
          // 也可以做全局的自定義控件替換處理,比如:將TextView全局替換為自定義的TextView。
          LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
          @Override
          public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

          if (TextUtils.equals(name, "TextView")) {
          // 生成自定義TextView
          }
          long time = System.currentTimeMillis();
          // 1
          View view = getDelegate().createView(parent, name, context, attrs);
          LogHelper.i(name + " cost " + (System.currentTimeMillis() - time));
          return view;
          }

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

          // 2、setFactory2方法需在super.onCreate方法前調(diào)用,否則無效
          super.onCreate(savedInstanceState);
          setContentView(getLayoutId());
          unBinder = ButterKnife.bind(this);
          mActivity = this;
          ActivityCollector.getInstance().addActivity(this);
          onViewCreated();
          initToolbar();
          initEventAndData();
          }

          UI優(yōu)化的常用手段

          1. 盡量使用硬件加速

          • 之所以不能使用硬件加速,是因為硬件加速不能支持所有的 Canvas API;

          • 如果使用了不支持的 API,系統(tǒng)就需要通過 CPU 軟件模擬繪制,這也是漸變、磨砂、圓角等效果渲染性能比較低的原因。

          • SVG 也是一個非常典型的例子,SVG 有很多指令硬件加速都不支持。

          • 但我們可以用一個取巧的方法,提前將這些 SVG 轉(zhuǎn)換成 Bitmap 緩存起來,這樣系統(tǒng)就可以更好地使用硬件加速繪制。

          • 同理,對于其他圓角、漸變等場景,我們也可以改為 Bitmap 實現(xiàn)。

          2. Create View 優(yōu)化

          • View 的創(chuàng)建也是在 UI 線程里,對于一些非常復雜的界面,這部分的耗時不容忽視。包括各種 XML 的隨機讀的 I/O 時間、解析 XML 的時間、生成對象的時間(Framework 會大量使用到反射)。

          • 優(yōu)化方式:

            Button button=new Button(this);
            button.setBackgroundColor(Color.RED);
            button.setText("Hello World");
            ViewGroup viewGroup = (ViewGroup) LayoutInflater.from(this).inflate(R.layout.activity_main, null);
            viewGroup.addView(button);
            public static boolean prepareLooperWithMainThreadQueue(boolean reset) {
            if (isMainThread()) {
            return true;
            } else {
            ThreadLocal<Looper> threadLocal = (ThreadLocal) ReflectionHelper.getStaticFieldValue(Looper.class, "sThreadLocal");
            if (threadLocal == null) {
            return false;
            } else {
            Looper looper = null;
            if (!reset) {
            Looper.prepare();
            looper = Looper.myLooper();
            Object queue = ReflectionHelper.invokeMethod(Looper.getMainLooper(), "getQUeue", new Class[0], new Object[0]);
            if (!(queue instanceof MessageQueue)) {
            return false;
            }
            }
            ReflectionHelper.invokeMethod(threadLocal, "set", new Class[]{Object.class}, new Object[]{looper});
            return true;
            }
            }
            }
            // 要注意的是,在創(chuàng)建完 View 后我們需要把線程的 Looper 恢復成原來的。

            private static boolean isMainThread() {
            return Looper.myLooper() == Looper.getMainLooper();
            }
            implementation 'com.android.support:asynclayoutinflater:28.0.0'
            // 內(nèi)部分別使用了IO和反射的方式去加載布局解析器和創(chuàng)建對應的View
            // setContentView(R.layout.activity_main);
            // 使用AsyncLayoutInflater進行布局的加載
            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);
            // findViewById、視圖操作等
            }
            });
            super.onCreate(savedInstanceState);
            • AsyncLayoutInflater是通過側面緩解的方式去緩解布局加載過程中的卡頓,但是它依然存在一些問題:

            • Android AsyncLayoutInflater 限制及改進:

              /**
              * 實現(xiàn)異步加載布局的功能,修改點:
              *
              * 1. super.onCreate之前調(diào)用沒有了默認的Factory
              * 2. 排隊過多的優(yōu)化;
              */

              public class AsyncLayoutInflaterPlus {

              private static final String TAG = "AsyncLayoutInflaterPlus";
              private Handler mHandler;
              private LayoutInflater mInflater;
              private InflateRunnable mInflateRunnable;
              // 真正執(zhí)行加載任務的線程池
              private static ExecutorService sExecutor = Executors.newFixedThreadPool(Math.max(2,
              Runtime.getRuntime().availableProcessors() - 2));
              // InflateRequest pool
              private static Pools.SynchronizedPool<AsyncLayoutInflaterPlus.InflateRequest> sRequestPool = new Pools.SynchronizedPool<>(10);
              private Future<?> future;

              public AsyncLayoutInflaterPlus(@NonNull Context context) {
              mInflater = new AsyncLayoutInflaterPlus.BasicInflater(context);
              mHandler = new Handler(mHandlerCallback);
              }

              @UiThread
              public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent, @NonNull CountDownLatch countDownLatch,
              @NonNull AsyncLayoutInflaterPlus.OnInflateFinishedListener callback)
              {
              if (callback == null) {
              throw new NullPointerException("callback argument may not be null!");
              }
              AsyncLayoutInflaterPlus.InflateRequest request = obtainRequest();
              request.inflater = this;
              request.resid = resid;
              request.parent = parent;
              request.callback = callback;
              request.countDownLatch = countDownLatch;
              mInflateRunnable = new InflateRunnable(request);
              future = sExecutor.submit(mInflateRunnable);
              }

              public void cancel() {
              future.cancel(true);
              }

              /**
              * 判斷這個任務是否已經(jīng)開始執(zhí)行
              *
              * @return
              */

              public boolean isRunning() {
              return mInflateRunnable.isRunning();
              }

              private Handler.Callback mHandlerCallback = new Handler.Callback() {
              @Override
              public boolean handleMessage(Message msg) {
              AsyncLayoutInflaterPlus.InflateRequest request = (AsyncLayoutInflaterPlus.InflateRequest) msg.obj;
              if (request.view == null) {
              request.view = mInflater.inflate(
              request.resid, request.parent, false);
              }
              request.callback.onInflateFinished(
              request.view, request.resid, request.parent);
              request.countDownLatch.countDown();
              releaseRequest(request);
              return true;
              }
              };

              public interface OnInflateFinishedListener {
              void onInflateFinished(View view, int resid, ViewGroup parent);
              }

              private class InflateRunnable implements Runnable {
              private InflateRequest request;
              private boolean isRunning;

              public InflateRunnable(InflateRequest request) {
              this.request = request;
              }

              @Override
              public void run() {
              isRunning = true;
              try {
              request.view = request.inflater.mInflater.inflate(
              request.resid, request.parent, false);
              } catch (RuntimeException ex) {
              // Probably a Looper failure, retry on the UI thread
              Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"
              + " thread", ex);
              }
              Message.obtain(request.inflater.mHandler, 0, request)
              .sendToTarget();
              }

              public boolean isRunning() {
              return isRunning;
              }
              }

              private static class InflateRequest {
              AsyncLayoutInflaterPlus inflater;
              ViewGroup parent;
              int resid;
              View view;
              AsyncLayoutInflaterPlus.OnInflateFinishedListener callback;
              CountDownLatch countDownLatch;

              InflateRequest() {
              }
              }

              private static class BasicInflater extends LayoutInflater {
              private static final String[] sClassPrefixList = {
              "android.widget.",
              "android.webkit.",
              "android.app."
              };

              BasicInflater(Context context) {
              super(context);
              if (context instanceof AppCompatActivity) {
              // 加上這些可以保證AppCompatActivity的情況下,super.onCreate之前
              // 使用AsyncLayoutInflater加載的布局也擁有默認的效果
              AppCompatDelegate appCompatDelegate = ((AppCompatActivity) context).getDelegate();
              if (appCompatDelegate instanceof LayoutInflater.Factory2) {
              LayoutInflaterCompat.setFactory2(this, (LayoutInflater.Factory2) appCompatDelegate);
              }
              }
              }

              @Override
              public LayoutInflater cloneInContext(Context newContext) {
              return new AsyncLayoutInflaterPlus.BasicInflater(newContext);
              }

              @Override
              protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
              for (String prefix : sClassPrefixList) {
              try {
              View view = createView(name, prefix, attrs);
              if (view != null) {
              return view;
              }
              } catch (ClassNotFoundException e) {
              // In this case we want to let the base class take a crack
              // at it.
              }
              }

              return super.onCreateView(name, attrs);
              }
              }

              public AsyncLayoutInflaterPlus.InflateRequest obtainRequest() {
              AsyncLayoutInflaterPlus.InflateRequest obj = sRequestPool.acquire();
              if (obj == null) {
              obj = new AsyncLayoutInflaterPlus.InflateRequest();
              }
              return obj;
              }

              public void releaseRequest(AsyncLayoutInflaterPlus.InflateRequest obj) {
              obj.callback = null;
              obj.inflater = null;
              obj.parent = null;
              obj.resid = 0;
              obj.view = null;
              sRequestPool.release(obj);
              }

              }
            • View 重用:ListView、RecycleView 通過 View 的緩存與重用大大地提升渲染性能。因此我們可以參考它們的思想,實現(xiàn)一套可以在不同 Activity 或者 Fragment 使用的 View 緩存機制。

            • AsynclayoutInflater異步創(chuàng)建View

            • 異步創(chuàng)建:那我們能不能在線程提前創(chuàng)建 View,實現(xiàn) UI 的預加載嗎?可以通過又一個非常取巧的方式來實現(xiàn)。在使用線程創(chuàng)建 UI 的時候,先把線程的 Looper 的 MessageQueue 替換成 UI 線程 Looper 的 Queue。

            • 使用代碼創(chuàng)建;

            1. 不能設置LayoutInflater.Factory,需要通過自定義AsyncLayoutInflater的方式解決,由于它是一個final,所以需要將代碼直接拷處進行修改。

            2. 因為是異步加載,所以需要注意在布局加載過程中不能有依賴于主線程的操作。

          • X2C: 框架保留了XML的優(yōu)點,并解決了其IO操作和反射的性能問題。開發(fā)人員只需要正常寫XML代碼即可,在編譯期,X2C會利用APT工具將XML代碼翻譯為Java代碼。

          3. measure/layout 優(yōu)化

          • 渲染流程中 measure 和 layout 也是需要 CPU 在主線程執(zhí)行的;

          • 優(yōu)化方法:減少 UI 布局層次,優(yōu)化 layout 的開銷,盡量不要重復去設置背景

            • 單層布局:盡量選擇LinearLayout或FrameLayout,而少用 RelativeLayout,應為RelativeLayout功能較復雜,更耗性能;但從程序擴展性的角度看,更傾向于RelativeLayout

            • 多層布局:布局較復雜時,RelativeLayout能夠有效的減少布局層級

            • <include/>標簽:實現(xiàn)布局文件的復用,如app自定義的TitleBar 只支持 layout_xx 和id屬性,當include和被包含布局的根標簽都指定了id時,以include為準;指定layout_xx屬性時, 必須也要指定layout_width和layout_height,否則無法生效

            • <merge/>標簽:在UI的結構優(yōu)化中起著非常重要的作用,它可以刪減多余的層級,優(yōu)化UI。<merge/>多用于替換FrameLayout或者當一個布局包含另一個時,<merge/>標簽消除視圖層次結構中多余的視圖組。例如你的主布局文件是垂直布局,引入了一個垂直布局的include,這是如果include布局使用的LinearLayout就沒意義了, 使用的話反而減慢你的UI表現(xiàn)。這時可以使用<merge/>標簽優(yōu)化。

            • <ViewStub/>標簽:懶加載,不會影響UI初始化時的性能;各種不常用的布局,如進度條、顯示錯誤消息等可以使用ViewStub標簽,以減少內(nèi)存使用量,加快渲染速度

            • 使用 style 來定義通用的屬性,從而重復利用代碼,減少代碼量

            • 封裝組合view實現(xiàn)view復用

            • 使用 LinearLayoutCompat 組件來實現(xiàn)線性布局元素之間的分割線,從而減少了使用View來實現(xiàn)分割線效果

            • 布局優(yōu)化:

          • Litho:異步布局

            1. 配置Litho的相關依賴
            // 項目下
            repositories {
            jcenter()
            }

            // module
            dependencies {
            // ...
            // Litho
            implementation 'com.facebook.litho:litho-core:0.33.0'
            implementation 'com.facebook.litho:litho-widget:0.33.0'

            annotationProcessor 'com.facebook.litho:litho-processor:0.33.0'

            // SoLoader
            implementation 'com.facebook.soloader:soloader:0.5.1'

            // For integration with Fresco
            implementation 'com.facebook.litho:litho-fresco:0.33.0'

            // For testing
            testImplementation 'com.facebook.litho:litho-testing:0.33.0'

            // Sections options,用來聲明去構建一個list
            implementation 'com.facebook.litho:litho-sections-core:0.33.0'
            implementation 'com.facebook.litho:litho-sections-widget:0.33.0'
            compileOnly 'com.facebook.litho:litho-sections-annotations:0.33.0'

            annotationProcessor 'com.facebook.litho:litho-sections-processor:0.33.0'
            }
            2. Application下的onCreate方法中初始化SoLoader
            @Override
            public void onCreate() {
            super.onCreate();
            SoLoader.init(this, false);
            //Litho使用了Yoga進行布局,而Yoga包含有native依賴,在Soloader.init方法中對這些native依賴進行了加載。
            }
            3. ActivityonCreate方法中添加如下代碼即可顯示單個的文本視圖:
            // 1、將ActivityContext對象保存到ComponentContext中,并同時初始化
            // 一個資源解析者實例ResourceResolver供其余組件使用。
            ComponentContext componentContext = new ComponentContext(this);
            // 2、Text內(nèi)部使用建造者模式以實現(xiàn)組件屬性的鏈式調(diào)用,下面設置的text、
            // TextColor等屬性在Litho中被稱為Prop,此概念引申字React。
            Text lithoText = Text.create(componentContext)
            .text("Litho text")
            .textSizeDip(64)
            .textColor(ContextCompat.getColor(this, R.color.light_deep_red))
            .build();
            // 3、設置一個LithoView去展示Text組件:LithoView.create內(nèi)部新建了一個
            // LithoView實例,并用給定的ComponentlithoText)進行初始化
            setContentView(LithoView.create(componentContext, lithoText));
            4. 使用自定義Component
            Litho中的視圖單元叫做Component,即組件,它的設計理念來源于React組件化的思想。
            每個組件持有描述一個視圖單元所必須的屬性與狀態(tài),用于視圖布局的計算工作。
            視圖最終的繪制工作是由組件指定的繪制單元(ViewDrawable)來完成的。
            @LayoutSpec
            public class ListItemSpec {

            @OnCreateLayout
            static Component onCreateLayout(ComponentContext context) {
            // Column的作用類似于HTML中的<div>標簽
            return Column.create(context)
            .paddingDip(YogaEdge.ALL, 16)
            .backgroundColor(Color.WHITE)
            .child(Text.create(context)
            .text("Litho Study")
            .textSizeSp(36)
            .textColor(Color.BLUE)
            .build())
            .child(Text.create(context)
            .text("JsonChao")
            .textSizeSp(24)
            .textColor(Color.MAGENTA)
            .build())
            .build();
            }
            }
            // 2、構建ListItem組件
            ListItem listItem = ListItem.create(componentContext).build();
            • Litho是 Facebook 開源的聲明式 Android UI 渲染框架,它是基于另外一個 Facebook 開源的布局引擎Yoga開發(fā)的。

          • Flutter:自己的布局 + 渲染引擎

          • RenderThread 與 RenderScript

            • Android 5.0,系統(tǒng)增加了 RenderThread,對于 ViewPropertyAnimator 和 CircularReveal 動畫,我們可以使用RenderThead 實現(xiàn)動畫的異步渲染。

          最后歡迎大家加入 音視頻開發(fā)進階 知識星球 ,這里有知識干貨、編程答疑、開發(fā)教程,還有很多精彩分享。


          更多內(nèi)容可以在星球菜單中找到,隨著時間推移,干貨也會越來越多?。?!


          給出 10元 優(yōu)惠券,漲價在即,目前還是白菜價,基本上提幾個問題就回本,投資自己就是最好的投資?。?!


          加我微信 ezglumes ,拉你進技術交流群

          推薦閱讀:

          音視頻開發(fā)工作經(jīng)驗分享 || 視頻版

          OpenGL ES 學習資源分享

          開通專輯 | 細數(shù)那些年寫過的技術文章專輯

          Android NDK 免費視頻在線學習?。。?/a>

          你想要的音視頻開發(fā)資料庫來了

          推薦幾個堪稱教科書級別的 Android 音視頻入門項目

          覺得不錯,點個在看唄~


          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品男人操女人 | 操碰免费 | 俺来俺去www色官网 | 91人人| 91精品婷婷国产综合久久 |