在前面的文章中,我們一直致力於開發一個可以在Google Play上釋出的簡單App。因為上一篇文章中提出在App中發現一個Bug,所以在這篇文章中,我們將尋找工具夠探測並修復該Bug。
在這之前,首先得道個歉。在上一篇文章中我承諾這篇文章將會介紹一個新的特性,但是Bug的出現打亂這個節奏。與其增加一個新特性卻Bug不能使用App,還不如首先集中精力修復這個Bug。所以在此,對那些想學習新特性的人說一聲抱歉了。
那些在他們裝置上裝了Text clock App的人可能會受到2013年2月23號更新版本的汙染。當使用者遇到軟體崩潰的時候,他們會有一個機會來向開發者的提交遇到的Bug。當有使用者提交了之後,開發者會在控制檯得到一個錯誤報告日誌。下面這張圖就是我收到的一個錯誤報告。
這告訴我們這個Bug是由NoSuchMethodError
異常引起的。 這個異常是由android.appwidget.AppWidgetManager.getAppWidgetOptions
的方法丟擲的。如果我們檢視一下關於這個方法的官方檔案,我們會發現它在官方API16中的介紹:
現在,這個問題非常清楚。 當這個App執行在一個裝API16(Jelly Bean 4.1)或者裝有16之前版本的作業系統裝置上時,系統不支援這個方法。所以會丟擲NoSuchMethodError
異常。與此同時,錯誤報告還告訴我們這個方法在那裡被呼叫:TextClockService
的updateTime
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
; html-script: false ] private void updateTime(Calendar date) { Log.d(TAG, "Update: " + dateFormat.format(date.getTime())); AppWidgetManager manager = AppWidgetManager.getInstance(this); ComponentName name = new ComponentName(this, TextClockAppWidget.class); int[] appIds = manager.getAppWidgetIds(name); String[] words = TimeToWords.timeToWords(date); for (int id : appIds) { Bundle options = manager.getAppWidgetOptions(id); int layoutId = R.layout.appwidget; if (options != null) { int type = options.getInt("appWidgetCategory", 1); if (type == 2) { layoutId = R.layout.keyguard; } } RemoteViews v = new RemoteViews(getPackageName(), layoutId); updateTime(words, v); manager.updateAppWidget(id, v); } } |
儘管像前面討論過的,我們App具備一定的API向前相容性,但是這個問題是由我們對getAppWidgetOptions
包容性疏忽造成的。
解決這個問題相對容易,而且可以用我們之前討論過的技術來確保API版本的向前相容性。首先需要檢測作業系統的API版本,並根據我們檢測的結果來確定執行哪段程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
; html-script: false ] private void updateTime( Calendar date ) { Log.d( TAG, "Update: " + dateFormat.format( date.getTime() ) ); AppWidgetManager manager = AppWidgetManager.getInstance( this ); ComponentName name = new ComponentName( this, TextClockAppWidget.class ); int[] appIds = manager.getAppWidgetIds( name ); String[] words = TimeToWords.timeToWords( date ); for ( int id : appIds ) { int layoutId = R.layout.appwidget; if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ) { if ( getAppWidgetCategory( manager, id ) == WIDGET_CATEGORY_KEYGUARD ) { layoutId = R.layout.keyguard; } } RemoteViews v = new RemoteViews( getPackageName(), layoutId ); updateTime( words, v ); manager.updateAppWidget( id, v ); } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private int getAppWidgetCategory(AppWidgetManager manager, int id) { int category = WIDGET_CATEGORY_HOME_SCREEN; Bundle options = manager.getAppWidgetOptions(id); if (options != null) { category = options.getInt("appWidgetCategory", 1); } return category; } |
這樣處理後,現在我們能確保出現問題的這個方法只在較早版本的Android API中才被呼叫。這可以確保App對之前API版本的相容性。
此外,我們還要做一個小的改動。App最低支援的API版本需用從API 3(Android cupcake 1.5) 改到API 4(Android Donut 1.6)。因為 Build.VERSION.SDK_INT
只在此後的版本才支援。因為現在的App不支援API 3的裝置,所以應該沒有API 3的使用者能使用這個App。我認為這樣做比增加程式碼來支援一個沒人使用的API更明智。
這個問題的出現拖延了我們App的開發時間,然而它向我們展示了能夠追蹤已釋出App中發生問題的工具。同時我想指出的是,在Eclipse的環境下開發沒有很好的lint check的工具,從而導致了這個問題。現在我轉而使用IntelliJ IDEA。在此我學到了,在釋出之前永遠要做lint check。因為它會幫你解決很多潛在問題。
在之後的文章中,我們將會繼續向軟體新增新的特性。
修復軟體App後,App將會本在Google play提供V1.1.1版本下載,原始碼可以在這裡找到。