在前面的文章中,我们一直致力于开发一个可以在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版本下载,源代码可以在这里找到。