如何解決安卓開發中最常見的13個錯誤?
Chinese (Traditional) (中文(繁體)) translation by tianma (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,這樣就可以過濾出錯誤資訊。



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 文件,然後設置multiDexEnabled為true。
1 |
android {
|
2 |
defaultConfig {
|
3 |
minSdkVersion 21 |
4 |
multiDexEnabled true |
然而假如你的minSdkVersion為20或者更低,那麼你需要增加屬性multiDexEnabled true,然後增加multidex支援函式庫的依賴。
1 |
dependencies {
|
2 |
compile 'com.android.support:multidex:1.0.1' |
3 |
} |
下一步得看你是否重寫了Application類。
假如你的工程重寫了Application類,那麼你打開Manifest檔,然後增加如下的標籤: <application>
1 |
<application
|
2 |
android:name="android.support.multidex.MultiDexApplication" > |
3 |
... |
4 |
... |
5 |
... |
6 |
</application>
|
假如你的工程沒有重寫Application,那麼你需要繼承MultiDexApplication:
1 |
public class MyApplication extends MultiDexApplication |
最後假如你的工程重寫了Application,但是沒有改變基類,那麼你可以通過重寫attachBaseContext()方法,並且調用MultiDex.install(this)來打開multidex,比如,
1 |
@Override
|
2 |
protected void attachBaseContext(Context base) { |
3 |
super.attachBaseContext(base); |
4 |
MultiDex.install(this); |
5 |
}
|
3. Please Choose a Valid JDK Directory
當你在build你的app時遇到這個錯誤,那意味著AS正試圖找到JDK在電腦中的安裝位置。
解決這個錯誤的方法
- 從AS工具列選擇File > Project structure…
- 從左側功能表選擇SDK Location
- 勾選Use embedded JDK



如果還是沒有解決問題,那麼選擇File > Project structure… > SDK Location, 然後手動輸入JDK的完整路徑。如果你不知道JDK安裝在電腦的哪個地方,你可以打開Mac系統的終端或者Windows的cmd命苦提示符,輸入命令:
1 |
/usr/libexec/java_home |
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視窗,比如,
1 |
cd /Users/Downloads/adt-bundle-mac/sdk/platform-tools
|
按循序執行下麵兩條命令來重啟adb進程:
1 |
./adb kill-server |
1 |
./adb start-server |
全部重啟
假如這些方法都失效,嘗試斷開設備,然後重新連接設備,重啟設備,重啟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。
1 |
buildTypes {
|
2 |
release {
|
3 |
|
4 |
//Enable ProGuard// |
5 |
|
6 |
minifyEnabled true |
7 |
|
8 |
//Since we want to reduce our APK size as much as possible, I’m using the settings from the proguard-android-optimize.txt file// |
9 |
|
10 |
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
11 |
|
12 |
} |
13 |
} |
14 |
|
15 |
} |
- 使用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:tint和tintMode屬性,你可以將一幅圖片修改為不同的顏色,通過使用android:fromDegrees,android:toDegrees,android:pivotX和android:pivotY來旋轉你的圖片。 優化你的庫。嘗試刪除你工程中不需要的或者是記憶體密集型的庫。 假如你確實需要一個很大的庫,那麼你可以檢查一下是否存在針對手機的任何可以優化這個庫的方法,因為一般擴展庫的代碼並不是為手機設計的。 還有一點你需要知道的是,許多庫包含大量的當地語系化字串。 如你的app並不支持這些庫,那麼你可以通知Gradle不要將這些字串引入到你編譯的apk中,以此來減小庫的大小。 為了指定你的app官方支援的語言,打開module的 build.gradle 檔並且使用
resConfigs屬性。比如說我們只想引入英語字串到我們的工程:
1 |
android {
|
2 |
defaultConfig {
|
3 |
resConfigs "en" |
考慮一下你的apk是否包含大量的單個用戶下載了但是卻從未使用過的內容。比如說,hdpi螢幕尺寸的手機不會經常使用
xxxhdpiassets! 減小你apk大小的最有效方法之一是將apk分為多個apk,所以當用戶下載你的apk的時候,所以它們下載到的apk裡面的代碼和資源檔只適用於他們的設備。 更多資訊請查看 creating APKs that target different screen densities and specific ABIs(應用二進位介面)安卓官方文檔。
6. ActivityNotFoundException
當你調用startActivity(Intent)或者類似的調用失敗的時候會出現該錯誤,因為Activity無法執行給定的Intent。
大多數情況下出現該錯誤的原因是忘記在設定檔聲明activity,所以打開你的設定檔,檢查你是否聲明了所有的activity。 還要檢查你是否正確的聲明了activity,你可以使用完全合適的類名,或者帶包名的類名。比如說下面兩種情況都是有效的:
1 |
<activity android:name=".MainActivity"> |
1 |
<activity android:name="com.jessicathornsby.myapplication.MainActivity"> |
果你還是發現不了出現該問題的原因,也有可能是其他潛在的原因。首先,如果你是將Activity從一個包移到另一個包的時候,那麼你只需要clean and rebuil工程,因為AS搞不清楚了。
除此之外,如果Activity裡面的錯誤沒有被正確的載入,也會出現該錯誤。為了檢查是否是這種情況,使用try-catch代碼塊:
1 |
try { |
2 |
|
3 |
//Your code here//
|
4 |
|
5 |
} catch ( ActivityNotFoundException e) { |
6 |
e.printStackTrace(); |
7 |
|
8 |
}
|
再次運行app,看看AS的Logcat Monitor視窗是否在創建Activity的時候捕獲了異常。如果是這種情況,解決就行了。
7. ClassCastException
該錯誤與Java的類型轉換機制有關,該機制允許你將變數從一種類型轉換成另外一種類型。 如果你轉換的時候類型不一致,就會出現該錯誤。比如說,下面兩種情況都會導致該錯誤:
1 |
Object x = new Integer(0); |
2 |
System.out.println((String)x); |
1 |
ImageView image = (ImageView)context.findViewById(R.id.button); |
一般錯誤資訊都會給出錯誤所在行,所以在工程中定位,找出錯誤解決它。
假如你還不能發現該錯誤,那麼你想一下你最近是否移動了layout資源檔裡面的View,這是因為曾經有用戶報告在重新佈局View後出現此錯誤。假如你懷疑是這種情況,那麼操作clean/rebuild來告訴AS重新生成資源檔。 這樣就可以強制AS重新註冊layout資源檔裡面的變更,即解決ClassCastException錯誤。
8. NullPointerException
在Java中,當你聲明了一個變數,你實際上創建了一個指向某物件的指標。你可以聲明某個物件指向某塊未知數據,並且給該對象分配null值。 null值在編寫設計模式的很有用,但是當你遇到NullPointerException(NPE)錯誤,那麼表示你正在使用指向null值的引用,儘管也是一個物件。 由於該引用指向的地方沒有代碼可以執行,你將以NPE結束。
一般NPE異常會被捕獲,所以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中,比如:
1 |
runOnUiThread(new Runnable() { |
2 |
@Override
|
3 |
public void run() { |
4 |
|
5 |
//Update your UI//
|
6 |
|
7 |
}
|
8 |
|
9 |
});
|
或者你可以使用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(),比如:
1 |
@Override
|
2 |
protected void onDestroy() |
3 |
{
|
4 |
super.onDestroy(); |
5 |
if(pDialogue!= null) |
6 |
pDialogue.dismiss(); |
7 |
|
8 |
}
|
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,它開始自動記錄三方面資訊。



由於我們關心app的記憶體使用情況,點擊Memory,打開Memory Profiler。
Memory Profiler由時間線組成,顯示app當前分配的不同類型的記憶體,比如說Java, native和stack. 。上圖中你會發現一排圖示,可以出發不同的操作:
- 強制回收垃圾
- 獲取app的Hprof快照。app堆裡面的所有物件的快照,包括app正在分配的物件,分配的物件的數量,還有這些物件佔有的空間。
- 記錄記憶體分配。當執行某個動作的時候,記錄app記憶體分配情況,你可以找出消耗過多記憶體的操作。



為了找出app中導致OutOfMemoryError錯誤的地方,花些時間與app交互,監控一下,app在不同的操作時記憶體分配的變化情況。一旦你找到了導致問題的地方,花時間修改好以避免記憶體洩露以及記憶體不足。
結語
在這篇文章中,我們探索了安卓開發中最常見的13個錯誤。我們討論了各種導致該錯誤的所有原因,以及解決錯誤的步驟。
假如困擾你的錯誤在這裡找不到,那麼你首先應該做的是複製粘貼整個錯誤資訊到穀歌,這樣就可以找到別人是怎麼解決這個問題的。
並且,假如你在網上找不到任何解決方法,你可以直接去安卓社區尋求,把你的問題發到Stack Overflow。
下面是安卓開發的其他文章


Android SDKHow to Create an Android Chat App Using FirebaseAshraff Hathibelagal

Android SDKSending Data With Retrofit 2 HTTP Client for AndroidChike Mgbemena

Android SDKCode an Image Gallery Android App With GlideChike Mgbemena

Android SDKGet Started With RxJava 2 for AndroidJessica Thornsby





