2020年10月29日 星期四

[Android] Activity Layout 層級與 DecorView

markdown 我們為 activity 編寫的 layout 層級都在 android.id.content 這個 Android 內建的 layout id 之下,那 android.id.content 是不是 Activity 的 頂層 view 了呢?其實並不是的,它只是我們編寫的 layout 的 parent view 而已,當我們呼叫 [setContentView](https://developer.android.com/reference/android/app/Activity#setContentView(int)) 的時候,API 內部就將傳入的參數 layout id 放在 android.id.content 之下。 實際上 activity 的頂層 view 是一個叫 Decorview 的 FrameLayout,可以通過 API 取得。
protected void onCreate(Bundle savedInstanceState) {
    View decorView = getWindoe().getDecorView();
}
Decorview 會在 Activity initialize 的時候被創建出來,不過它也不是被 Activity 直接持有,而是 Activity 的 Window 成員持有 Decorview,看下面這張圖會比較清楚它們之間的關係。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjhrQ4YxP_wJRxZjtCCW_pFg2Mxmap7vF2SvD9p5_f29SDRjMzbWkFWAzX5HPJ5fvPwLdjhM4wS0CjoKkKit717nkn8Gfm2jJy3gwlHpIiSKAP87XFkj0kDw2RIEWb6BKDZMF6bTyWM5V5/s0/1.png) 雖然近來 Android 不斷提倡將 layout 層級扁平化以改善效能,但是你可以看到,就算我們寫的 layout 沒有任何嵌套,它也已經是第三層了,上面還有 android.id.content 和 Decorview 呢。 關於減少 layout 嵌套層級,在這裡順便提到使用 Fragment 時的一個小技巧,網路上很多範例,會在 activity layout xml 中放一個 FrameLayout 當成 fragment container。
FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
然後在 activity 中將 fragment 放入 container。
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState?) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);

        HeadlinesFragment firstFragment = new HeadlinesFragment();
        // Add the fragment to the 'fragment_container' FrameLayout
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, firstFragment).commit();
    }
}
其實並不需要自己再建立一個 FrameLayout 當作 container,android.id.content 已經是一個 FrameLayout,直接將 fragment 放到 android.id.content 就好,這樣可以減少一層 layout 嵌套。
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState?) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);

        HeadlinesFragment firstFragment = new HeadlinesFragment();
        // Add the fragment to the 'fragment_container' FrameLayout
        getSupportFragmentManager().beginTransaction()
                .add(android.id.content, firstFragment).commit();
    }
}
--- * [Android: Full Screen UI with Transparent Status Bar](https://proandroiddev.com/android-full-screen-ui-with-transparent-status-bar-ef52f3adde63) * [Activity 原理剖析 ](https://juejin.im/post/6844903978057072648) * [android.R.id.content as container for Fragment](https://stackoverflow.com/questions/24712227/android-r-id-content-as-container-for-fragment)

2020年9月27日 星期日

[Android] 動畫簡介

markdown 簡單介紹一下 Android 上面的動畫,主要分為兩類,第一種是 Android 一開始提供的 [View Animation](https://developer.android.com/guide/topics/resources/animation-resource#View),第二種是 Android 3.0 之後提供的 [Property Animation](https://developer.android.com/guide/topics/resources/animation-resource#Property)。 ## View Animation View Animation 又細分為兩種,Tween animation 跟 Frame animation。 大多數情況我們會使用 Tween animation,這種動畫可以用來做旋轉、淡入淡出、移動以及縮放,而且可以定義在 xml 中。 Frame animation 就是通過顯示一張張圖片而成的動畫,就像播放影片一樣,也可以定義再 xml 中,不過這種方式使用情境較少。 View animation 的 class 定義在 [android.view.animation](https://developer.android.com/reference/android/view/animation/package-summary) package 中,主要的 class 有 * [AlphaAnimation](https://developer.android.com/reference/android/view/animation/AlphaAnimation) * [AnimationSet](https://developer.android.com/reference/android/view/animation/AnimationSet) * [RotateAnimation](https://developer.android.com/reference/android/view/animation/RotateAnimation) * [ScaleAnimation](https://developer.android.com/reference/android/view/animation/ScaleAnimation) * [TranslateAnimation](https://developer.android.com/reference/android/view/animation/TranslateAnimation) 那既然 Android 已經支持了這些動畫功能,為什麼要在之後引入 Property Animation 呢?一個是因為 View Animation 如同它的名字一樣,只能作用在 View 上面,假如我們有一個 custom view,而它的繪製是基於某個物件,我們希望實現的動畫是去操作這個物件讓 custom view 重繪,這時 View Animation 就派不上用場。 另外就是 View Animation 的動畫顯示的改變沒有實際作用到 View 上面,例如 View Animation 的動畫把一個左側的 Button 平滑移動到右側,此時你去點擊右側的 Button,是沒有作用的,Button 實際上還在左側,只是在畫面上將它繪製在右側而已。 當然可能還有其它各種 View Animation 不好發揮用處的場景,於是後來 Android 推出了 Property Animation。 ## Property Animation Property Animation 如同名字一樣,在動畫中做的改變會實際影響到 View 的 property 例如位置,大小等等。 核心的 class 有 * [ObjectAnimator](https://developer.android.com/reference/android/animation/ObjectAnimator) * [ValueAnimator](https://developer.android.com/reference/android/animation/ValueAnimator) 可以看到,跟 View animation 相比,Property Animation 的 class 是以 animator 結尾,這可以讓人比較好一眼就分辨出現在在使用的是 View Animation 還是 Property Animation。 ValueAnimator 的使用,下面有一個簡單範例。
ValueAnimator anim = ValueAnimator.ofInt(1, 10);
anim.setDuration(1000);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int v = (int) animation.getAnimatedValue();
        // Redraw the view according to the value...
    }
});
anim.start();
ValueAnimator 的最簡單用法,就是讓你指定一個的初始值跟結束值,還有時間長度,然後當 Animator 開始後,就會不斷呼叫你實作的 listener,讓你可以重繪 View 來達成動畫的效果。 不過 ValueAnimator 還要自己手刻重繪製的動作,有點麻煩,另一個 ObjectAnimator 可以更簡單的實現動畫效果,看下面的範例。
ObjectAnimator animator = ObjectAnimator.ofInt(button, "width", 200, 300);
animator.setDuration(1000);
animator.start();
ObjectAnimator 是繼承 ValueAnimator 的,所以像 ofInt 跟 ofFloat 這類的 函式,ObjectAnimator 也是有的,但跟 ValueAnimator 不一樣的是,ObjectAnimator 可以讓你傳入任意的 View 還有你想操作的 View 屬性, 讓 ObjectAnimator 自動幫你設定屬性。例如上面的範例,呼叫 start 後,就會看到為時 1 秒的動畫顯示 button 的 width 從 200 變成 300。 這些需要提一下的是,ObjectAnimator 不是去看 button 物件有沒有 width 這個屬性,而是去看 button 有沒有 setWidth、getWidth 方法,所以,只要你想操作的值在物件中有對應的 set、get 方法,就都可以使用 ObjectAnimator。 這些 ValueAnimator 跟 ObjectAnimator 的基本用法,可以了解 Property Animation 的概念,也可以實現很多簡單的動畫了,進階的用法,就等需要用到時再去研究。 --- * [属性动画 ValueAnimator 运行原理全解析](https://www.jianshu.com/p/46f48f1b98a9) * [Android属性动画完全解析(上),初识属性动画的基本用法](https://blog.csdn.net/sinyu890807/article/details/43536355)

2020年8月26日 星期三

[Image Processing] Video aspect ratio

markdown 因為工作的關係接觸到了影片的、PAR、SAR、DAR 專有名詞,其實這些專有名詞本身的概念並不難懂,但是因為縮寫的關係,不同的意義卻有同樣的縮寫,例如 Sample Aspect Ratio 跟 Storage Aspect Ratio 縮寫都是 SAR,但這兩個卻是不同的東西,所以容易讓人感到 confuse,下面就簡單介紹一下這些名詞的意思。 ### Storage Aspect Ratio 這個名詞表示影片畫面的原始解析度,例如 HD 影片解析度為 1280 x 720,Storage Aspect Ratio 就會是 1280:720。 ### Display Aspect Ratio 這個名詞表示這個影片應該以甚麼比例播放出來,例如高畫質影片的 16:9 或是 SD 的 4:3。 ### Pixel Aspect Ratio 這個是在說影片的一個 pixel 的長寬比是多少,pixel 的比例在現在的電腦螢幕或 LCD 電視上都是 1:1 沒錯,但是在早期映像管電視的時代就不一定是這樣了,從映像管電視的歷史因素到導致有 Pixel Aspect Ratio 的過程,可以到下方的參考資料連結詳閱。簡單來說,例如 720 x 576 的 SD 影片,為了要在播放時呈現 4:3 的比例,pixel 比例就應該是 59:54,這樣實際畫面就會是 PAR x SAR = 720x59:576x54 = 4:3。 基本上,這三個概念就是全部了,而這三者間的關係就如上面所說 PAR x SAR = DAR。那下面要再介紹兩個名詞,讓你知道為什麼這些看似基本的概念會很讓人疑惑。 ### Sample Aspect Ratio 這個東西基本上其實意義跟 Pixel Aspect Ratio 是一樣的,是包在 MPEG4 container 內的影片 metadata 資訊,可以看到,這個名詞的縮寫是 SAR,但是跟 Storage Aspect Ratio 沒有半毛錢關係。 ### Picture Aspect Ratio 這個東西意思跟 Storage Aspect Ratio 一樣,但是你看,它的縮寫是 PAR =.=。 網路上解釋這三個概念的文章是很多,但是,如果都是用縮寫,你看某篇文章講的 PAR 是 Pixel Aspect Ratio,但是另一篇講的是 Picture Aspect Ratio,你就會被搞混了。 這時就想到[馬斯克講的話](https://www.inside.com.tw/article/12614-elon-musk-productivity-tips-for-tesla-employees),不要用縮寫簡稱,真的精闢,因為溝通誤解造成的成本真的很巨大。 --- * [PAR, SAR, and DAR: Making Sense of Standard Definition (SD) video pixels](https://bavc.org/blog/par-sar-and-dar-making-sense-standard-definition-sd-video-pixels#fn6) * [Advanced Aspect Ratios - PAR, DAR and SAR](https://www.animemusicvideos.org/guides/avtech3/theory-videoaspectratios.html) * [Aspect Ratios](https://www.animemusicvideos.org/guides/avtech31/theory-videoaspectratios.html) * [Aspect Ratio – Understanding the Information and Using the Filter](https://blog.ampedsoftware.com/2016/03/17/aspect-ratio-understanding-the-information-and-using-the-filter/) * [Video aspect ratios](https://riptutorial.com/video/topic/5713/video-aspect-ratios) * [Introduction to Aspect Ratio](http://blog.ampedsoftware.com/2016/03/14/introduction-to-aspect-ratio/)

2020年6月25日 星期四

[Android] RecyclerView load 耗時造成動畫丟幀

markdown 又是 RecyclerView 造成的問題分析,這次問題的情境是,在進入一個 fragment 時會帶有一些動畫效果同時載入一個 RecyclerView,但是動畫會發生 junk。 因為這個 fragment 中一些比較 heavy 的 IO 操作都被移到 background thread 中去做了,所以一開始真的想不到是甚麼原因造成的,code review 了很久始終沒有頭緒,只好又請出 systrace 這神器來檢查。然後才發現,原來兇手是 RecyclerView。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy8qqyLsUxRX1pdMilL3aV2ccGN4FUPZg4wtYPtQffP-rDtXKykiheyn81jgzy9smuq6_c6-iFv5BfDvJ8yBvuHAApSud86qwi5tHzwscLqi5i_KBENjNi0uypMMKOYqmr1Ll80SXkJShu/s1600/Image+1.png =900x*) 一查才知道,原來 RecyclerView Load 花了這麼久的時間,圖中顯示一目了然,左上方紅色的 F 表示這個 Frame 被 delay 了,就是被 RecyclerView 拖累到,所以動畫會有 lag 的感覺。 後來想辦法要縮減 RecyclerView 的載入時間,想說最花時間的應該是 layout inflate,就把 inflate 改成在 background thread 去做,甚至用 custom view 就為了減少 view measure 時間,但是雖然症狀有減輕,但是還是會有 lag 的感覺,達不到預想中滑順的動畫效果。 隨著查了越來越多資料,發現動畫顯示實際上是一個非常嚴苛的條件,想一下,動畫中每個 frame 每 16ms 要顯示一次,你的 RecyclerView 載入要壓在 16ms 之內,而即使已經用了上述技巧減少每個 item load 的時間,但有很多時間是花在 RecyclerView 內部的運作,這時你再怎麼減少 item 的 onCreateViewHolder 跟 onBindViewHolder 也無濟於事。 最後面用了一個折衷的辦法,先讓動畫執行完,再來做載入 RecyclerView,這樣可以保證動畫流暢的執行完。當然,這是因為目前對 RecyclerView 還不夠了解,若是之後對 RecyclerView 了解的足夠深入,有能力改寫它裡面對 item 的載入流程,也許是可以將載入壓到 16ms 的。 --- * [The battle for jank*-less UI on Android](https://medium.com/swlh/the-battle-for-junk-less-ui-713d7680aebc) * [Developing for Android, III: The Rules: Performance](https://medium.com/google-developers/developing-for-android-iii-2efc140167fd)

2020年5月24日 星期日

Windows 下 YouCompleteMe 安裝步驟

markdown [YouCompleteMe](https://github.com/ycm-core/YouCompleteMe) 是一個非常強大的 vim 套件,在各個工作環境中也安裝好幾次了,不過安裝過程有點複雜,每次都要重新看一次文件才知道要怎麼裝,這裡寫一下簡略的步驟,讓以後要在新環境安裝時可以很快的喚起記憶。 使用的平台是 Windows,搭配的 vim 也是 Windows 版的 gvim,因為工作環境都是在 Windows 中。 ## 第一步:安裝 YouCompleteMe 編譯所需套件 因為 YouCompleteMe 沒有提供預先編譯好的執行檔,需要下載它的程式碼在你本機電腦編譯,所以要先安裝好編譯所需的套件。安裝步驟以及所需套件在 YouCompleteMe 的 [Github](https://github.com/ycm-core/YouCompleteMe#windows) 官網就有說明清楚。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3DrprbjbCK_VlRykmsvglAJftxzUgoE4yBreR7t-zuYo_b6LkJyvyxPbVDZJj0H0hXEhMBg0nu34_GULe9A8PXv_TiKuFlflQbFsj1tDmx_pLM5n7TiGnl8VHc2RCx9rzK0tBEd4KfXNk/s1600/Image+2.png) 安裝 python 時要注意,如果安裝的 gvim 是 32 位元的,python 也要安裝 32 位元的版本,否則 gvim 無法載入 python dll。當然 gvim 本身也要安裝支援 python 的版本,可以用 `gvim --version` 來檢查,是否有 +python/dyn +python3/dyn 的 feature。 想知道 gvim 有沒有成功載入 python dll,可以用 command `:py3 pass` 來檢查,如果顯示無法載入,可能是因為 gvim 找不到 python dll。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY8VOvSd8mja1IExn90m3GCXGCc-ID_BQ_AjfOmRUat6lUlXjUyOAnthDh2W0OJupxOhe3eRdeH0JcjhYJSTGb-BY4tsTxmWnP4aj80yX-dSHBnxt1_LkYLzlVUUMWJ60DJx-FsoyLwvb3/s1600/Image+1.png) 這時可以用 pythonthreedll 跟 pythonthreehome 來設定 python dll 位置讓 gvim 可以找到。 ## 第二步:安裝 Vundle [Vundle](https://github.com/VundleVim/Vundle.Vim) 是一個 vim 的套件管理員,需將 Vundle 安裝好之後,再用 Vundle 把 YouCompleteMe 原始碼抓下來。 安裝步驟就照著 [Vundle 官網](https://github.com/VundleVim/Vundle.vim/wiki/Vundle-for-Windows) 的說明,其實主要就是將 Vundle.vim 用 `git clone` 到 vim 的設定檔目錄里。 當然,跟 linux 上的 ~/.vim 不一樣,Windows 的 gvim 設定檔目錄通常是在
C:\Program Files (x86)\Vim\vimfiles
官網給的範例是用 .vim 路徑,下載時要注意一下路徑的不同。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEik2L9CBR_q7InxSZrAEs04lufKg4FyaF3WjxnU2-_T028Ph1PH30SINw_bF_dWkeo0AJoi3KzliXaRoC7MLPgzjnuz_YyYsmhcbr7PG8-FGJNLlmbvd3uegPzTIKdHBeafLtNzZ96rEfVY/s1600/Image+3.png) ## 第三步:下載 YouCompleteMe Vundle.vim 裝好後就是照著[官網範例](https://github.com/VundleVim/Vundle.Vim#quick-start)將你想裝的 Plugin,包含 YouCompleteMe 寫到 _vimrc 設定檔中,然後執行 `:PluginInstall`,接著就等 Vundle 將 YouCompleteMe 原始碼抓下來了。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinAvtAUHOBFwAYhvkLfdDlxv9HWboT_Ex7lBZJn7UDIuPsb9RKPgiqP0WaBSrsw0QwAK3hAWCRugHNoevAOq6vZNj2qKOrhttjGJoD0FqvAozFADFWK_pgXd2oqqfHdFzabJMEUvIcG5Nz/s1600/Image+6.png) 安裝過程中我出現了無法存取 temp 資料夾下檔案的問題,結果是因為權限不足,用管理者權限開啟 gvim 後執行 `:PluginInstall` 就可以了。 ## 第四步:編譯 YouCompleteMe 原始碼 因為在第一步已經將 YouCompleteMe 編譯所需套件安裝好,所以 Vundle 將 YouCompleteMe 原始碼抓下來後,在 YouCompleteMe 目錄下執行 `install.py --all`,就會開始編譯 YouCompleteMe 了,--all 選項是將 YouCompleteMe 對各種語言的自動完成都打開,例如 C/C++ 或 java 等等。 當然,不一定你套件裝完然後 `install.py --all` 跑下去就可以直接開始編譯沒問題了,例如我裝的最新版 cmake 要搭配 Visual Studio 2019 的 build tool,不能用 YouCompleteMe 官網上寫的 Visual Studio 2017,不過如果有問題,通常都會顯示錯誤訊息,這時就只能照著錯誤訊息的提示一步一步解決,搭建好你的編譯環境。 等到 YouCompleteMe 編譯完成,沒意外的話,就可以開始使用,享受它強大的自動補完功能了。
## 一些常見問題 ### Unable to detect a .tern-project file vim 開起來時可能會發現顯示 Unable to detect a .tern-project file 的 warning,只要照[官網](https://github.com/ycm-core/YouCompleteMe#javascript-and-typescript-semantic-completion)的指示,將 `third_party/ycmd/third_party/tern_runtime/node_module` folder 刪除即可 ### 安裝 NERDTree 後發現 vim 結束時 YCM daemon 沒有跟著結束 YCM daemon 基本上就是一個 python process,沒有正常跟著 vim 結束的話,系統中會看到很多孤立的 python.exe 占用系統資源,經過 debug 發現 YCM 註冊一個 handler 到 vim 的 VimLeave 中,當 VimLeave trigger 時執行該 event 然後結束 YCM。但是安裝 NERDTree 後會發現 VimLeave 沒有被呼叫了。 確切來說是 NERDTree README 的一個 autocmd 造成 VimLeave 失效,這個 autocmd 會讓 NERDTree 是最後一個 vim 視窗時結束 vim,我想應該每個裝 NERDTree 的人都會用才對。
autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif
在 github 有人提出 [issue](https://github.com/preservim/nerdtree/issues/1108),只要在加個 nested 就可以解了。
autocmd bufenter * nested 
    \ if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif

2020年5月3日 星期日

[Android] system tracing

markdown systrace 是一個用來量測 Android 系統層級執行時間的 command line tool,位置在 android-sdk/platform-tools/systrace/systrace.py,是一個 python script。它的一些參數可以看[官方文件](https://developer.android.com/topic/performance/tracing/command-line)的說明。 在講 systrace 的用法之前,先來講一下為什麼它可以收集 Android 系統的執行時間,Android 本身會在很多重要的函數前後插入 Label,用這個資訊讓 systrace 可以知道某個函數執行了多久。所謂插入 Label,其實就是呼叫 [TraceCompat](https://developer.android.com/reference/android/support/v4/os/TraceCompat) 的 beginSection 和 endSection。例如在 RecyclerView 的 onLayout 就有插入 Label。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibRjV9ccicC5xhih9wBTdxMnDKqq73mg38BG6AOVv5Mrlc6bE4JCggMTFcgaCnHfVl8JMiVpUWt_grolqxF3dnXxctDSb3isDC12L-ZNN4gBzsD1ChuN2bwz-p5A27z1Oe-586ff037nw3/s1600/Image+2.png) 這樣的 Label 被插入到 Android 系統的各個關鍵 call path 中,讓你可以知道你的 app 在這些系統函數到底花了多久時間。 執行 systrace 時會喂給它時間參數,表示你希望它收集多久的資料,然後是各個模組名稱,表示你希望它收集哪些模組的資料。還有你自己 app 的 package name,這樣 systrace 也會抓你自己在 app code 中插入的 Label,例如下面是我用來 debug UI 卡頓時常用的命令。
systrace.py -t 3 sched gfx view -a myapp.package.name --no-compress
systrace 執行完之後,就會在目錄中產生一個 trace.html 檔案,這個檔案可以用 chrome 開啟,用圖形化的界面顯示執行時間,如下圖,你可以用 WASD 按鍵做移動還有放大跟縮小 timeline。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnOkhF7X1mkYZ5Cr5Qw8UxuPo3cp6d4PT48hyphenhyphenz9odjEj7penLMDnCukpVxoAJgYG182-Y03XD7OlUu5ExZfdivVGJz-6rlyC94E2fVkFyKVZepgiiZId_fJQ4kxJczHO4KVF4_Rul5Wv0u/s1600/Image+1.png) 跟 method trace 不同,systrace 只記錄某些關鍵 call path 的執行時間,不過這也正好是他的優點,因為 method trace 記錄的 method 太多,除非有某個函數真的明顯跟其他函數比起來花了太多時間,不然在調查效能瓶頸時很容易被淹沒在函數海中,找不到該優化的地方。用 systrace 可以先從大方向找出在哪些系統函式比較耗時,接著再用 method trace 做深入追查,這樣會比較有效率。 --- * [Overview of system tracing](https://developer.android.com/topic/performance/tracing/)

2020年3月21日 星期六

[Android] 一次 RecyclerView 效能分析

markdown 最近遇到了一點效能問題,所以開始研究了一下要如何增進 app 效能,那要改善效能,首先要知道 app 的 bottleneck 到底是甚麼,最經典的方法就是用 profiler 看一下效能都花到哪去了。 圖形化的 profiler 最方便直觀,早期都是推薦用 [Traceview](https://developer.android.com/studio/profile/traceview),但是現在已經被 Google 列為不建議使用的工具,因為 Android Studio 在 3.0 之後推出了 [Android Profiler](https://developer.android.com/studio/profile/android-profiler) 可以讓你來觀察 CPU 與 memory 的使用量,因為我是遇到畫面 lag 的問題,就用裡面的 CPU profiler 研究了一下看 CPU 花在哪個 method 里。 在用 profiler 之前之前,其實我預期會看到的是 [setText](https://developer.android.com/reference/android/widget/TextView#setText(java.lang.CharSequence)) 這個 method 吃掉了 CPU resource,因為我有一個 RecyclerView 會頻繁刷新並且呼叫 setText。而之前曾經在 RecyclerView 最佳化的文章中看到過 setText 是很耗時的。 的確在每次畫面刷新的時候就看到 CPU 瞬間飆起來一下,但是從 Call Chart 看到了一堆 method,但都是 Android 系統的函式,看不到 setText 的耗時,後來自己在程式里計算 setText 的時間,發現才不到 1ms。耗時這麼少,被淹沒在 call stack 茫茫大海中,難怪 Call Chart 中找不到,而且也不會是 lag 的原因。後來把 TextView 的 layout class name 印出來看,發現使用了 BoringLayout,所以 setText 的耗時非常短。 後來無意中發現,當我呼叫 notifyDataSetChanged 時,onCreateViewHolder 被呼叫了很多次,這跟我原本對 RecyclerView 的認知有出入,在我原本認知下,應該只有 onBindViewHolder 會頻繁呼叫到才對,然後看了原始碼才發現,原來 RecyclerView 預設 view pool 只會暫存 5 個 item。
public static class RecycledViewPool {
    private ArrayList<ViewHolder>[] mScrap;
    private int[] mMaxScrap;
    private static final int DEFAULT_MAX_SCRAP = 5;
    // Ignore......
}
我的 list item 有 30 幾個,所以會頻繁的呼叫到 onCreateViewHolder。 看來我的 RecyclerView 有一些不必要的耗時操作,接下來就是要對 RecyclerView 作一些優化了,首先記錄一下沒優化的前的 CPU 使用率,如下圖,可以看到稍微有點高了。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlJ-h1DXYl6FIXK2bSB4q4tz1xntgF4yzjKwvDcqh1w3nyKx_tBEFZcm2pCS2SDLkoC_ddH8y8QSOiQuh8y4ZxcJtQoJn7ByOGGXAGCL5mc9v9MhzpyhtAn4Hn0vyjDAdSzk440dLyiMez/s1600/no_optimize_real.png) create view 是蠻耗時的操作,將 view pool 的 cache 數量設成 36 之後 觀察 CPU 使用率如下。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgJ12RtxDTTUC-Cjs6MRoPxff9B_4XdBmvaexscZxfwXXsEmkeyG68OlUKASqQO89k0rfzxCOaQY9kLOpeppj-d_tTFFuo6TY2u6sjfRU7Bu5He1nZ8olvp3R6evHdpI9aRL3lUsPvFlq_/s1600/view_pool_real.png) 之前 RecyclerView 最佳化的文章中還有看到 DiffUtil 這個工具類,可以進一步減少 CPU 耗時,使用後 CPU 使用率如下。 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF-ZxJ9MgLQBDdey5NBxGHHsf7vtanX9f_TEdR1Fk5B_gnBIE3Ge17cED7yzqPmHaTcxdPLQEP57etV92sLpUIowdHrRZS7V8yc5oTMHsGeJGZW-TaesXX9AQQIxA69ULTFOAx304eF4_f/s1600/diffutil.png) 從圖中可以看到,CPU 使用率降低了,優化是有效果的,之後使用 RecyclerView 時可以考慮一下用這些優化方法。 --- * [Android - 性能优化方案分享](https://cloud.tencent.com/developer/article/1415758) * [Android性能优化之CPU Profiler](https://juejin.im/entry/5c0daf65f265da6150644a1d)