爲了開發出商業級的應用程序,大規模的測試是不可避免的,同一時候爲了提升應用程序的執行速度,需要進行必要的優化。在Android中。提供了豐富的調試與優化工具供開發者應用,主要包含模擬器和目標端等兩種場景下使用的工具。java
1.Android調試linux
軟件調試是一個伴隨軟件開發的一定過程。好的調試環境和工具可以提升開發的效率。在Android中,除了提供GDB調試外。還提供了DNSS、Logcat、Dmtracedump、DevTools、Procrank、Dumpsys等開發工具供開發人員使用,當中DMSS包括了多個組件。android
當發生Android ANR錯誤時,錯誤信息會保存在\data\anr\traces.txt中,這有助於在沒法實時查看日誌的狀況下分析Bug。shell
(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類。app
C/C++依舊支持log的輸出。框架
(2)dmtracedump跟蹤eclipse
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)機制。是建立一個新進程很高效。