在一次调试问题中,出现下面错误信息:
09-07 09:15:08.000 1342-1342/? W/System.err﹕ android.os.NetworkOnMainThreadException 09-07 09:15:08.000 1342-1342/? W/System.err﹕ at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1133) 09-07 09:15:08.000 1342-1342/? W/System.err﹕ at java.net.InetAddress.lookupHostByName(InetAddress.java:385) 09-07 09:15:08.000 1342-1342/? W/System.err﹕ at java.net.InetAddress.getAllByNameImpl(InetAddress.java:236) 09-07 09:15:08.000 1342-1342/? W/System.err﹕ at java.net.InetAddress.getByName(InetAddress.java:289) 略。。。。。。
后来百度/Google"android.os.NetworkOnMainThreadException", [1]中在2013-06-24的博文中就指出:NetworkOnMainThreadException是因为应用程序在主线程上尝试网络操作。另外,这个异常在Honeycomb SDK或更高版本的SDK上才会抛出。低版本的SDK允许应用程序在主线程或loop线程执行网络操作,但不鼓励这样做。应该放到子线程中,然后当子线程完成之后再发广播也好,message也好,通知主线程做相应的UI更改,或利用AsyncTask来做网络操作。
这个问题涉及到AndroidManifest.xml文件中的SDK版本配置,而且随着Android Studio缺省使用Gradle来编译,build.gradle文件中也存在相应的SDK版本配置,那么如何来正确地配置这些SDK版本呢?
参考[2], AndroidManifest.xml文件中,<uses-sdk>标签中有minSdkVersion, targetSdkVersion, maxSdkVersion三个属性,由于maxSdkVersion属性已经不再推荐配置,因此下面主要讨论另外两个属性。
首先minSdkVersion与targetSdkVersion都是整数值,通常都需要配置而不要采用默认值。
minSdkVersion是应用程序运行所需的SDK最低版本。如果不指明该属性,其默认值为1。如果Android手机版本低于这个值,应用程序将安装失败。
[3]中通过查看Android源码和两个例子来理解targetSdkVersion:
例子1: DatePickerDialog组件
当AndroidManifest.xml文件中 targetSDKversion属性设置不同的值,DatePickerDialog组件表现会不同。如果设置的值为10或更低的值,DatePickerDialog为左侧显示。如果设置的值为11或更高的值,DatePickerDialog为右侧显示。
Android源码如下(注意:下面代码可能在最新的SDK版本中有变化,但判断targetSdkVersion的逻辑没变):
public DatePickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth, boolean yearOptional) { this(context, context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB ? com.android.internal.R.style.Theme_Holo_Light_Dialog_Alert : com.android.internal.R.style.Theme_Dialog_Alert, callBack, year, monthOfYear, dayOfMonth, yearOptional); }
在第7行根据设置的targetSDKversion来设置不同的theme:getApplicationInfo().targetSdkVersion >= SOME_VERSION。
实际上在Android源码中到处都有类似的判断
。
例子2: Webview类
当设置 targetSDKversion属性为18或更高值时,Webview类的public方法应当在主线程中调用,否则运行时系统将抛出一个RuntimeException
。可参见下面源代码:
sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2; if (sEnforceThreadChecking) { throw new RuntimeException(throwable); }
Android官方文档中提到, 随着Android新版本的发展,一些行为甚至appearances 可能发生改变。从前面两个例子已经可以看出。
总结一下:当设置targetSdkVersion为更高的值时,就像DatePickerDialog例子中那样会有appearance增强的好处,但正如Webview例子那样,以前在低版本中正常运行的代码,如果修改targetSdkVersion为更高值时可能会引发致命错误,但此时在开发阶段就可以修订代码来适应更高的targetSdkVersion。因此targetSdkVersion属性告诉系统该应用已经在target Version系统上进行了充分测试。
但可能targetSdkVersion这个SDK版本中的某个新特性在低版本中是不支持的,那么在低版本的API设备上运行程序时,可能会报错:java.lang.VerifyError。也就是说,此属性不会帮你解决兼容性的测试问题。你至少需要在minSdkVersion这个版本上将程序完整的跑一遍来确定兼容性是没有问题的。
因此,需要在minSdkVersion与targetSdkVersion分别需要将程序完整的跑一遍来确保兼容性没有问题。
build.gradle中会有以下代码:
android { compileSdkVersion 19 buildToolsVersion "21.1.1" defaultConfig { minSdkVersion 8 targetSdkVersion 19 }
AndroidManifest.xml
文件中有以下代码:
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8"/>
Gradle文件会覆盖manifest文件中的值,可以不用管mainfest文件中的值,将minSdkVersion和targetSdkVersion都在Gradle文件中配置即可,也可以删除mainfest文件中的<uses-sdk>标签。
在build.gradle文件中,
compileSdkVersion是编译时Android的API level。
buildToolsVersion是编译器(aapt, dx, renderscript compiler等)的版本. 从版本18开始的每个API level, 会有一个匹配的.0.0编译器版本。例如在IO 2014, 发布了API 20和build-tools 20.0.0。
minSdkVersion与targetSdkVersion在前面已经讨论过。
注意:targetSdkVersion不应当超过compileSdkVersion。
在[4]中还给出了一种统一各个版本的方法:
编辑 gradle.properties
文件,增加以下代码:
ANDROID_BUILD_MIN_SDK_VERSION=14 ANDROID_BUILD_TARGET_SDK_VERSION=21 ANDROID_BUILD_TOOLS_VERSION=21.1.3 ANDROID_BUILD_SDK_VERSION=21
然后,就可以在 build.gradle
文件中这样使用:
//...android { compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION defaultConfig { minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION) targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) } }//..
在build.gradle文件中,除了上述与版本相关的配置以外,在dependencies中也涉及编译期间的版本配置,这留待后续再学习研究:
dependencies { compile 'com.android.support:support-v4:20.0.0' // your unit test dependencies as described in the article unitTestCompile files("$project.buildDir/classes/release") unitTestCompile 'junit:junit:4.11' unitTestCompile 'com.google.android:android:4.1.1.4' unitTestCompile 'org.robolectric:robolectric:2.1.1' // duplicate these dependencies in the instrumentTestCompile scope // in order to have the integration in Android Studio (autocompletion and stuff) instrumentTestCompile 'junit:junit:4.11' instrumentTestCompile 'org.robolectric:robolectric:2.1.1' }
5. 参考资料
[1] android.os.NetworkOnMainThreadException的解决方案, 2013-06-24, http://www.cnblogs.com/kissazi2/archive/2013/06/24/3153307.html
[2] Android中<uses-sdk>属性和target属性分析, 2014-06-03, http://blog.csdn.net/fuzhengchao/article/details/28121193
[3] Android Min SDK Version vs. Target SDK Version, http://stackoverflow.com/questions/4568267/android-min-sdk-version-vs-target-sdk-version
[4] How do I add a library project to the Android Studio?http://stackoverflow.com/questions/16588064/how-do-i-add-a-library-project-to-the-android-studio/16639227#16639227