1. 程式人生 > >Android調試優化篇

Android調試優化篇

副作用 debug 等級 觸屏 文件 拷貝 eap 虛擬機 width

為了開發出商業級的應用程序。大規模的測試是不可避免的,同一時候為了提高應用程序的執行速度,須要進行必要的優化。在Android中。提供了豐富的調試與優化工具供開發者應用,主要包含模擬器和目標端等兩種場景下使用的工具。

1.Android調試

軟件調試是一個伴隨軟件開發的必定過程。好的調試環境和工具能夠提高開發的效率。在Android中,除了提供GDB調試外,還提供了DNSS、Logcat、Dmtracedump、DevTools、Procrank、Dumpsys等開發工具供開發人員使用,當中DMSS包括了多個組件。

當發生Android ANR錯誤時,錯誤信息會保存在\data\anr\traces.txt中,這有助於在無法實時查看日誌的情況下分析Bug。

(1)Logcat日誌調試

android.util.Log經常使用的方法有下面5個:Log.v()、Log.d()、Log.i()、Log.w()及Log.e(),其分別相應VERBOSE、DEBUG、INFO、WARN、ERROR等不同等級。

開發人員能夠依據場景的不同選擇不同的方法。

普通情況下。對於純粹的調試信息,筆者建議採用Log.d()方法。

須要說明的是,android.util.Log僅適用於應用層。對於框架層的調試。須要使用android.util.slog類。

C/C++依舊支持log的輸出。

(2)dmtracedump跟蹤

dmtracedump是一個基於圖形界面的使用方法間調用關系的工具。在使用dmtracedump前,必須安裝Graphviz,在Linux下安裝Graphviz的方法例如以下:

#apt-get install graphviz

dmtracedump的使用方法例如以下:

dmtracedump [-ho] [-s sortable] [-d trace-base-name] [-g outfile] <trace-base-name>

在實際操作中,為了分析函數間的調用關系,首先要在須要分析的方法中設置跟蹤的起始點和結束點。方法例如以下:

開始跟蹤:Debug.startMethodTracing("loadEvents"); //輸出文件為loadEvents.trace

結束跟蹤:Debug.stopMethodTracing();

程序執行結束後。就可以在\sdcard下看到loadEvents.trace就可以分析時間關系和方法調用關系。當然這是文本界面的。要通過圖形界面來顯示就要用到dmtracedump,方法例如以下:

#dmtracedump -g out.png clac.trace

瀏覽out.png就可以看到相關函數調用關系圖,其結點的格式例如以下:

<ref> callname (<inc-ma>,<exc-ms>,<numcalls>)

上述格式中,ref表示編號,callname表示方法名。inc-ms表示調用事件,exc-ms表示運行時間,numcalls表示運行次數。

(3)Dev Tools調試

Dev Tools是Android特有的、幫組在物理設備上開發調試應用的工具,默認在SDK中存在。

通過Dev Tools能夠打開一些設備幫組調試的設置。如USB的設備等,還能夠查看安裝包、啟動終端等,這些對開發人員在物理設備上調試應用顯得十分實用。

(4)屏幕截圖分析

還有一個簡單卻十分實用的工具是DDMS下的screen capture工具。通過它,開發人員能夠直接截圖。供自己或同事進行對應的分析。這在須要溝通的場景中十分實用。screen caption工具支持界面的手工刷新和旋轉,其保存圖片的格式為PNG。

眼下screen capture工具對視頻播放尚無法提供有效的支持,這可能是因為視頻幀速率過快,而screen capture工具尚無法支持這麽高的幀速率導致的。

(5)內存調試

內存調試不僅限於C、C++等原生代碼,Java也相同須要,在Android中。有多個工具如DDMS、Procrank、Dumpsys等能夠獲取系統執行期的內存信息,這些信息十分有助於進行內存調試。

1)DDMS內存調試

在Eclipse中集成的DMSS並沒有呈現出所有的DMSS能力,假設希望觀察系統更具體的信息,能夠直接啟動DK\tools\ddms。

2)Dumpsys內存調試

通過Dumpsys能夠進行非常多分析,其分析內存的方法為adb shell dumpsys meminfo。

3)Procrank內存調試

另外。通過adb shell procrank也能夠查看到進程占用內存的情況,當中Uss(Unique Set Size)的大小代表屬於本進程正在使用的內存大小。這些內存在該進程被撤銷後,會被全然回收,Uss也是進行內存泄露觀察時的重點:Vss(Virtual Set Size)和Rss(Rsdident Set Size)表示共享庫的內存使用,可是因為共享庫的資源一般占用比較大,因此會使進程自身創建引起的內存波動所占比例減小;而Pss(Proportional Set Size)則依照比例進行共享內存切割。

通過間隔性地執行Procrank來觀察進程占用Uss內存的變化,能夠分析應用是否存在內存泄露。Procrank的代碼位於system\extras\procrank目錄。

Procrank的使用方法為:

procrank [-W] [-v | -r| -p| -u| -h]

考慮到Android是基於Dalvik虛擬機的。垃圾回收並不是實時的,故通過單個界面的單次啟動、關閉是無法確定內存是否泄露的。一個號的策略是反復運行某個界面的啟動、關閉,假設發現應用占用的內存不斷上升。則能夠推斷該界面存在內存泄露。

4)Eclipse插件內存調試

在Eclipse中。有一個插件MAT(Memory Analyzer Tool)能夠幫組分析Java層的內存泄露,其下載地址為http://www.eclipse.org/mat/。

MAT分析的是hprof文件,該文件裏存放了進程的內存快照。以下是從終端獲取hprof文件的方法:

#adb shell

#ps //查看進程號

#chmod 777 /data/misc

#kill -10 PID //PID即進程號

這樣就可以在\data\misc文件夾下生成一個帶當前時間的hprof文件,但這個文件並不能直接被MAT讀取。開發人員需借助hprof-conv將hprof轉換為MAT能夠讀取的格式,然後才可用MAT進行分析。hprof-conv的使用方法例如以下:

hprof-conv <infile><outfile>

2.Android布局優化

為了實現精細的布局。Android提供了兩個Android布局優化。即Layoutopt和Hierarchyviewer。當中Layoutopt能夠優化布局,幫組開發人員降低冗余信息。而Hierarchyviewer則可直接調試用戶界面。

(1)Layoutopy優化

Layoutipt是一個優化布局的工具。它能夠幫組開發人員分析採用的布局是否合理。並給出改動意見。其使用方法是:

layoutopt <directories/files to analyze>

詳細方法例如以下:

layoutopt res/layout-land

layoutopt res/layout/main.xml

Layoutopt能夠指出有問題的代碼所在的位置和出問題的解決辦法。還能夠給出優化的建議。

(2)Hierarchyviewer優化

Hierarchyviewer同意開發人員調試和優化用戶界面。通常Hierarchyviewer開發人員能夠清晰地看到當前設備的UI界面的實際布局和控件屬性。這在復雜界面的調試中顯得很實用。能夠幫助開發人員高速定位問題。當然出於安全性考慮。Hierachyviewer僅能優化debug模式的應用。

發起Hierarchyviewer優化的過程例如以下:

1)啟動物理設備或模擬器。

2)執行應用至欲優化的界面。

3)在終端啟動Hierarchyviewer(位於SDK的tools文件夾下)。

4)選擇設備和Activity。

Hierarchyviewer提供了兩個層次的分析界面,即視圖界面和像素界面。在Herarchyviewer的視圖界面中。開發人員能夠清晰地看到布局文件的層次關系,針對特定的UI控件。開發人員能夠清晰地看到控件在屏幕上的位置以及各屬性的值。熟練使用Hierarchyviewer。能夠加快UI調試效率。

3.Android測試

Android提供的幾種測試工具能夠幫組開發人員最大限度地提升開發質量和應用程序的兼容性。這些測試工具中最重要的兩個為Monkey壓力測試工具和CTS兼容性測試工具。

(1)Monkey壓力測試

Monkey工具能夠模擬各種按鍵、觸屏、軌跡球、導航、Activity等事件。

此工具使用方法例如以下:

adb shell monkey [option] <event-count> //特定事件

adb shell monkey -p your.packege.name -v 50000 //50000隨機事件

為了使開發的應用程序具有一定的穩定度。在使用前建議進行Monkey壓力測試。須要註意的是,Monkey壓力測試僅能模擬系統事件監測應用中存在的語法Bug。對於深層次的語義Bug依舊無能為力。並且對於網絡功能,Monkey壓力測試也無法進行有效的測試。

因為Monkey是通過載入特定Activity(category屬性需為android.intent.category.LAUNCHER或android.intent.category.MONKEY)作為程序入口來進行測試的。故在進行Monkey壓力測試時。easy形成孤島的Activity,為了進行全面測試,須要為其添加Intent過濾器。

對應的參考實現例如以下:

<activity android:name="Hello">

<intent-filter>

<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.MONKEY"/>

</intent-filter>

</activity>

須要註意的是。假設僅僅針對單個應用進行壓力測試。Monkey會阻止對其它包的調用。當然針對單個應用的壓力測試無法檢測應用間交互可能存在Bug。為了測試系統內應用交互與沖突帶來的問題,能夠針對系統進行Monkey壓力測試。方法例如以下:

#adb shell monkey -p -v 50000

在進行Monkey壓力測試的過程中。Monkey會由於應用奔潰、網絡超時及一些無法處理的異常而停止測試。建議在對網絡應用進行壓力測試時。忽略網絡超時的情況,方法例如以下:

#adb shell monkey -p your.package.name -v 50000 --ignore-timeouts

假設希望忽略應用奔潰的情況,那麽可運行例如以下方法:

#adb shell monkey -p your.package.name -v 50000 --ignore-crashes

在進行壓力測試時,假設發生異常情況,如系統奔潰,那麽Android會自己主動打印出相關的系統信息(如內存分配等)供開發人員分析。

另外,為了更好地進行Monkey壓力測試。須要使外圍設備的配置保持和實際產品同樣,如默認的鍵盤時QWERTY鍵盤(這對於沒有物理鍵盤的產品不適用)。

(2)JUnit回歸測試

JUnit回歸測試即白盒測試,通經常使用在單元測試場景中,如對非圖形界面的接口的測試,這在開發框架性代碼時很實用。

Android僅支持JUnit3,尚不支持JUnit4。

在Android中,Google對JUnit進行了封裝,Android JUnit還支持圖形界面如Activity、View等的測試,甚至支持對圖形界面接口的功能壓力測試。Android JUnit的內容分主要分布在android.text中,另外android.text.mock和android.text.suitebuilder也提供了一些輔助和支持。

依據約束的不同。測試能夠分為SmallTest、MediumTest、LargeTest、AndroidOnly、SideEffect、UiThreadTest、BrokenTest、SmokeSuppress等。當中AndroidOnly、表示測試項僅適用於Android;SideEffect表示測試項具有副作用;UiThreadTest表示測試項在UI主線程中執行;BrokenTest表示測試項須要修復;Smoke表示測試項為冒煙測試;Suppress表示該測試項不應出如今測試用例中。

為了進行JUnit回歸測試。須要在Eclipse中創建Android Test Project,可通過運行File-New-Other-Android-Android Test Project命令完畢。運行測試的方法為右擊project項。在彈出的菜單中運行run as-android JUnit Test命令,對於具有多個Instrumentation-TestRunner的測試project,在運行測試時,應該先在project的Run Configurations中指明InstrumentationTestRunner。測試完畢後。會自己主動給出測試結論。在物理設備上借助Dev Tools也能夠做JUnit測試,當然,通過am命令也能夠運行JUnit測試,只是這樣的方法比較繁瑣。

1)JUnit測試的框架

Junit測試主要包含TestCase、Instrumentation等。

為了執行測試用例,必須將測試用例加入到TestSuite中,通過Instrumentation來管理。

測試用例的繼承關系例如以下圖:

技術分享

除了上面的測試用例外。開發人員還能夠通過PerformanceTestCase運行性能測試。註意,ProviderTestCase和ActivityInstrumentationTestCase已經被拋棄。應慎用。

InstrumentationTestRunner主要通過AndroidTestRunner的支持被載入的。

AndroidTestRunner的繼承關系例如以下圖所看到的:

技術分享

AndroidTestRunner繼承了TestListener接口,用來發起和結束測試。生成測試報告,當中發起執行測試的方法為runTest(),這才是JUnit測試的核心。

2)JUnit測試的實現

通過JUnit進行回歸測試,須要做例如以下幾方面的工作:

構建AndroidMainifest.xml配置文件。

制定InstrumentationTestRunner文件。

構建詳細測試代碼。

假設是基於源碼進行的測試。那麽還須要構建Android.mk文件。

(1)構建AndroidMainifest.xml配置文件

和普通project類似的是,JUnit回歸測試project須要構建AndroidMainfest.xml配置文件;但和普通project不同的是,JUnit回歸測試project沒有圖形界面,必須聲明要用到“android.test.runner"JAR包,聲明對應的Instrumentation TestRunner。在通過SDK創建測試project時,AndroidMainfest.xml會自己主動生成,默認的InstrumentationTestRunner為android.testRunner。在某些情況下,開發人員須要定義InstrumentationTestRunner。以下是JUnit回歸測試的AndroidMainfest.xml文件的實現:

<mainfest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.android.calcutor2.tests">

<application>

<use-library android:name=”android.test.runner"/>

<application>

<instrumentation android:name="CalculatorLaunchPerformace" //僅在源碼下可用

android:targetPackage="com.android.calculator2"

android:label="Calculator Launch Performance">

</instrumentation>

<instrumentation

android:name="android.test.InstrumentationTestRunner"

android:targetPackage="com.android.calculator2"

android:label="Calculator Funstional Testset">

</instrumentation>

</manifest>

(2)指定InstrumentationTestRunner文件

InstrumentationTestRunner文件為Junit測試的入口文件,在某些情況下自己定義InstrumentationTestRunner類。最重要的是addTestSuite方法。他將TestCase納入TestSuite流程,調用對應的方法,步驟例如以下:

public class MusicPlayerFunctionalTestRunner extends InstrumentationTestRunner{

public TestSuite getAllTests(){

TestSuite suite=new InstrumentationTestSuite(this);

suite.addTestSuite(TestSongs.class);

suite.addTestSuite(TestPlaylist.class);

suite.addTestSuite(MusicPlayerStability.class);

retuen suite;

}

public ClassLoader getLoader(){

return MusicPlayerFunctionnalTestRunner.class.getClassLoader();

}

}

(3)構建詳細測試代碼

為了測試詳細的代碼,須要依據組件的類型構建對應的測試用例。當中。有兩個關鍵的方法要註意:setUp方法用來構建測試環境。如打開網絡鏈接等。tearDown方法能夠確保在進入下一個測試用例前全部資源被銷毀並被回收。對於不同的測試,應該配置不同的測試類型。測試Activity的用例的實現例如以下:

public class SpinnerTest extends ActivityInstrumentationTestCase2<RelativeLayoutStubActivity>{

private Context mTargetContext;

public SpinnerTest(){

super("com.android.cts.stub", RelativeLayoutStubActivity.class);

}

protected void setUp() throws Exception{

super.setUp();

mTargetContext=getInstrumentation().getTargetContext();

}

protected void tearDown() throws Exception{

super.tearDown();

}

public void testGetBaseline(){ //測試項方法必須以test開頭

Spinner spinner=new Spinner(mTargetContext);

assertEquals(-1, spinner,getBaseline());

spinner=(Spinner)getActivity().findViewById(R.id.spinnere1);

ArrayAdapter<CharSequence> adapter=ArrayAdapter.createFromResource(mTargetContext, com.android.cts.stub.R.array.string, android.R.layout.simple_spinner_item);

adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

spinner.setAdapter(adapter);

assertTrue(spinner.getBaseline() > 0);

}

}

(4)構建Android.mk文件

在源碼中進行編譯。必須構建Android.mk文件。

和普通Android.mk文件不同,Android.mk文件須要指定LOCAL_MODULE_TAGS為tests,通過LOCAL_JAVA_LIBRARIES變量載入android.test.runner的JAR包,通過LOCAL_INSTRUMENTATION_FOR指定對那個包進行回歸測試。以下是進行JUnit回歸測試的Android.mk實現:

LOCAL_PATH:=$(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE_TAGS:=tests //指定TAGS為“tests”

LOCAL_JAVA_LIBRARIES:=android.test.runner //載入名為android.test.runner的JAR包

LOCAL_SRC_FILES:=$(call all -java-file-under, src)

LOCAL_PACKAGE_NAMES:=CalculatorTests //包名

LOCAL_INSTRUMENTATION_FOR:=Calculator //指定為那個project做回歸測試

include $(BUILD_PACKAGE)

利用JUnit進行回歸測試時,假設在控制臺上發現例如以下錯誤提示:

Application does not specify a android.test.InstrumentationTestRunner instrumentation or does not declare uses-library android.test.runner

應檢查AndroidManifest.xml的配置是否正確。假設Eclipse彈出提示信息“No tests found with runner ‘JUnit 3‘。應檢查測試項目目的Run Configurations中InstrumentationTestRunner的配置是否有誤。

(3)CTS兼容性測試

為了防止OEM廠商對Android的定制導致平臺的不兼容問題,Google在公布Android版本號的同一時候會公布相關的CTS測試。編譯CTS和啟動交互CTS控制臺的方法例如以下:

cd /path/to/android/root

make cts

cts

運行CTS測試並設置測試參數的方法例如以下:

cts start --plan CTS -p android.os.cts.BuildVersionTest

須要說明的是。為了保持Android系統間的兼容性,Google規定。必須通過CTS兼容性測試,才會授予OEM廠商Android商標和接入Android Market的權利。

CTS兼容性測試對於不少OEM廠商甚至芯片廠商而言,是無法全然通過的。

(4)目標環境測試

在實際的開發過程中,假設臨時無法獲得物理設備進行測試,考慮到目標環境的差異,應該針對目標物理設備構建自己定義的模擬器,這就是所謂的目標環境測試。

1)硬件配置

硬件的配置能夠通過AVD管理器在創建模擬器時設置,當然以後也能夠變更。在默認Eclipse工作空間的登錄用戶為root的情況下。AVD的配置位於\root\.android\avd\avdname.avd文件夾下的config.ini中。以下是一個config.ini的配置:

hw.lcd.density=160

sdcard.size=200M

skin.name=WVGA800

skin.path=platforms/android-8/skins/WVGA800

hw.cpu.arch=arm

abi.type=armeabi

vm.heapSize=24

image.sysdir.1=platforms/android-8/images

除了以上硬件信息外,開發人員開能夠配置攝像頭、電池、耳機、麥克風、GPS、LCD背光等。

另外\root\.android\avd\avdname.avd\hardware-qemu.ini中給出了和硬件相關的全然配置。在模擬器執行時,會檢查config.int和hardware-qemu.int中相應的配置項是否一致,當不一致時,依據config.int中的配置來改動hardware-qemu.int中的配置。

2)網絡模式

在Android模擬器中。童工AVD管理器還能夠模擬網絡,如是否支持GSM。通過project的Run Configuration能夠選擇網絡速度,其類型包含Full、GSM、HSCSD、GPRS、EDGE、UMTS、HSDPA等;還能夠選擇網絡延遲。其類型包含None、GPRS、EDGE、UMTS等。這些在開發具有卓越用戶體驗的移動終端時很重要。

網絡配置會在模擬器啟動時作為參數傳遞給模擬器。

3)通話/SNS/模擬

在DDMS中,Andoid支持通話/SMS的測試。

在DDMS面板中。選擇好網絡狀態。如unregistered、home、roaming、searching、denied等。做好速度和延遲配置。之後就可以測試語音、數據和SMS。關於網絡狀態和無線接入協議的詳細含義。涉及無線通信的協議棧內容。

4)GPS模擬

Android還支持GPS模擬,能夠明白指定經緯度,還能夠載入GPX、GML等軌跡文件。

4.Android性能優化

盡管眼下的Android終端普遍配置了ARM Cortex A8甚至ARM Cortex A9的雙核處理器,可是這並不意味著無須優化性能。Android採取了多種手段來加快應用啟動和執行速度,還提供了多個工具供用戶分析性能的瓶頸。

在詳細的軟件開發中。智能終端盡管近今年得到了高速發展。但其所擁有的資源仍然有限。為了降低不必要的開銷。有兩個原則必須遵守:

不做不必要的事。

不分配不必要的內存。

(1)優化資源讀取

依據資源性質的不同。Android對資源文件和SD卡資源進行了優化。

1)資源文件

Android在編譯時對描寫敘述UI的XML文件進行了優化,這就是開發人員將APK解包後無法打開當中的XML文件的原因。

2)SD卡

對於SD卡中的數據。Android會在設備啟動和SD卡插拔時進行增量式掃描,而不是在應用載入資源時進行掃描,這無疑加快了啟動速度,其實。這依賴於MediaScanner的實現。

當然其也存在局限性,對於文件類型沒有包括在MediaFile.java中的文件。

MediaScanner就無能為力了。

(2)優化APK載入

Android提供了zipalign。zipalign提供了4字節的邊界對齊來映射內存,通過空間換時間的方式來提高APK載入的運行效率。

zipalign的使用方法: zipalign [-c] [-f] [-v] <align> infile.zip outfile.zip //-c表示檢查對齊。-f表示強制覆蓋已有輸出文件

經常使用的zipalign方法例如以下:#zipalign -v 4 infile.zip outfile.zip //-v 代表具體輸出。

”4“表示4字節對齊

對於APK的載入,假設是預制應用。Android會在系統編譯後生成後綴為ODEX的優化文件,對於非預制應用,在第一次啟動應用時,DEX文件會被優化為DEY文件並放置到\data\dalvik-cache文件夾下,從而加快啟動速度。

(3)Dalvik虛擬機

在Android中,出於性能和規避知識產權方面的考慮,Google並沒有直接採用CLASS字節碼,而是先將Java代碼編譯成CLASS字節碼,然後再將CLASS字節碼轉化為DEX字節碼。在這一過程中,Android會刪除冗余,這非常大程度上減小了APK的大小。最後將APK載入到Dalvik虛擬機中。

在CLASS字節碼轉化為DEX字節碼的過程中,Android對冗余信息進行了處理。

另外。通過Dexdump能夠查看出APK文件裏DEX運行情況,這有助於開發人員優化自己的程序。

(4)TraceView性能分析

TraceView是Android提供的一個調試應用和優化性能的圖形界面工具,其包含兩個面板,Timeline Panel和Profile Panel。

Timeline Panel從時間維度為開發人員提供了優化性能的視角,在Timeliness Panel的左側顯示的是不同的線程。Profile Panel從方法調用順序角度為開發人員提供了優化性能的視角。Profile Panel面板中顯示的方法有父方法和子方法,所謂父方法即調用當前方法的方法,所謂子方法即當前方法調用的方法。

為了通過TraceView進行性能分析,首先要在代碼中設置跟蹤的起點和終點。方法例如以下:
Debug.startMethodTracing("test"); //"test"為生成的trace文件名稱

setContentView(R.layout.main);

Debug.stopMethodTracing();

生成的trace文件會被放置在\sdcard文件夾下。當然為了在SD卡下運行寫入操作,須要例如以下權限:

<uses-permission>

android:name="android.permission.WRITE_EXTERNAL_STORAGE">

</uses-permission>

這裏生成的trace文件為test.trace。

在將test.trace導出到Linux下後,就可以通過android_sdk_linux/tools/traceview腳本進行性能分析。方法例如以下:

#cd ANDROID_SDK_HOME

#./tools/traceview test.trace

Inclusive表示整個方法從調用到結束的運行時間。包括子方法運行時間;而Exclusive僅表示方法本身的運行時間。

當然因為test.trace僅分析了setContentView的載入過程,從視圖上看不出有不論什麽性能問題,可是在實際的開發中就不一樣了。開發人員要活用TraceView,提升應用的質量。

(5)執行效率優化

為了提升代碼的執行效率,必須知道最消耗計算能力的代碼段的位置。依據80/20法則,優化最重要的20%的代碼就可以大幅提升代碼的執行效率。

最直接的觀察執行效率的方法是算出代碼段在目標環境下執行時間,對應方法例如以下:

long start = System.currentTimeMillis();

long duration = System.currentTimeMillis()-start; //毫秒數

另外,通過觀察Logcat顯示的虛擬機釋放內存的情況。也可觀察出計算能力消耗的位置。

在平臺方面。為了充分挖掘CPU的性能。Android還針對armv5te進行了優化。充分利用armv5te的運行流水線來提高運行的效率。

在創建新進程時,Android採用了Linux的寫時拷貝(Copy on Write)機制,是創建一個新進程很高效。

Android調試優化篇