所有Android 開發(fā)者都要知道的開發(fā)常識
軟件開發(fā)流程
一個完整的軟件開發(fā)流程離不開策劃、交互、視覺、軟件、測試、維護和運營這七個環(huán)節(jié),這七個環(huán)節(jié)并不是孤立的,它們是開發(fā)一款成功產(chǎn)品的前提,但每一項也都可以形成一個學科,是一個獨立的崗位,隨著敏捷開發(fā)的流行,以及來到了體驗為王的時代,現(xiàn)代軟件開發(fā)更多的是注重效率和敏捷,而不是循規(guī)蹈矩的遵循這些開發(fā)流程,比如軟件開發(fā)的崗位不再僅僅是個技術崗位,它需要去參與前期的設計和評審、可以在視覺和交互方面提出自己的見解,在開發(fā)的過程中需要自測程序盡快解決現(xiàn)存問題,運營和維護的過程中也需要軟件的幫助。可見現(xiàn)代軟件開發(fā)對開發(fā)者的綜合素質(zhì)(這并不是facebook所講的全棧工程師)越來越高,自稱為碼農(nóng)或者程序猿顯然是不合理的,因為這個過程是腦力勞動和體力腦動并存,稱呼自己為工程師顯得更為合理。
策劃:需求收集(通過用戶調(diào)研、灰度發(fā)布、大數(shù)據(jù)分析、競品分析、領導拍腦袋等方式獲取需求)、需求整理(將需求歸類、劃分優(yōu)先級等)、將需求轉換成解決方案(輸出設計文檔);
交互:從心理學(利用人性的弱點)、人性化(心智)、個性化的角度將解決方案轉換成可交互的功能和界面(需要輸出交互文檔),比如加載等待、消息提示、頁面布局、頁面內(nèi)和頁面間的交互邏輯、頁面切換動畫等等,這個過程中一般會使用Axure或者PowerPoint來制作交互文檔;
視覺:根據(jù)交互圖,使用PhotoShop來做視覺效果,在Android上的圖片格式大多是png和jpg,對于需要屏幕適配,程序又適合做屏幕適配的地方可以使用九圖,格式為*.9.png。
軟件:根據(jù)視覺和交互效果將需求轉化為具體的實現(xiàn),在實現(xiàn)的過程中可能會因為需求、交互或者視覺的變動導致軟件實現(xiàn)的變動,因為策劃、交互、視覺這每一個環(huán)節(jié)都可能會有信息失真的現(xiàn)象,或者是由于市場環(huán)境的變化、獲取信息不夠準確、領導拍腦袋等等情況導致軟件始終處于被動狀態(tài),所以現(xiàn)在會提倡敏捷開發(fā)、結對編程、程序設計、同行評審、單元測試來提高程序的靈活性和穩(wěn)定性;
測試:軟件達到可交互的標準后,需要將可交互的程序提供測試,其中灰度發(fā)布(用戶測試)、自測(開發(fā)自測)、SQA(品質(zhì)保證)都算是測試;
維護和運營:通過測試程序達到穩(wěn)定標準后,軟件就可以上線了,軟件上線后,需要去維護,用戶反饋的問題要及時解決、用戶有疑問要及時解答;根據(jù)后臺統(tǒng)計信息、抓住可運營的節(jié)日、民族文化需要做運營來提高用戶使用產(chǎn)品的粘度,讓更多的用戶知道、使用產(chǎn)品都是運營應該做的。
注:
可以查看這個答案了解一個APP從創(chuàng)意到上線的具體流程,開發(fā)一個APP有多難?
可以查看筆戈科技的這篇文章了解一個手機(平板或其它電子產(chǎn)品也差不多)的誕生需要哪些環(huán)節(jié),一個手機的誕生過程
提問的智慧
大多數(shù)工作都是以結果為導向的,特別是軟件開發(fā)這個職業(yè),績效考核、KPI這些都是在考核你工作的成果,所以工作更多地是需要你解決問題的能力,至于學習這個事情,還是在工作之外的時間去做吧。對于提高解決問題能力我有兩個建議:
學會學習和思考:學習的過程中要廣度和深度并存,Android應用開發(fā)本身對技術功底的要求不高(因為很多底層的東西都被google、框架、開源代碼給封裝起來了,多數(shù)時候你只需要看ReadMe或者API知道怎么用就可以了),更多地是在你遇到問題的時候知道這個問題能夠通過什么方法和方式來解決。書要看,但多逛逛論壇、QQ群、Github、StackOverflow、CSDN博客專欄對自己都是有益的。
學會提問:你身邊有很多資源,比如同事、StackOverflow、QQ技術交流群、搜索引擎,當你遇到問題的時候完全可以利用身邊的資源來解決遇到的問題,如果一個問題在一個小時之內(nèi)自己都不能夠解決它,我就會通過搜索引擎、Github、QQ技術交流群、同事、StackOverflow(以上排序是按優(yōu)先級排列的)來解決它。如果你需要好的答案你就需要有好的提問,特別是在QQ群或者論壇,在提問的過程中需要體現(xiàn)出你的思考,能夠通過搜索引擎解決的問題堅決不問他人,這是對別人的尊重,在這里推薦幾個鏈接,認真看會對你有莫大的幫助:
如何用好 Google 等搜索引擎?
程序員應該如何提問?
提問的智慧
Smart Questions
解決bug的方法
為了寫這一項我專門在知乎上提過一個問題:
你有哪些解決bug的技巧?
在知道如何快速解決bug之前,你需要知道什么是bug。沒有完成策劃、交互、視覺要求的功能,這不叫bug,這叫功能缺陷;一個功能完成后不能正常使用也不叫bug,因為它根本還沒達到可測試的標準。我認為當你的程序達到可測試標準之后發(fā)現(xiàn)的問題才叫bug。綜合我自己解決bug的經(jīng)驗和知乎上的回答,總結常見的解決bug的方法有(你想要高效解決bug的前提是你能夠快速定位到缺陷所在的位置,所以以下方法多數(shù)講的是如何快速定位問題,至于真正解決bug,需要你自己修改程序才行):
斷點調(diào)試:
以Eclipse為例:
1、打斷點:
(1)打斷點:
(2)清除斷點:
2、啟動調(diào)試模式的兩種方式:
(1)通過debug as啟動調(diào)試程序:右鍵工程名--]Debug AS --]Android Application --]模擬器或者真機會彈出......watching for the debugger......的提示框,不要點擊等待其自動消失 --] 此時已經(jīng)進入調(diào)試模式,操作程序到達打斷點的地方。
(2)在程序運行過程中,在DDMS視圖下選中要調(diào)試的程序,啟動調(diào)試模式:
3、調(diào)試:請自行嘗試F5、F6、F7、F8這幾個調(diào)試的快捷鍵;
4、watch成員變量:在調(diào)試的過程中,比如在執(zhí)行for、while、do while循環(huán)、遞歸、系統(tǒng)回調(diào)等程序時可以通過watch來觀察成員變量或者方法返回值的變化情況,watch的方法:
注:更多關于在Eclipse IDE中調(diào)試Android程序的知識請參見:Android eclipse中程序調(diào)試
打印:
打印調(diào)試的方法對于循環(huán)、異步加載、遞歸、JNI等代碼段非常有用,特別是在循環(huán)中,在循環(huán)次數(shù)非常大時,通過打斷點調(diào)試顯然是一件費力的事情,這時候打印就顯得更“智能”了,我通常會通過下面封裝的打印調(diào)試類來輸出打印信息,這個類可以打印print、log、行號、文件名、StrictMode等信息,當不需要打印信息時,只需要將DEBUG_MODE改為false就可以了:
- import android.content.Context;
- import android.os.StrictMode;
- import android.util.Log;
- import android.widget.Toast;
- /**
- * 調(diào)試打印類
- *
- * */
- public class DebugUtils{
- private DebugUtils( ){
- }
- public static void println( String printInfo ){
- if( Debug.DEBUG_MODE && null != printInfo ){
- System.out.println( printInfo );
- }
- }
- public static void print( String printInfo ){
- if( Debug.DEBUG_MODE && null != printInfo ){
- System.out.print( printInfo );
- }
- }
- public static void printLogI( String logInfo ){
- printLogI( TAG, logInfo );
- }
- public static void printLogI( String tag, String logInfo ){
- if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
- Log.i( tag, logInfo );
- }
- }
- public static void printLogE( String logInfo ){
- printLogE( TAG, logInfo );
- }
- public static void printLogE( String tag, String logInfo ){
- if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
- Log.e( tag, logInfo );
- }
- }
- public static void printLogW( String logInfo ){
- printLogW( TAG, logInfo );
- }
- public static void printLogW( String tag, String logInfo ){
- if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
- Log.w( tag, logInfo );
- }
- }
- public static void printLogD( String logInfo ){
- printLogD( TAG, logInfo );
- }
- public static void printLogD( String tag, String logInfo ){
- if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
- Log.d( tag, logInfo );
- }
- }
- public static void printLogV( String logInfo ){
- printLogV( TAG, logInfo );
- }
- public static void printLogV( String tag, String logInfo ){
- if( Debug.DEBUG_MODE && null != tag || null != logInfo ){
- Log.v( tag, logInfo );
- }
- }
- public static void printLogWtf( String logInfo ){
- printLogWtf( TAG, logInfo );
- }
- public static void printLogWtf( String tag, String logInfo ){
- if( Debug.DEBUG_MODE && null != tag && null != logInfo ){
- Log.wtf( tag, logInfo );
- }
- }
- public static void showToast( Context context, String toastInfo ){
- if( null != context && null != toastInfo ){
- Toast.makeText( context, toastInfo, Toast.LENGTH_LONG ).show( );
- }
- }
- public static void showToast( Context context, String toastInfo, int timeLen ){
- if( null != context && null != toastInfo && ( timeLen ] 0 ) ){
- Toast.makeText( context, toastInfo, timeLen ).show( );
- }
- }
- public static void printBaseInfo( ){
- if( Debug.DEBUG_MODE ){
- StringBuffer strBuffer = new StringBuffer( );
- StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );
- strBuffer.append( "; class:" ).append( stackTrace[ 1 ].getClassName( ) )
- .append( "; method:" ).append( stackTrace[ 1 ].getMethodName( ) )
- .append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) )
- .append( "; fileName:" ).append( stackTrace[ 1 ].getFileName( ) );
- println( strBuffer.toString( ) );
- }
- }
- public static void printFileNameAndLinerNumber( ){
- if( Debug.DEBUG_MODE ){
- StringBuffer strBuffer = new StringBuffer( );
- StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );
- strBuffer.append( "; fileName:" ).append( stackTrace[ 1 ].getFileName( ) )
- .append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) );
- println( strBuffer.toString( ) );
- }
- }
- public static int printLineNumber( ){
- if( Debug.DEBUG_MODE ){
- StringBuffer strBuffer = new StringBuffer( );
- StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );
- strBuffer.append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) );
- println( strBuffer.toString( ) );
- return stackTrace[ 1 ].getLineNumber( );
- }else{
- return 0;
- }
- }
- public static void printMethod( ){
- if( Debug.DEBUG_MODE ){
- StringBuffer strBuffer = new StringBuffer( );
- StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );
- strBuffer.append( "; number:" ).append( stackTrace[ 1 ].getMethodName( ) );
- println( strBuffer.toString( ) );
- }
- }
- public static void printFileNameAndLinerNumber( String printInfo ){
- if( null == printInfo || !Debug.DEBUG_MODE ){
- return;
- }
- StringBuffer strBuffer = new StringBuffer( );
- StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );
- strBuffer.append( "; fileName:" ).append( stackTrace[ 1 ].getFileName( ) )
- .append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) ).append( "\n" )
- .append( ( null != printInfo ) ? printInfo : "" );
- println( strBuffer.toString( ) );
- }
- public static void showStrictMode( ) {
- if (DebugUtils.Debug.DEBUG_MODE) {
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build());
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectLeakedSqlLiteObjects().detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());
- }
- }
- public static void d(String tag, String msg){
- if(DebugUtils.Debug.DEBUG_MODE){
- Log.d(tag, msg);
- }
- }
- public class Debug{
- public static final boolean DEBUG_MODE = true;
- }
- public static final String TAG = "Debug";
- }
目視法:
這適合于code review,但是不太靠譜,因為人的精力畢竟有限,有時候你多敲一個分號,縮進不對都有可能導致程序出現(xiàn)問題,但在代碼量較少時是一個高效率的方法。
自動化測試:
Android的自動化測試(分白盒測試和黑盒測試)工具有:monkey、Robotium、Appium、云端測試(比如testin),具體用法可參見:
android實用測試方法之Monkey與MonkeyRunner
Robotium
Testin
Appium中文教程
排除法:
調(diào)試、打印、目視這三種方法適合于可以復現(xiàn)的問題,對于隨機問題(實際上不存在隨機問題,只是問題不那么容易復現(xiàn)而已),比如在線程、音頻播放、AnsynTask、Timer切換或者結束時剛好做了相應地人為操作導致出現(xiàn)靈異現(xiàn)象。這時候可以通過排除法來排查問題,具體的方法是首先大概定位到出現(xiàn)問題的位置,然后將代碼一段一段地注釋,觀察程序現(xiàn)象,逐步縮小出現(xiàn)問題的范圍。
版本管理介紹
在較大的軟件開發(fā)過程中,可能有多個軟件工程師同時開發(fā)一個項目的情況,比如有負責讀取數(shù)據(jù)、獲取網(wǎng)絡數(shù)據(jù)等API封裝的,有負責程序架構的,有負責上層界面實現(xiàn)的,為了能夠最終編譯一個完成的程序出來,需要將代碼整合,這個時候最方便的方法就是使用版本管理工具,固定時間上傳(比如每天、沒改動一個功能等等),這樣能夠實時保證服務器上的代碼是最完整、最新的,也可以避免由于自然災害、電腦異常導致本地電腦掛掉損失掉代碼的問題。
常見的版本管理工具有SVN和Git,我也使用過CVS,關于版本管理工具的介紹參見:
版本控制
版本控制系統(tǒng)的選擇之路
git教程
git簡易指南
注:對于windows用戶來說,建議使用烏龜殼系列的版本控制客戶端,使用github的朋友可以使用github for windows客戶端:
tortoisegit tortoisecvs tortoisesvn github for windows
編譯
通常我們用Eclipse或者Android Studio開發(fā)android程序時,只需要運行程序就可以在模擬器或者機器上運行程序了,但為了保證代碼的完整性、能夠在服務器上編譯,需要通過編譯工具將代碼編譯成apk,常見的編譯工具有:ant、gradle,但這兩種編譯工具都是需要通過手動敲命令來完成編譯功能(當然你也可以自己寫腳本來實現(xiàn)編譯自動化),jenkins是一個持續(xù)集成的工具,通過它可以代碼克隆、編譯以及程序加密自動化,其實它也是通過批處理來實現(xiàn)的,ant、gradle和jenkins的具體用法自行谷歌,使用起來很簡單,目前android studio和github上很多功能都是通過gradle來編譯的。
專業(yè)術語介紹
以下解釋完全是本人的理解,詳細解釋可自行谷歌。
版本迭代:按照需求優(yōu)先級,在保證基本功能OK后持續(xù)開發(fā)和升級,這樣能夠降低軟件開發(fā)的風險,并且能夠及時解決用戶反饋的問題,船小好掉頭嘛;
敏捷開發(fā):小步快跑,大概意思就是不要過于注重文檔,要注重當面交流,能夠在實現(xiàn)時高保真的還原用戶的需求場景,并且能夠快速地解決用戶的需求。
單元測試:白盒測試的一種,對核心方法通過寫程序來測試自己的程序,單元測試的目的是讓你有意識地降低程序間的耦合,保證每一個方法都是最小單元,但這對于測試程序邏輯是沒有幫助,這是我自己的理解。。。
灰度發(fā)布:先找一部分用戶來使用即將發(fā)布的程序(這部分用戶可以是隨機抽取、制定年齡段、指定地區(qū)或者通過某種方式知道他是活躍用戶),在測試的過程中給與用戶一點好處讓用戶寫用戶體驗報告、反饋問題等方式來發(fā)現(xiàn)程序存在的問題和缺陷;
DA統(tǒng)計:也叫后臺統(tǒng)計,通過在程序中埋點的方式,在有網(wǎng)絡的情況下將用戶的操作行為和數(shù)據(jù)上傳到后臺,將每個用戶的信息都上傳回來就叫大數(shù)據(jù),通過建模對這些數(shù)據(jù)分析就叫大數(shù)據(jù)分析。
開放平臺:比如分享到QQ空間、分享到微信、訊飛語音、友盟的后臺統(tǒng)計、天氣、地圖等等都叫做開放平臺,它提供了一些開放的接口給開發(fā)者,方便開發(fā)者使用它的服務,開放平臺多數(shù)服務都是免費的,但有時候也可能不穩(wěn)定,比如用的人少它自然就活不下去了,然后就沒有然后了。
同行評審:你的同行和你一起看看你的代碼,發(fā)現(xiàn)是否有問題;
結對編程:在寫代碼的過程中,有個人坐在你旁邊或者你坐在別人旁邊,編寫邊討論,降低程序出現(xiàn)邏輯和低級錯誤的概率。
Android開發(fā)資源
參見我的另一篇文章:Android開發(fā)者網(wǎng)址導航
建議
盡量閱讀官方文檔,這才是原汁原味、不失真的開發(fā)指導;
即使你認為設計程序是浪費時間,你只是喜歡寫程序,至少你也得用思維導圖理清思路,思維導圖對于幫助你理解設計文檔、理清思路有很大的幫助;
不要用Intent傳遞大量的數(shù)據(jù),這有可能導致ANR或者報異常;
在退出頁面后,系統(tǒng)不一定會及時執(zhí)行onDestory方法,如果你在onDestory方法里做關閉文件、釋放內(nèi)存的操作可能出現(xiàn)退出程序又立即進入時,由于需要重新初始化這些信息導致代碼重入的異常;
在改動JNI后,運行程序之前記得卸載掉已經(jīng)安裝在模擬器或者真機上的該程序,如果直接運行,android不會load最新編譯的so,也就不能立即看到修改后的效果;
代碼至少每天備份一次,或者是完善一個功能就備份一次,不要堆積之后一次性備份,因為在你的代碼出問題需要回溯代碼時你需要從服務器上重新取代碼,同時也可以避免代碼不是最新導致最后和其他人合并時不知道改了哪些地方;
將打印信息封裝成一個方法,用一個標志位控制這個這個方法的方法體是否需要執(zhí)行,這樣在由debug版釋放到release版本時,不需要傻傻地一行一行地去掉代碼,你只需要改變標志位的值就可以了;
對于有返回值的JNI函數(shù),即使你不返回任何值,用NDK編譯JNI的時候也不會報錯,所以在寫JNI代碼的時候,一定要仔細檢查代碼;
JNI頻繁讀寫文件操作會影響程序的運行性能,可以考慮一次性在內(nèi)存中申請一塊大內(nèi)存作為緩存空間,用這種空間換時間的方式可以大大提高程序的運行效率;
不要指望類的finalize方法去處理需要回收和銷毀的工作,因為finalize是系統(tǒng)回調(diào)的方法,調(diào)用時機不可預見,切記;
使用文件流、Cursor時,使用結束后記得一定要關閉,否則可能導致內(nèi)存泄漏,嚴重的情況可能引發(fā)程序崩潰;
優(yōu)先使用Google搜索引擎(少用百度),如果不能正常使用Google搜索引擎建議通過代理、VPN、修改hosts文件等方式搭建梯子。這里提供一個免費的谷歌搜索引擎
對于不需要使用硬件加速的activity(沒有動畫效果、視頻播放以及各種多媒體文件的操作都可以關掉硬件加速),在AndroidManifest.xml文件中通過“android:hardwareAccelerated="false"”關掉硬件加速可節(jié)省應用內(nèi)存;
對于需要橫豎屏轉換的應用,又不想在橫豎屏切換的時候重新跑onCreate方法,可以在AndroidManifest.xml文件中對應的Activity標簽下調(diào)用“android:configChanges="screenSize|orientation"”;
為了減輕應用程序主進程的內(nèi)存壓力,對于耗內(nèi)存比較多的界面(比如視頻播放界面、flash播放界面等),可以在AndroidManifest.xml文件中對應的Activity標簽下調(diào)用“android:process=".processname"”單開一個進程,但在退出這個界面的時候一定要在該界面的onDestory方法中調(diào)用System的kill方法來殺掉該進程;
在res/values/arrays.xml文件中定義的單個數(shù)組的元素個數(shù)不宜過大,過大會導致加載數(shù)據(jù)時非常慢,有時候你需要使用數(shù)組資源時數(shù)據(jù)有可能還沒加載完成;
一個Activity中最耗費內(nèi)存的是activity的背景(多數(shù)情況如此,特別是對于分辨率很大的機器,一個界面的背景算下來都需要好幾兆內(nèi)存),所以在程序界面較多時,可以考慮將圖片轉換成靜態(tài)的drawable,然后多個activity共用這一張背景圖;
可以通過為application、activity自定義主題的方式來關掉多點觸摸功能,只需要在自定義的主題下添加這兩個標簽:
- [item name="android:windowEnableSplitTouch"]false[/item]
- [item name="android:splitMotionEvents"]false[/item]
很多游戲進入時,播放的片頭動畫多數(shù)是一個視頻文件;
Android單個dex文件的方法數(shù)不能超過65536個,android使用多個dex能否避開65536方法數(shù)限制?
使用模擬器genymotion代替android自帶模擬器(它需要虛擬機vituralbox的支持,不過官網(wǎng)已經(jīng)提供了一個集成虛擬機的安裝包了,直接下載下來安裝即可),可以大大提高使用模擬器的體驗(流暢、快),它也可以以插件的形式集成在Eclipse中,這是視頻教程
給Application或者activity設置自定義主題時,最好不要設置為全透明,否則在activity按Home鍵回退到桌面的時候效果很渣;
如果你需要取消toast顯示的功能,在一個類中你只需要實例化該類一次(也就是說將Toast定義成一個全局的成員變量),這樣你就可以調(diào)用mToast.cancel()了,我把它寫成了一個靜態(tài)類:
- public class ToastUtils {
- private ToastUtils( ){
- }
- public static void showToast( Context context, String toast ){
- if( null == mToast ){
- mToast = Toast.makeText( context, toast, Toast.LENGTH_LONG );
- }else{
- mToast.setText( toast );
- }
- mToast.show( );
- }
- public static void cancel( ){
- if( null != mToast ){
- mToast.cancel( );
- }
- }
- public static Toast mToast = null;
- }
你可以定義一個靜態(tài)類來實現(xiàn)防止按鈕被重復點擊導致重復執(zhí)行一段代碼的問題:
- /**
- * 按鈕重復點擊
- *
- * */
- public class BtnClickUtils {
- private BtnClickUtils( ){
- }
- public static boolean isFastDoubleClick() {
- long time = System.currentTimeMillis();
- long timeD = time - mLastClickTime;
- if ( 0 [ timeD && timeD [ 1000) {
- return true;
- }
- mLastClickTime = time;
- return false;
- }
- private static long mLastClickTime = 0;
- }
放在apk的assets或者raw目錄下的數(shù)據(jù)文件最好做加密處理,在需要使用的時候才解密,這樣可以避免在apk被他人破解時數(shù)據(jù)也被破解的問題;
最好不要再activity的onCreate方法里面調(diào)用popupwindow的show方法,有可能由于activity沒有完全初始化導致程序異常(android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid),如果非要在一進activity就顯示popupwindow,建議用handler.post、View.postDelay來處理;
對于自定義View,在構造方法里面是獲取不到視圖的寬高的(此時獲取長寬都為0),需要在onMeasure方法中或者跑了onMeasure方法后才能夠獲取到視圖的寬高,不過你可以通過在構造方法里面強制測量視圖的寬高來實現(xiàn)在構造方法里獲取視圖的寬高信息,具體見MeasureSpec介紹及使用詳解
如果你覺得在安裝Eclipse后還需要配置android開發(fā)環(huán)境很麻煩,你可以直接使用ADT Bundle,它是一個懶人套餐,下載下來就可以用了,可以在這里下載。
有時間看看阿里技術嘉年華、InfoQ演講與訪談、Google IO視頻,可以學習到一些解決問題、做大項目的經(jīng)驗。
當應用中動畫比較多,并且動畫都是通過圖片來切換的時候,可以考慮借用Cocos的精靈表單思想,這樣就可以避免圖片命名的煩惱。
工具推薦
代碼對比:Beyond compare
屏幕取色:ColorPix
梯子:紅杏
思維導圖: mindmanager
在線工具:在線工具