天天看點

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

為了另外一篇性能優化實戰方案講解部落格的結構清晰和篇幅,

我們“斷章取義”,把架構的源碼解析部分搬到這邊哈~

項目GitHub

目錄

1. 監控周期的 定義

2. dump子產品 / 關于.log檔案

3. 采集堆棧周期的 設定

4. 架構的 配置存儲類 以及 檔案系統操作封裝

5. 檔案寫入過程(生成.log檔案的源碼)

6. 上傳檔案

7. 設計模式、技巧

8. 架構中各個主要類的功能劃分

1. 【監控周期的 定義】

blockCanary列印一輪資訊的周期,

是從

主線程一輪阻塞的開始

開始,到

阻塞的結束

結束,為一輪資訊;

這個周期我們也可以成為

BlockCanary

監控周期/監控時間段

2. 【dump子產品 / 關于.log檔案】

這一個周期的資訊,除了展現在

通知

處,還會展示在

logcat

處,

同時架構封裝了

dump子產品

即架構會把我們這一輪資訊,在手機(移動終端)記憶體中,

輸出成一個

.log

檔案;

【當然,前提是在終端需要給這個APP

授權

,允許APP

讀寫記憶體

存放

.log

檔案的目錄名,我們可以在上面提到的

配置類

自定義

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

如這裡定義成

blockcanary

在終端生成的檔案與目錄便是這樣:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

3. 【采集堆棧周期的 設定】

我們說過

配置類

中,這個函數可以指定

認定為卡頓的門檻值時間

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

這裡指定為

500ms

,使得剛剛那個

2s

的阻塞被确定為

卡頓問題

其實還有一個函數,

用于指定在一個

監控周期

内,

采集資料的周期

!!!:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

這裡傳回的同樣是

500ms

即從

線程阻塞

開始,每

500ms

采集一次資料,

給出一個

阻塞問題出現的根源

而剛剛那個

卡頓問題

阻塞的時間是

2s

那毫無疑問我們可以猜到,剛剛那個

.log

檔案的内容裡邊,

2s/500ms = 4

采集的堆棧資訊

!!

但是一個

監控周期/log檔案

隻列印一次

現場的詳細資訊

:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

如果設定為

250ms

,那便是有

2s/250ms = 8

采集的堆棧資訊

了:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)
Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

4. 【架構的 配置存儲類 以及 檔案系統操作封裝】

架構準備了一個存儲配置的類,用于存儲響應的配置:

配置存儲類:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

-

getPath()

:拿到sd卡根目錄到存儲log檔案夾的

目錄路徑

;!!!!!!!!

-

detectedBlockDirectory()

:傳回

file類型

存儲log檔案

檔案夾

目錄

(如果沒有這個檔案夾,就建立檔案夾,再傳回

file類型

的這個檔案夾);!!!!!!!!!!

-

getLogFiles()

如果

detectedBlockDirectory()

傳回的那個

存儲log檔案

檔案夾

目錄

存在的話,

就把這個目錄下所有的

.log

檔案過濾提取出來,

并存儲在一個

File[](即File數組)

裡邊,最後傳回這個

File數組

;!!!!!!!

-

getLogFiles()

中的

listFiles()

是JDK中的方法,

用來傳回

檔案夾類型的File類執行個體

對應檔案夾中(對應目錄下)

所有的檔案,

這裡用的是它的重載方法,

就是傳入一個過濾器,可以過濾掉不需要的檔案;!!!!!!!

-

BlockLogFileFilter

是過濾器,用于過濾出

.log

檔案;

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

###下面稍微實戰一下這個檔案封裝:

呐我們在MainActivity的onCreate中,使用

getLogFiles()

功能是剛說的擷取BlockCanary生成的所有

.log

檔案,以

.log

檔案的形式傳回,

完了我們把它列印出來:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

運作之後,呐,毫無懸念,BlockCanary生成的所有

.log

檔案都被列印出來了:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

拿到了檔案,

意味着我們可以在适當的時機,

将之上傳到伺服器處理!!!

5. 【檔案寫入過程(生成.log檔案的源碼)】

  • 一切要從架構的初始化開始說起:
Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)
  • install()

    做了什麼,

    install()

    裡邊,初始化了BlockCanaryContext和Notification等的一些對象,

    重要的,最後

    return

    調用了,

    get()

    有點單例的味道哈,

    BlockCanary

    的構造方法是私有的(下圖可以見得),

    get()

    正是傳回一個

    BlockCanary

    執行個體,

    當然new這一下也就調用了

    BlockCanary

    的構造方法;

    哦~

    BlockCanary

    的構造方法中,

    調用了

    BlockCanaryInternals.getInstance();

    拿到一個

    BlockCanaryInternals

    執行個體,賦給類中的全局變量!
Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)
Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

BlockCanaryInternals.getInstance();

同樣是使用了單例模式,

傳回一個

BlockCanaryInternals

執行個體:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

同樣也是new時候調用了

BlockCanaryInternals

的構造方法:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

可以看到

BlockCanaryInternals

的構造方法中

出現了關于

配置資訊存儲類

以及

檔案的寫入邏輯

了;

LogWriter.save(blockInfo.toString());

注意這裡傳入的是

配置資訊的字元串

,接着是

LogWriter.save()

,這裡的str便是剛剛的

blockInfo.toString()

,即配置資訊;

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

往下還有一層

save(一參對應剛剛的字元串"looper",二參為Block字元串資訊【最早是來自BlockCanaryInternals中的LogWriter.save(blockInfo.toString());中的 blockInfo.toString() 】)

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

可以看到

.log

檔案名的命名規則的就是定義在這裡了,

.log檔案

寫入的輸入流邏輯,也都在這裡了;

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

對比一下剛剛實驗的結果,也就是實際生成的

.log檔案

檔案名

可見檔案名跟上面

save()

方法中定義好的規則是一樣的,無誤;

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

這兩個在表頭的字元串格式化器,

第一個是用來給

.log檔案

命名的,

.log檔案名

中的時間序列來自這裡;

第二個是在save()函數中,用來寫入檔案的,

用時間來區分堆棧資訊的每一次收集:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

下面這個方法是用來構造zip檔案執行個體的,

給出一個檔案名,再構造一個成對應的File執行個體;

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

這個則是用來删除本架構生成的所有log檔案的:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

其他的很容易看懂,就不多說了;

6.【上傳檔案】

首先架構想得很周到哈,它已經為我們封裝了一個

Uploader

類,源碼如下:

/*
 * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
...
final class Uploader {

    private static final String TAG = "Uploader";
    private static final SimpleDateFormat FORMAT =
            new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);

    private Uploader() {
        throw new InstantiationError("Must not instantiate this class");
    }

    private static File zip() {
        String timeString = Long.toString(System.currentTimeMillis());
        try {
            timeString = FORMAT.format(new Date());
        } catch (Throwable e) {
            Log.e(TAG, "zip: ", e);
        }
        File zippedFile = LogWriter.generateTempZip("BlockCanary-" + timeString);
        BlockCanaryInternals.getContext().zip(BlockCanaryInternals.getLogFiles(), zippedFile);
        LogWriter.deleteAll();
        return zippedFile;
    }

    public static void zipAndUpload() {
        HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
            @Override
            public void run() {
                final File file = zip();
                if (file.exists()) {
                    BlockCanaryInternals.getContext().upload(file);
                }
            }
        });
    }
}           

複制

都封裝成zip檔案了,想得很周到很齊全吼,

點一下這個

upload

,又回到

配置類BlockCanaryContext

這兒來,

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

或者可以參考一下 這篇部落格!!!!!!!,

可以在背景開啟一個線程,定時掃描并上傳。

或者

可以利用一下剛剛提到的 架構的檔案系統操作封裝 ,

再結合 自定義網絡請求邏輯,

把檔案上傳到伺服器也是ok的!

7. 設計模式、技巧:

7.1 單例模式,不用多說,

剛剛提到

BlockCanary

BlockCanaryInternals

裡邊都用到了;

7.2 回調機制設計:

内部接口,供給回調:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

定義内部接口的類,“抽象調用”回調接口方法:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

接口暴露給外部,在外部實作回調:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

8 .架構中各個主要類的功能劃分

-

BlockCanary

提供給外部使用的,負責架構整體的方法排程;整體的、最頂層的排程;

-

BlockCanaryInternals

 封裝控制

周期性采集堆棧資訊

列印、輸入

的關鍵邏輯;

(卡頓判定

門檻值

采集資訊周期

的配置,都在這裡首先被使用)

(注意這裡的

onBlockEvent() 回調方法

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

 封裝檔案操作子產品(建立檔案、建立檔案目錄、擷取相關路徑等等 這些

從SD卡根目錄到存儲

.log

檔案目錄 這個級别的處理,往下的目錄下檔案機關級别的處理,交給

LogWriter

)等核心邏輯;

 調用了

LogWriter.save()

進行log檔案存儲等;

 建立

CpuSampler

StackSample

執行個體,用于協助完成

周期性采集

-

LogWriter

封裝了

檔案流

寫入、處理

等邏輯;

-

LooperMonitor

協助完成

周期性采集

【主要是阻塞任務始末的各種排程,即面向

卡頓門檻值

當然,排程的内容也包括對

周期性采集

啟閉排程

!!!!】;

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

如上,

&

println()

有點像鬧鐘的角色,

它在主線程的任務分發

dispatchMessage前後

分别

被調用一次

它在采集周期

開始的時候

,就記錄下

開始時間

在阻塞

任務完成之後

,會再次被調用,記錄下

結束時間

&

isBlock()

:借助

println()

中記錄的關于

主線程任務分發

開始時間

結束時間

來判斷

阻塞的時間

是不是大于

我們設定

的或者

預設

卡頓判定時間

如果是,調用

notifyBlockEvent()

,間接調用到

回調方法 onBlockEvent()

這個方法上面說了,在

BlockCanaryInternals

的構造器中被具體實作了,

可以調用

LogWriter

最終輸出

.log

檔案;

&

startDump()

stopDump()

我們可以看到在

println()

中還有

startDump()

stopDump()

這兩個方法,

分别也是在

主線程任務分發

開始

結束

時,随着

println()

被調用而被調用;

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

startDump()

stopDump()

的内容正是控制兩個

Sample

類的

啟閉

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

-

CpuSampler

StackSample

同樣負責協助完成

周期性采集

CpuSampler

的邏輯主要是面向

CPU資訊

的處理,而

StackSample

的邏輯主要是對

堆棧資訊

的收集;

他們都繼承自

AbstractSample

首先在上面的源碼我們可以看到,

BlockCanaryInternals

的構造器中,

就分别建立了一個

CpuSampler

(參數正為

采集堆棧資訊周期

屬性)和一個

StackSample

執行個體(參數為

采集堆棧資訊周期

屬性):

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

這個參數一路上走,經過

CpuSampler

的構造器,

最終是在

CpuSampler

的父類

AbstractSampler

中被使用!!!!!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

我們可以看到在

AbstractSampler

中,

AbstractSampler

構造器接收了

采集堆棧資訊周期

屬性,

同時準備了一個Runnable任務單元,

任務run()中做了兩件事,

第一件事是調用抽象方法

doSample()

第二件事是基于這個

采集堆棧周期

屬性這個

Runnable單元

建立一個

循環定時任務

!!!!!!!!!!!!!!!!!!!!!!!!!

即,

這個

Runnable單元

start()

之後,

将會每隔一個

采集周期

,就執行一次

run()

和其中的

doSample()

進行

堆棧資訊

CPU資訊

的周期性采集工作;

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

這便是

BlockCanary

能夠

周期采集堆棧資訊

的根源!!!!!!!!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

那接下來我們可以展開三個點,

解決這個三個疑問點,脈絡就理得差不多了:

【1. 由哪個

Handler

來處理這個

Runnable

我們知道,

Android中的

多線程任務單元

可以由一個

Handler

去post或者postDelayed一個

Runnable

來開啟;

而這裡處理這個

Runnable

Handler

正是

HandlerThreadFactory.getTimerThreadHandler()

HandlerThreadFactory

是架構的提供的一個内部線程類,

源碼解析如下,使用了工廠模式吼:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

如此便可以獲得,綁定了

工作線程(子線程)的Looper

Handler

有了這個

Handler

就可以處理剛剛說的

Runnable

任務單元了;

【2.

Handler

Runnable任務單元

啟閉

是在哪個地方?】

當然是在

AbstractSampler

提供的

start()

stop()

裡邊了;

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

CpuSampler

StackSample

都會繼承自

AbstractSampler

,自然也就繼承了這

start()

stop()

最後上面講過了,

LooperMonitor

println()

中,

startDump()

stopDump()

會被調用,

而在

startDump()

stopDump()

中,

CpuSampler

StackSample

執行個體的

start()

stop()

也會被調用了,

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

進而控制了

周期采集資訊

工作線程(子線程)任務單元

【上述的

Runnable執行個體

】的

啟閉

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

【3.

doSample()

的實作】

CpuSampler

StackSample

都繼承自

AbstractSample

使用的都是從

AbstractSample

繼承過來的

Runnable執行個體

前面說過這個

Runnable單元

start()

之後,

将會每隔一個

采集周期

,就執行一次

run()

和其中的

doSample()

進行

堆棧資訊

CPU資訊

的周期性采集工作;

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

是這樣的,

然後

CpuSampler

StackSample

通過對父類抽象方法

doSample()

做了不同的實作,

使得各自循環處理的任務内容不同罷了;

CpuSampler

的面向

CPU資訊

的處理,

StackSample

則對

堆棧資訊

的收集;】

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)
Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)
Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)

-

BlockCanaryContext

架構配置類的超類,提供給外部進行內建和配置資訊:

Android卡頓優化 | AndroidPerformanceMonitor(BlockCanary)源碼詳析(真的很詳細哦!)