Advertisement
  1. Code
  2. Android

如何解决安卓开发中最常见的13个错误?

by
Difficulty:BeginnerLength:LongLanguages:

Chinese (Simplified) (中文(简体)) translation by Zhang Xiang Liang (you can also view the original English article)

测试是安卓开发中至关重要的一部分,测试能够在你对外发版之前发现所有潜伏在app中的bug,错误和性能问题。

每当你遇到问题的时候,安卓会产生一个错误信息,要么显示在AS(Android Studio)的Logcat Monitor窗口,要么在你测试app的设备上显示。

这些错误信息比较典型,它们短而切中要害,一眼望去似乎毫无帮助。然而这些信息实际上包含了你用来追踪工程代码的所有信息,你只需要知道如何解读它们。

在这篇文章中,我们将研究13个在安卓开发中最常遇到的错误。我们将仔细观察每一条错误信息,弄清楚含义,可能的原因,还有最重要的,一步一步叫你怎么解决这些错误。

Spotting Error Messages

当测试app时可能会遇到各种各样的错误信息,从第一次安装在目标设备时导致app崩溃的严重错误,到不断降低app性能的小问题。

根据你遇到的错误类型,安卓会将错误信息显示在AS或者设备上。

显示在物理设备或者AVD上的错误信息比较简单,你只需要注意显示在设备屏幕对话框上的错误信息。然而出现在AS上的错误信息比较困难,因为Logcat Monitor记录了大量的信息,我们很容易错过重要的信息。

想不要错过任何重要因信息,最简单的方法是打开Logcat Monitor的Verbose下拉菜单并选择Error,这样就可以过滤出错误信息。

Open Logcat Monitors dropdown and select Error

1. R.layout.main Cannot Be Found / Cannot Resolve Symbol R

当AS不能正确的生成R.java文件时导致该错误,该错误随时都可能发生,这一分钟还运行良好,可能下一分钟工程的每个部分都编译报错。更糟的是,当AS遇到此问题,所有的R.layout资源文件都报错,这使得AS很难知道哪里才是代码错误开始的地方。

一般来说,最有效的方法是最简单的:clean 然后rebuild工程。在AS工具栏选择Build > Clean Project ,稍等一下,然后选择Rebuild Project来重新构建工程。

假如一次完整的操作clean/rebuild没有生效,多试几次。毕竟有开发者反应多次操作后生效了。

如果你移动了一些文件和目录后导致了该问题,可能是AS缓存和你工作工程的R.layout不匹配。你怀疑是这种情况的话,在AS工具看选择File > Invalidate Caches / Restart > Invalidate and Restart

资源文件名也有可能导致该问题,所以确保没有多个资源文件重名,没有文件名包含非法字符。AS支持的合法字符包括小写字母a-z,0-9,句号和下划线,只要有一处文件名含有非法字符,就会导致整个工程出错,即使该文件没有在工程的任何地方使用。

假如你刚发现并解决了一个错误,但是AS仍然显示R.layout错误,你可能需要操作一次clean/rebuild来使得刚才的变化生效。

2. Too Many Field References….Max is 65,536

当你编译app的时候,APK包含可执行的字节码文件,该文件以DEX(Dalvik Executable)字节码形式存在。根据DEX 规格,单一DEX 文件最多有65535个方法,如果你的app方法数量超过这个数,你就会遇到Too many fields…这个错误。 要注意的是,这个方法数量限制指的是你工程中引用到的方法,而不是定义的方法。

如果你遇到这个问题,你可以从以下方法中选择一个:

  • -减少你工程中引用的方法数,而减少引用的方法数量最有效的方法是检查你应用的依赖,因为一般应用的依赖会导致大量的方法引用。
  • -通过打开multidex来配置app使用多个dex文件。

打开multidex支持的过程将依赖于你的工程支持的安卓版本。

假如你工程的目标版本为安卓5.0或者更高,第一步是打开module的build.gradle 文件,然后设置multiDexEnabledtrue

然而假如你的minSdkVersion为20或者更低,那么你需要增加属性multiDexEnabled true,然后增加multidex支持库的依赖。

下一步得看你是否重写了Application类。

假如你的工程重写了Application类,那么你打开Manifest文件,然后增加如下的标签:

假如你的工程没有重写Application,那么你需要继承MultiDexApplication

最后假如你的工程重写了Application,但是没有改变基类,那么你可以通过重写attachBaseContext()方法,并且调用MultiDex.install(this)来打开multidex,比如,

3. Please Choose a Valid JDK Directory

当你在build你的app时遇到这个错误,那意味着AS正试图找到JDK在电脑中的安装位置。

解决这个错误的方法

  • -从AS工具栏选择File > Project structure…
  • -从左侧菜单选择SDK Location
  • -勾选Use embedded JDK
Navigate to File Project structure SDK Location and select the Use embedded JDK checkbox

如果还是没有解决问题,那么选择File > Project structure… > SDK Location, 然后手动输入JDK的完整路径。如果你不知道JDK安装在电脑的哪个地方,你可以打开Mac系统的终端或者Windows的cmd命苦提示符,输入命令:

4. Error Installing APK

虽然AVD可以从不同的硬件和软件版本来测试你的app,但你还是要至少在一个物理设备(手机或平板)上测试app。然而AS有时候找不到连接的设备。

如果你将设备连接到电脑上并且安装apk时报 Error installing APK错误,或者设备根本不出现在Select Deployment Target窗口,你可以尝试如下方法:

-检查USB调试模式是否打开。

具体步骤为,打开设备的设置Settings,点击开发者选项,打开USB调试。如果设置中没有开发者选项,那么可以在 关于手机里面连续点击版本号直至出现开发者选项。 再次查看设置,就能看到开发者选项了。

-检查手机或平板的屏幕

当你将设备连接到电脑上的时候有时需要你点击屏幕来作选择。比如说要你选择不同的模式或者授权手机连接到电脑。

-确保你的电脑上安装了设备的驱动。

如果你是在Windows下开发,你可能需要为设备安装合适的驱动程序。如果你的设备是Nexus,那你可以直接通过AS的SDK管理来下载谷歌驱动

-检查设备是否满足你工程的最小SDK要求。

在module的gradle.buil里面查看最小SDK,然后在设备的设置-关于手机里面查看安卓版本。

-尝试重启adb (Android Debug Bridge) 进程

打开终端或cmd,然后切换目录 (cd),然后进去platform-tools窗口,比如,

按顺序执行下面两条命令来重启adb进程

-全部重启

假如这些方法都失效,尝试断开设备,然后重新连接设备,重启设备,重启AS,最后重启电脑。

5. INSTALL_FAILED_INSUFFICIENT_STORAGE

如果你在尝试安装app的时候遇到这个错误,这说明你的设备内存不足。

如果你尝试在AVD上面安装app,那么你应该检查你给这个虚拟机分配了多少内存:

  • -打开虚拟机管理
  • -在question中找到AVD,点击它旁边的图标Edit this AVD
  • -出现对话框后,点击Show Advanced Settings.
  • -滚动到Memory and Storage部分.

该部分列出了分配给虚拟机的各种类型的内存,只有这其中有一个值过低的话,你就应该增大,直到能够支持典型的安卓手机或平板。下面是各种类型的内存:

  • RAM.虚拟设备可用的RAM大小
  • VM Heap.有多少堆空间(内存)被分配给手机或平板的虚拟机(VM)。
  • 内置内存。设备不可移除的内存大小。
  • SD卡。可移除内存的可用大小 假如你想使用AS管理的虚拟SD卡,选择Studio-managed,然后进入虚拟SD卡的大小(最小为100MB)。  或者,你可以在一个文件里管理SD卡空间,选择External file,然后指定你想要使用的位置。

如果你虚拟机内存足够的话,或者你正在物理设备上安装app的话,那么这个错误往往表明你编译的app太大了。如果一个app在运行的时候占用设备内存太多的话,这不是一件好事情。

假如你需要减少app大小的话,你可以尝试如下的方法:

  • 使用ProGuard来删除被使用到的类文件,域,属性和方法。通过打开module的build.gradle 文件并添加如下代码,可以打开ProGuard。

  • 使用aapt来通过无损压缩优化图片,或者使用一个能减少PNG 文件(zopflipng, pngcrush, OptiPNG, TinyPNG, or pngquant)大小或者JPEGs(packJPG)的软件。或者你可以尝试使用WebP格式的图片来替换PNG或者JPEG文件。
  • 记住要从发布版本的app中删除所有的调试相关信息。安卓运行的时候不需要这些信息,他们仅仅是占了内存空间。
  • 检查你的工程,看是否有重复的资源。即使是轻量级的资源,比如说字符串,最终会增加你apk的大小。
  • 使用Lint来检查没有在你代码中引用的资源文件,并且删掉这些资源文件。运行Lint的步骤,从AS的工具栏打开Analyze > Inspect Code... 。
  • 在工程的build.gradle 增加shrinkResources true 来打开资源瘦身功能。
  • 假如你需要使用相同图片的不同版本,那么你可以使用相同的原始图片,但是你需要在运行的时候自定义,而不是多次增加相同的图片到你的工程。比如说,通过使用android:tinttintMode属性,你可以将一幅图片修改为不同的颜色,通过使用android:fromDegrees, android:toDegrees, android:pivotX和 android:pivotY来旋转你的图片。
  • 优化你的库。尝试删除你工程中不需要的或者是内存密集型的库。 假如你确实需要一个很大的库,那么你可以检查一下是否存在针对手机的任何可以优化这个库的方法,因为一般扩展库的代码并不是为手机设计的。 还有一点你需要知道的是,许多库包含大量的本地化字符串。 如你的app并不支持这些库,那么你可以通知Gradle不要将这些字符串引入到你编译的apk中,以此来减小库的大小。 为了指定你的app官方支持的语言,打开module的 build.gradle 文件并且使用resConfigs属性。比如说我们只想引入英语字符串到我们的工程:

  • 考虑一下你的apk是否包含大量的单个用户下载了但是却从未使用过的内容。比如说,hdpi屏幕尺寸的手机不会经常使用xxxhdpi assets! 减小你apk大小的最有效方法之一是将apk分为多个apk,所以当用户下载你的apk的时候,所以它们下载到的apk里面的代码和资源文件只适用于他们的设备。 更多信息请查看 creating APKs that target different screen densities and specific ABIs(应用二进制接口)安卓官方文档。

6. ActivityNotFoundException

当你调用startActivity(Intent)或者类似的调用失败的时候会出现该错误,因为Activity无法执行给定的Intent

大多数情况下出现该错误的原因是忘记在配置文件声明activity,所以打开你的配置文件,检查你是否声明了所有的activity。 还要检查你是否正确的声明了activity,你可以使用完全合适的类名,或者带包名的类名。比如说下面两种情况都是有效的:

如果你还是发现不了出现该问题的原因,也有可能是其他潜在的原因。首先,如果你是将Activity从一个包移到另一个包的时候,那么你只需要clean and rebuil工程,因为AS搞不清楚了。

除此之外,如果Activity里面的错误没有被正确的加载,也会出现该错误。为了检查是否是这种情况,使用try-catch代码块:

再次运行app,看看AS的Logcat Monitor窗口是否在创建Activity的时候捕获了异常。如果是这种情况,解决就行了。

7. ClassCastException

该错误与Java的类型转换机制有关,该机制允许你将变量从一种类型转换成另外一种类型。 如果你转换的时候类型不一致,就会出现该错误。比如说,下面两种情况都会导致该错误:

一般错误信息都会给出错误所在行,所以在工程中定位,找出错误解决它。

假如你还不能发现该错误,那么你想一下你最近是否移动了layout资源文件里面的View,这是因为曾经有用户报告在重新布局View后出现此错误。假如你怀疑是这种情况,那么操作clean/rebuild来告诉AS重新生成资源文件。 这样就可以强制AS重新注册layout资源文件里面的变更,即解决ClassCastException错误。

8. NullPointerException

在Java中,当你声明了一个变量,你实际上创建了一个指向某对象的指针。你可以声明某个对象指向某块未知数据,并且给该对象分配null值。 null值在编写设计模式的很有用,但是当你遇到NullPointerException(NPE)错误,那么表示你正在使用指向null值的引用,尽管也是一个对象。 由于该引用指向的地方没有代码可以执行,你将以NPE结束。

一般APE异常会被捕获,所以Logcat Monitor窗口可以看到错误具体在哪行。 在工程中定义到错误行,并且找到空引用。然后你需要找到赋值的地方并赋值。

如果请求的View没有被找到,findViewById方法也会返回null值,所以如果NPE所在行包含findViewById,那么检查是否初始化了包含该View的布局。还要检查findViewById里面的拼写错误或其他小错误,这也可能导致NPE错误。

为了避免你的工程中出现NPE错误,确保所有的对象在使用前被初始化了,还要经常验证使用某对象的方法或属性不为空。

9. Application Not Responding Error

该错误以对话框的形式出现在安卓设备或AVD上。如果5秒钟内未对用户输入做出响应就会出现Application Not Responding (ANR) 。 当你的app在UI主线程执行冗长的或密集型的操作就会出现ANR。

在安卓里,UI主线程负责将所有的用户输入事件分配给合适的UI部件,还负责更新app的UI。然而,主线程一次只能处理一个任务,所以假如长时间运行或密集型操作阻塞了主线程,那么UI将完全无响应直到该任务被完成。

假如你在测试app的时候遇到ANR,那么你需要看看在主线程上执行的操作。然而假如你没有遇到ANR,但是你注意到app有时很慢或很卡,那么这表明你即将遇到ANR,那么你应该再次看看你的UI线程的状态。

为了解决ANR错误(或者类似的错误),你需要检查所有的操作是否会导致运行减慢或需要大量的性能处理,如果有请将它们从主线程移走。即移到一个工作线程,在工作线程里面的这些操作就不会阻塞主UI线程了。

创建子线程有许多方法,其中最简单的方法是AsynTask,因为该类已经包含了自己的工作线程,和一个回调,在该回调里与安卓UI主线程通信。

然而AsyncTasks更适合简短的操作,所以假如你需要执行长时间的操作,那么你应该使用Service 或者IntentService 来代替。

尽管从主线程移走长时间运行和密集型任务能够很大程度改善app性能,我们还是尽可能在主线程中做更少的工作。即使是在主线程运行少量非必需的代码也会影响app响应速度,所以你一旦找到长时间运行和密集型的操作,你应该看看是否可以从主线程中移除更多的代码。

10. Only the Original Thread That Created a View Hierarchy Can Touch Its Views

在安卓中,你只可以在主线程中更新UI。假如你想从其他线程中访问UI元素,那么你就会遇到这个错误。

如果你想解决这个问题,你要找出尝试更新UI的后台任务并把它放到runOnUiThread中,比如:

或者你可以使用handler或者在AsyncTask中执行后台工作,使用AsyncTask的onPostExecute()的回调与主线程通信。最后假如你发现你频繁切换线程,那么你可能需要RxAndroid,使用该库你可以创建一个新线程,在这个线程中制定要操作的工作计划,然后仅仅几行代码就可以把执行结果发送到主线程。

11. NetworkOnMainThreadException

当你尝试在主线程执行网络操作的时候抛出该异常,比如说发送API请求,连接远程数据库或者下载文件。由于网络操作耗时并且是劳动密集型,它们更容易阻塞线程,所以Android 3.0 (Honeycomb) 和更高版本在你尝试在主线程进行网络操作的时候抛出该异常。

假如你遇到NetworkOnMainThreadException错误,那么找到主线程中的网络请求代码并移到单独的线程。

假如你需要频繁的进行网络请求,那么你需要看看Volley,它是一个HTTP库,初始化自己的后台线程,所以所有的网络请求默认在主线程以外进行。

12. Activity Has Leaked Window That Was Originally Added Here

如果你在退出Activity后尝试显示对话框就会出现该错误。如果你遇到此错误,那么打开Activity,确保你正确地关闭对话框,正确的做法是在Activity的onDestroy() 或onPause() 方法里调用dismiss(),比如:

13. OutofMemoryError

当app请求内存而系统无法满足的时候出现该错误。假如你遇到这个错误,那么从改掉所有常见没错管理开始。检查你是否反注册了所有的广播接收者,是否停止了所有的services,确保你没有在静态成员变量里持有引用,确保你没有尝试载入超大bitmaps。

假如你排除了所有导致该错误的明显原因,那么你需要深挖和仔细检查app如何分配内存,正好趁此机会优化app内存管理机制。

AS有整个区域来帮助你分析app内存使用情况,在AS工具栏选择View > Tools Window。此时你会看到Android Monitor 或者Android Profiler选项,这依赖于你安装的 AS版本。

我们之前在这个网站讨论了 working with the Memory Monitor ,但是由于Android Profiler 是AS新增的特性,所以我们快速介绍下它。

当你打开Android Profiler,它开始自动记录三方面信息。

The Android Profiler tracks the apps CPU Memory and Network information automatically

由于我们关心app的内存使用情况,点击Memory,打开Memory Profiler

Memory Profiler由时间线组成,显示app当前分配的不同类型的内存,比如说Java, nativestack. 。上图中你会发现一排图标,可以出发不同的操作:

  • -强制回收垃圾
  • -获取app的Hprof快照。app堆里面的所有对象的快照,包括app正在分配的对象,分配的对象的数量,还有这些对象占有的空间。
  • -记录内存分配。当执行某个动作的时候,记录app内存分配情况,你可以找出消耗过多内存的操作。
The Memory Profiler displays the different kinds of memory your app is allocating

为了找出app中导致OutOfMemoryError错误的地方,花些时间与app交互,监控一下,app在不同的操作时内存分配的变化情况。一旦你找到了导致问题的地方,花时间修改好以避免内存泄露以及内存不足。

结语

在这篇文章中,我们探索了安卓开发中最常见的13个错误。我们讨论了各种导致该错误的所有原因,以及解决错误的步骤。

假如困扰你的错误在这里找不到,那么你首先应该做的是复制粘贴整个错误信息到谷歌,这样就可以找到别人是怎么解决这个问题的。

并且,假如你在网上找不到任何解决方法,你可以直接去安卓社区寻求,把你的问题发到Stack Overflow

下面是安卓开发的其他文章

关注我们的公众号
Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.