編輯:Android資訊
本文介紹Android開發過程中的一些基本常識,大多是一些流程、專業術語和解決問題的方法等。
一個完整的軟件開發流程離不開策劃、交互、視覺、軟件、測試、維護和運營這七個環節,這七個環節並不是孤立的,它們是開發一款成功產品的前提,但每一項也都可以形成一個學科,是一個獨立的崗位,隨著敏捷開發的流行,以及來到了體驗為王的時代,現代軟件開發更多的是注重效率和敏捷,而不是循規蹈矩的遵循這些開發流程,比如軟件開發的崗位不再僅僅是個技術崗位,它需要去參與前期的設計和評審、可以在視覺和交互方面提出自己的見解,在開發的過程中需要自測程序盡快解決現存問題,運營和維護的過程中也需要軟件的幫助。可見現代軟件開發對開發者的綜合素質(這並不是facebook所講的全棧工程師)越來越高,自稱為碼農或者程序猿顯然是不合理的,因為這個過程是腦力勞動和體力腦動並存,稱呼自己為工程師顯得更為合理。
注:
大多數工作都是以結果為導向的,特別是軟件開發這個職業,績效考核、KPI這些都是在考核你工作的成果,所以工作更多地是需要你解決問題的能力,至於學習這個事情,還是在工作之外的時間去做吧。對於提高解決問題能力我有兩個建議:
如何用好 Google 等搜索引擎?
程序員應該如何提問?
提問的智慧
Smart Questions
為了寫這一項我專門在知乎上提過一個問題:
你有哪些解決bug的技巧?
在知道如何快速解決bug之前,你需要知道什麼是bug。沒有完成策劃、交互、視覺要求的功能,這不叫bug,這叫功能缺陷;一個功能完成後不能正常使用也不叫bug,因為它根本還沒達到可測試的標准。我認為當你的程序達到可測試標准之後發現的問題才叫bug。綜合我自己解決bug的經驗和知乎上的回答,總結常見的解決bug的方法有(你想要高效解決bug的前提是你能夠快速定位到缺陷所在的位置,所以以下方法多數講的是如何快速定位問題,至於真正解決bug,需要你自己修改程序才行):
以Eclipse為例:
1、打斷點:
(1)打斷點:
打斷點(2)清除斷點:
清除斷點2、啟動調試模式的兩種方式:
(1)通過debug as啟動調試程序:右鍵工程名–>Debug AS –>Android Application –>模擬器或者真機會彈出……watching for the debugger……的提示框,不要點擊等待其自動消失 –> 此時已經進入調試模式,操作程序到達打斷點的地方。
(2)在程序運行過程中,在DDMS視圖下選中要調試的程序,啟動調試模式:
DDMS視圖進入調試模式3、調試:請自行嘗試F5、F6、F7、F8這幾個調試的快捷鍵;
4、watch成員變量:在調試的過程中,比如在執行for、while、do while循環、遞歸、系統回調等程序時可以通過watch來觀察成員變量或者方法返回值的變化情況,watch的方法:
watch注:更多關於在Eclipse IDE中調試Android程序的知識請參見:Android eclipse中程序調試
打印:
打印調試的方法對於循環、異步加載、遞歸、JNI等代碼段非常有用,特別是在循環中,在循環次數非常大時,通過打斷點調試顯然是一件費力的事情,這時候打印就顯得更“智能”了,我通常會通過下面封裝的打印調試類來輸出打印信息,這個類可以打印print、log、行號、文件名、StrictMode等信息,當不需要打印信息時,只需要將DEBUG_MODE改為false就可以了:
import android.content.Context; import android.os.StrictMode; import android.util.Log; import android.widget.Toast; /** * 調試打印類 * * */ 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,但是不太靠譜,因為人的精力畢竟有限,有時候你多敲一個分號,縮進不對都有可能導致程序出現問題,但在代碼量較少時是一個高效率的方法。
自動化測試:
Android的自動化測試(分白盒測試和黑盒測試)工具有:monkey、Robotium、Appium、雲端測試(比如testin),具體用法可參見:
android實用測試方法之Monkey與MonkeyRunner
Robotium
Testin
Appium中文教程
排除法:
調試、打印、目視這三種方法適合於可以復現的問題,對於隨機問題(實際上不存在隨機問題,只是問題不那麼容易復現而已),比如在線程、音頻播放、AnsynTask、Timer切換或者結束時剛好做了相應地人為操作導致出現靈異現象。這時候可以通過排除法來排查問題,具體的方法是首先大概定位到出現問題的位置,然後將代碼一段一段地注釋,觀察程序現象,逐步縮小出現問題的范圍。
在較大的軟件開發過程中,可能有多個軟件工程師同時開發一個項目的情況,比如有負責讀取數據、獲取網絡數據等API封裝的,有負責程序架構的,有負責上層界面實現的,為了能夠最終編譯一個完成的程序出來,需要將代碼整合,這個時候最方便的方法就是使用版本管理工具,固定時間上傳(比如每天、沒改動一個功能等等),這樣能夠實時保證服務器上的代碼是最完整、最新的,也可以避免由於自然災害、電腦異常導致本地電腦掛掉損失掉代碼的問題。
常見的版本管理工具有SVN和Git,我也使用過CVS,關於版本管理工具的介紹參見:
版本控制
版本控制系統的選擇之路
git教程
git簡易指南
注:對於windows用戶來說,建議使用烏龜殼系列的版本控制客戶端,使用github的朋友可以使用github for windows客戶端:
tortoisegit
tortoisecvs
tortoisesvn
github for windows
通常我們用Eclipse或者Android Studio開發android程序時,只需要運行程序就可以在模擬器或者機器上運行程序了,但為了保證代碼的完整性、能夠在服務器上編譯,需要通過編譯工具將代碼編譯成apk,常見的編譯工具有:ant、gradle,但這兩種編譯工具都是需要通過手動敲命令來完成編譯功能(當然你也可以自己寫腳本來實現編譯自動化),jenkins是一個持續集成的工具,通過它可以代碼克隆、編譯以及程序加密自動化,其實它也是通過批處理來實現的,ant、gradle和jenkins的具體用法自行谷歌,使用起來很簡單,目前android studio和github上很多功能都是通過gradle來編譯的。
以下解釋完全是本人的理解,詳細解釋可自行谷歌。
參見我的另一篇文章:Android開發者網址導航
<item name="android:windowEnableSplitTouch">false</item> <item name="android:splitMotionEvents">false</item>
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; }
/** * 按鈕重復點擊 * * */ 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; }
下圖為Android應用開發第三方解決方案匯總,有些可以借助第三方平台搞定的就盡量不要自己搞,一是可以節省成本,二是你沒人家專業,原文鏈接:Android應用開發第三方解決方案
本文由碼農網 – 小峰原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃! 谷歌的Android生態系統正在不斷地迅速擴張。有證據表明,新的移
在一個窗口裡面添加tab便簽,完成便簽切換來實現頁面的切換,這樣的好處是可以在同一個窗口裡面有多個頁面,這些頁面共享同一個窗口的資源,同使用多個窗口來實現這個功能
Android中的TTextView很強大,我們可以不僅可以設置純文本為其內容,還可以設置包含網址和電子郵件地址的內容,並且使得這些點擊可以點擊。但是我們可以捕獲
roid 6.0的源碼剖析, 本文深度剖析Binder IPC過程, 這絕對是一篇匠心巨作,從Java framework到Native,再到Linux Kern