Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android中的 Multiple dex files define 編譯錯誤引發的思考

Android中的 Multiple dex files define 編譯錯誤引發的思考

編輯:關於android開發

Android中的 Multiple dex files define 編譯錯誤引發的思考


昨天我龍哥問我一個問題,他說如果一個工程中,有一個com.x.A枚舉,導入的第三方jar中也有一個com.x.A枚舉,那麼我在工程中用A枚舉的時候,會用到那個枚舉呢?我當時一想,這個不是類(枚舉是個特殊類)定義沖突嗎?應該在編譯的時候就報錯呢,而且這個問題我之前遇到過,所以我很自信的和他說,這個應該在編譯的時候就報錯,結果他來了一句:沒有呀?運行成功了,而且導入的是工程中的那個枚舉A,我擦,我一想這不是打我臉嗎?我記得非常清楚,是會報錯的呀,所以我就自己寫了一個AndroidDemo工程:

\

工程很簡單,那麼運行一下:

\

媽蛋,果然報錯呀,編譯就失敗了,所以我又很有底氣的去找他理論,說我這都可以的呀,然後他也說沒有問題,那麼問題就陷入僵局了,但是我不能這麼算了呀,我就懷疑是環境的問題?工程的問題?編譯器的問題?一頓懷疑之後,那就嘗試,首先,猜測我們兩的調用方式是否一樣,他使用maven的,而我用的是build paths方式的,這個有點不同,所以我就叫他改成我這樣的,同時他使用注解的方式用NodeCode的,我是用NodeCode a = NodeCode.A方式,然後叫我哥把用法改成和我一模一樣的,編譯一下之後,他還是沒有報錯?那麼是不是環境問題呢?他用的是idea,我用的是Eclipse,然後我用idea工具試了一下,也是報錯的,那麼不是編輯器的問題,蛋疼了一會之後,仔細看看那個錯誤,發現是dex字眼,突然想到,我哥是開發Web的,我是開發Android的,他的工程是Web工程,我是Android工程,這是不是問題呢?所以我立馬新建一個Java工程來測試一下(其實Java工程和JavaWeb工程都一樣,因為都是用JVM虛擬機的,用javac進行編譯的):

\

運行:

\

尼瑪,盡然可以,擦,果然是工程問題,而且,我們在工程中的NodeCode中打印信息

\

發現打印的信息,可以得知,默認優先導入的是工程中的那個枚舉類。

 

那麼問題弄清楚了,Android中項目不可以,Java項目可以

但是我們得看看為什麼呢?所以只能通過源碼去看問題了,但是在看源碼之前,其實我們可以猜測一下問題的根源?

Android中的是dex文件,我們知道dex文件是用dx命令將多個class文件合成得到的,所以dex是一個文件,他有具體的格式,關於dex的格式說明,不了解的同學可以查看這篇文章:http://www.wjdiankong.cn:8888/blog/?p=508,而我們知道一個jar或者是運行的Java工程,都是編譯之後在bin目錄下的class文件,我們猜測編譯時都報錯了,那麼肯定是發生在dx執行的那一塊,所以我去找dx命令的源碼,源碼位置:源碼目錄\dalvik\dx\src\com\android\dx\command

主要看main方法:

\

我們再到這個類看看:com.android.dx.command.dexer.Main

入口方法main:

\

這裡解析命令行參數,然後執行run方法:

\

我們看到這裡有一個很重要的方法,就是合並引用第三方的jar的dex內容,正是我們想要知道的結果,進入看看:

\

方法的注釋:

/**
* Merges the dex files in library jars. If multiple dex files define the
* same type, this fails with an exception.
*/

到這裡,其實我們看到這個方法的注釋說明,就知道了如果定義了相同的types就會拋出一個異常,我們在進入看看什麼類型,拋出來的異常和我們看到的是一樣嗎?

有一個重要的類:DexMerger 位於:com.android.dx.merge.DexMerger

這個類的構造方法會傳遞兩個DexBuffer進去,第一個DexBuffer是我們之前一定操作好的dex內容,第二個DexBuffer是我們需要合並的Library的dex內容,構造好類之後,在調用merge方法:

\

merge方法中調用了mergeDexBuffers方法:

\

我擦,看到這裡是不是有點熟悉,merge很多方法,而且這些merge的動作就是我們之前解析dex文件格式中說道的那幾種類型,但是這裡我們是類定義沖突,那麼肯定是看mergeClassDefs方法:

\

在這個方法中會先得到有序的type類型,然後在設置classDefs區域的偏移值和大小,在看看getSortedTypes方法:

\

這裡其實是將dexA和dexB中的typeIds進行排序合並,在看看readSortableTypes方法:

\

好吧,終於看到核心的內容了,在這裡會判斷這個type是否已經存在了,如果存在的話,就會拋出異常信息,而且這個異常信息就是和我們之前看到的一樣:

\

到這裡我們可以知道,dex文件中是不允許有相同的類(包名+類名),而且這裡的包名+類名就是type。這個在之前解析dex文件格式中有說道,這裡就不解釋了。

其實我們可以想想,dex是一個文件,他有自己的文件結構,相同的內容是不可能存在的,會出現沖突。

所以現在我們知道為何Android工程不行,原因就是dx在進行class到dex轉化的時候就出錯了,也就是發生在編譯期。

但是在Java工程中是可以的,其實想一下還是合理的,因為如果我們是將一個Java工程變成一個Jar文件,其實jar文件其實是一個壓縮包,壓縮包裡面是不會存在相同的文件的(包名會轉化成指定的文件目錄),如果有的話,也不會出現沖突,不會出現問題的,但是我們從上面的例子可以看到,會優先導入本工程中的類,所以這個可以查看一下jar工具的源碼,地址:

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/tools/jar/Main.java

\

看這裡的main方法,其實他內部用了ZipFile/ZipEntry/ZipInputStream這三個類,來組件一個jar的,操作也很簡單,首先得到所有的class文件,然後將包名轉化成文件目錄即可。但是這裡並沒有找到如果有相同的第三方jar包含相同的類,會不會進行覆蓋的代碼的地方,所以這一步靠大家了,但是從上面的例子可以看到,默認用的是工程中的那個類。

這裡在多說一句吧,就是我們在導出一個jar的時候,如果要攜帶第三方的jar的話,導出來我所知道的是兩個方法:

1、使用Eclipse插件:fat_jar

\

2、使用ant腳本跑出來一個

\

關於網上還有一個方法就是用Eclipse自帶的Export,但是需要自己寫一個MANIFEST.MF文件,不過這種方法我沒成功過,不知道行不行,不過導出來攜帶第三方的jar的jar,應該是這種樣式:

\

其實,我們可以看出來上面的那兩種方式的原理:

首先將本工程中的class文件變成jar,然後在操作libs下的第三方的jar內容,那麼下面就是相當於將多個jar進行合並的操作,其實這個就很簡單了,我們自己都可以寫一個程序,使用ZipFile+ZipEntry即可,如果發現有相對應的ZipEntry的話,就跳過即可,那麼最終就可以合並多個jar了。當然這個只是我們的猜想,但是從上面的那個例子可以看到,Java工程可以導入多個包含相同類的jar,不會發生沖突,但是Android工程不行,原因是dx將多個class轉化成dex時會進行判斷。

需要思考的問題:

當遇到這個問題的第一時間應該想到是編譯器出現的問題,所以這個和Android中的DVM和Java中的JVM沒有關系,因為虛擬機是在運行期才會用到。所以我們應該從Android的編譯過程去看問題,這是解決問題的思路問題。

得到的知識點:Android工程中是不允許存在相同的類,Java工程是可以的

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved