曉鋒,曾在PPTV工作,餓了麼資深Android工程師,專注於Android單元測試、架構設計、效能優化、以及最新技術分享,個人部落格:michaelzhong。
注:本篇是《Android魔鏡:方法耗時統計外掛Mirror》系列部落格的第一篇,後面我們會持續更新,歡迎關注!
1 前言
1.1 發生背景
有一天,Boss跑過來說,下次迭代我們要做蜂鳥團隊App
效能調優。對於一個大型成熟的App應用,在業務穩定後,往往會更加關注效能相關的表現。那麼,Android App
的效能調優該從什麼地方入手呢?在進行效能調優、減少應用卡頓過程中,找出問題——耗時嚴重的程式碼,是一個不可或缺且非常重要的步驟,才能有的放矢對症下藥。如何發現應用中的耗時任務甚至是耗時函式呢,如果想依靠開發人員通過review程式碼來找出問題多少有點不太現實,要是可以在日誌中或者報表中羅列出每個方法的執行時間,絕對是一個高效便捷的功能,對耗時方法一目瞭然。所以,Mirror
工具就應運而生。Mirror
取自“魔鏡”,意為“魔鏡!魔鏡!照出所有妖魔鬼怪!”
1.2 傳統方式
在此之前,要統計一個函式的執行時間,可能我們大多數同學都是這麼做的:在方法呼叫的前後,手動編寫耗時統計程式碼,如下:
long start = SystemClock.elapsedRealtime();
// 目標方法
doingSomeThing();
Log.d(TAG, "doingSomeThing()[" + (SystemClock.elapsedRealtime() - start) + "ms]");
複製程式碼
如果統計一兩個方法的執行時間,完全可以應付的過來。但是如果方法比較多,怎麼辦,不可能在每個方法前後都寫這樣冗餘的程式碼吧。一旦接入Mirror,就可以輕而易舉地幫我們完成冗餘繁瑣的工作。Mirror是一個Android Studio Gradle外掛,在編譯時,通過AOP位元組碼插入的方式對每一個方法插入方法耗時統計程式碼。
1.3 對比Hugo
此處,可能有同學會問:Hugo工具也可以做到方法耗時的統計,為什麼要用Mirror呢?在解答之前,先簡單介紹一下Hugo工具。Hugo是國外大佬JakeWharton開發的,通過註解宣告的方式,統計函式的執行時間。還是以上面的例子來講,匯入依賴後,直接在doingSomeThing
方法上新增@DebugLog
註解即可,如下:
@DebugLog
private void doingSomeThing() {
......
}
複製程式碼
所謂成也蕭何,敗也蕭何!通過註解宣告的方式,統計一兩個方法耗時倒也方便,要是統計所有方法耗時就無法勝任,不可能註解滿天飛吧。再者,註解宣告要是多了,程式碼編譯的效率也就降低。因此,Hugo工具只適合有針對性地統計少數方法的耗時。
2 Gradle外掛
在開發實現Mirror
工具中,涉及到Gradle外掛開發和Aop位元組碼插入,我們將以上下兩篇部落格的形式來講解,本篇重點是Gradle外掛開發。
Mirror
是在編譯時藉助於Gradle 外掛,利用Aop位元組碼插入技術,從而幫助我們可以自動地往每個方法插入方法耗時統計的程式碼。不同於Eclipse,Android Studio開發工具為我們提供了Gradle Plugin方式,可以自定義Task在編譯時期完成我們制定好的任務。目前,我們經常用的ButterKnife、GreenDao工具都用到了Gradle外掛。自定義Gradle外掛,是Android開發人員不可缺少的一項技能,顯得特別基礎重要,是時候要學習一波了。
基於Mirror為例子,帶領大家自定義Gradle外掛
2.1 新建Project
如圖新建一個MirrorDemo工程,如果是在原有的Project上開發,這一步就可以跳過。
2.2 新建Module
在Project裡新建一個Module,這裡取名為"plugin",如圖。這個Module用於開發Gradle外掛,同樣Module裡面並沒有Gradle Plugin給你選,但是我們只是需要一個“容器”來容納我們寫的外掛。因此,你可以隨便選擇一個Module型別(如Phone、Tablet Module、Android Library),因為在下一步我們是將裡面的大部分內容刪除,所以選擇哪個型別的Module不重要。
2.3 刪除其他配置
將剛才新建的Module中把內容刪除,只保留build.gradle
檔案和src/main
目錄。
2.4 新建groovy目錄
由於Gradle是基於groovy語言,因此我們開發的Gradle外掛相當於一個groovy專案。所以,需要在main目錄下新建groovy目錄。
2.5 配置build.gradle
配置Module編譯環境,刪除build.gradle原有配置,匯入Plugin的依賴配置:
apply plugin: 'groovy'
dependencies {
compile gradleApi() //gradle sdk
compile localGroovy() //groovy sdk
compile 'com.android.tools.build:gradle:2.3.0'
}
repositories {
jcenter()
}
複製程式碼
2.6 配置Maven
為了方便管理和引用,就要把外掛打包釋出到Maven倉庫裡。可以選擇打包到本地,或者是遠端伺服器中。在build.gradle新增如下配置:
apply plugin: 'maven'
def mirror_version = "1.0.0"
uploadArchives {
repositories.mavenDeployer {
repository(url: uri('../repo'))
pom.groupId = 'me.ele'
pom.artifactId = 'mirror-plugin'
pom.version = "$mirror_version"
}
}
複製程式碼
2.7 建立MirrorPlugin
groovy是基於Java,因此接下來建立groovy的過程跟建立java很類似。在groovy新建包名,如:me.ele.mirror
,然後在該包下新建groovy檔案,通過new->file->MirrorPlugin.groovy
來新建名為MirrorPlugin
的groovy檔案。
package me.ele.mirror
import org.gradle.api.Plugin
import org.gradle.api.Project
public class MirrorPlugin implements Plugin<Project> {
void apply(Project project) {
System.out.println("========================");
System.out.println("Hello MirrorPlugin!");
System.out.println("========================");
}
}
複製程式碼
2.8 建立properties
在main目錄下建立\resources\META-INF\gradle-plugins\me.ele.mirror.plugin.properties
檔案,如圖:
這裡需要注意的兩點就是:
- 在
build.gradle
配置檔案裡要引入的外掛名是me.ele.mirror.plugin
,即properties
檔名,否則就會找不到外掛; implementation-class
配置的是繼承於Plugin的入口類,即me.ele.mirror.MirrorPlugin
,沒有.groovy
字尾名。
2.9 釋出到本地Maven
在plugin
module中,點選Tasks目錄下的uploadArchives
釋出依賴到repo倉庫中,如圖:
2.10 使用本地倉庫
在project 的build.gradle中buildscript中增加本地倉庫地址,如下:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
// 新增本地倉庫目錄
maven {
url uri('./repo')
}
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
// 匯入Mirror外掛依賴
classpath 'me.ele:mirror-plugin:1.0.0'
}
}
allprojects {
repositories {
google()
maven {
url uri('./repo')
}
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
複製程式碼
然後在app的build.gradle
中增加plugin
// 在編譯時期,應用Mirror外掛
apply plugin: 'me.ele.mirror.plugin'
複製程式碼
2.11 build專案
build專案後,可以在Gradle Console視窗中看到輸出內容:
通過以上步驟,我們已經實現了自定義Gradle外掛。雖然只是一個Demo,但是在看到控制檯下列印出自定義的log,心中還是有很大的成就感,畢竟我們接觸到了一個新姿勢。
3 小結
本篇部落格主要是帶領大家一步步手動自定義一個Gradle外掛,是實現Mirror工具的基礎,下一篇部落格將主要講Mirror工具中涉及的Aop技術。要是有什麼不對的地方,請多多指正!最後,非常感謝大家對本篇部落格的關注!
參考文獻
- github.com/JakeWharton…
- github.com/JakeWharton…
- greenrobot.org/greendao/
- docs.gradle.org/current/use…
閱讀部落格還不過癮?
歡迎大家掃二維碼通過新增群助手,加入交流群,討論和部落格有關的技術問題,還可以和博主有更多互動
部落格轉載、線下活動及合作等問題請郵件至 shadowfly_zyl@hotmail.com 進行溝通