談坑,關於「Android」的「Split(“”)」

Android:Android APPs

RICK
8 min readJul 22, 2020

概要

本篇文章是要聊一個在「Android」上才有的「Bug」,首先,我們先來看一段程式碼:

System.out.println("ABC".split("").length);

這段程式碼的意思是用「任意字元」對字串「ABC」進行切割,並求返回其結果陣列的長度。

那事情是這樣的,如果我將上面那段程式碼放到「Android Studio」中執行,結果如下:

印出「4」,對,我想你應該我一樣滿臉問號,WTF,其結果不是「3」嗎?

是的,理論上是要印出「3」,但它偏偏就印出了個「4」,所以它的確就是個 Bug,同時也是我們這篇文章要談的事情。

正文

秉著「打破沙鍋問到底」的精神,我們來追一下這個「Bug」。

首先,筆者先將這段程式碼放到「IntelliJ IDEA」中執行,呃⋯你說為什麼不用記事本和指令?

對不起,筆者患有「IDE」成癮症。

執行結果如下:

是的,結果印出「3」,符合預期的結果,但回想一下剛剛的時間,是不是分分鐘懷疑人生?

是的,忘掉「Android」,人生就會這麼幸福美滿;寫程式就是這麼有深度,一道並重科技與哲學的課題。

但懷疑人生的事情多的是,這邊先暫停一下,我們先繼續抽絲剝繭的追。

因為在「Android Studio」與「IntelliJ IDEA」的執行結果不同,而其中差異可能會影響程式碼編譯結果的就應該只有「JDK」。

因此,筆者判斷是「API」的實作上出了問題,廢話不多說,直接點開原始碼;在「Oracle JDK」的部分,結果如下:

因為實作的內容有點多,我就不展開了;接著是「Google OpenJDK」:

有看到差異嗎?

我們可以很明顯從註解與程式碼得知,「Pattern.fastSplit()」就是其中的差異。

「Pattern.fastSplit()」方法是「Google OpenJDK」專門為「Android」系統撰寫的。

事實上,追到這大概就可以推測出「split(“”)」這個「Bug」有很大的機率是「Google」在優化「OpenJDK」時,意外所導致的「Bug」;也就是說,這「鍋」該是「Google」揹。

在此先暫停一下,我們先來講個古,這故事要從「Oracle」收購「昇陽」開始說起,由於「昇陽」被收購,導致 Java 的版權落到了 Oracle 手裡;但問題來了,雖然「Java」是開源軟體,但「JDK」的使用權卻存在著模糊空間,因此「Oracle」便想藉此向「Oracle JDK」的使用者收取費用。

然而「Oracle」的第一個目標變向準了「Google」。

原因也不難猜,就是 Google 這麼「肥」,隨便卡一下油就好幾個億掉下來,再說了,「Android」系統的確就是「Oracle JDK」的使用者;因此 Oracle 就向 Google 求償近乎天價的版權費。

但 Google 哪是省油的燈,然後就這樣開啟了「Google」與「Oracle」的驚天大戰,那是一場打了十年的版權官司,至今都還沒落幕。

但不論最後結果如何,Google 勢必得還是得根治該問題,而「OpenJDK」就是個不錯的選擇。因此在 Android 7.0 以後,Google 就將 Android 中原本的Oracle JDK 替換成 OpenJDK。

而既然說到 Google 的「OpenJDK」,就不得不說另外一個貢獻者「Apache Harmony」:「Google 在 2007 年宣告使用 Harmony 的類別庫作為 Android 的類別庫。」。

備註:「Harmony」是由「Apache 基金會」所主導的專案之一,目標是以開放原始碼方式,實作出Java SDK,但令人惋惜的是該計畫在 2011 年宣布終止。

事實上,「Harmony」對「Android」的影響還有「Dalvik 虛擬機」;它是「Google」和其合作廠商設計專門為了運行「Android」的核心平台。不過從「5.0」開始,Android 就改採用 Google 自家研發的「Art」作為其執行環境。

備註:「Dalvik」和「Art」都是專門為「Android」設計的虛擬機器,換句話說就是 Android 平台「JVM」;其兩著除了架構不同之外,最主要的差異還是「編譯的技術」,Dalvik 是採用「JIT」,它是一種「動態編譯技術」,或著稱作即時編譯,在這點上「Dalvik」跟「JVM」是相同的;跟「Art」則是採用「AOT」,不同於「即時編譯」,它是一種「預編譯技術」。

講了這麼多,其實我只是要表達一件事情,就是「雖然我們在撰寫 Android APK 是用『Java 或 Kotlin』,但其許多相關的套件都已經被 Google 修改或置換,如許多核心的 Lib 甚至是 VM 等。」。

回到我們的主題,我們繼續追「Bug」;剛剛談到「fastSplit()」這個方法是 Google 自行增加的,那我們就殺上 Google ⋯⋯ 的網站「Android Git」。

簡單說的,它就是 Google 的 Git,找到「platform / libcore」,這邊就是存放核心 Lib 的地方,如下:

基本上,最新版的 Code 是放在 master 上,其包含開發中的程式碼;一般來說,若非要找特定版本,我們就不會去切換 Branche,因此我們就沿著主線往下找,其路徑如下:

路徑的找法,大致上就是參考 Package,如下:

備註:「ojluni」的意思是「OpenJDK, java.lang / java.util / java.net / java.io」。

接著,我們點開「Pattern.java」,然而為了方便我們的查找,我們點擊「log」,如下:

然後找到相對應的「Issue」,如下:

點擊進入就可以看到該「Issue」相關的資訊,如下:

往下拉,看到對應檔案,直接選擇「diff」,如下:

結果如下:

根據上面的訊息,我們可以知道,Google 已經針對這個問題做了處理,但修正後行為只會在「API > 28」;其實我們可以更深入的去看其程式碼的撰寫邏輯,不過我想除非對其「算法」有興趣,不然我們就到此為止吧。

總結

簡單的結論就是「如果我們在程式碼中使用了『split(“”)』這個方法,那在『Android Pie』版本以上所執行的結果與以下所執行的結果是不同的」。

這是件好事嗎?我認為不是,或許知道這問題的人可以避開「split(“”)」的使用或藉由「判斷式」處理掉,但是不知道的人呢?是不是就可能埋下一個潛在的「Bug」?

其實我想表達的是,類似這樣的「Bug」一定不止這個,而 Google 選擇只修正「API 28」版以上,是因為「做不到」呢?還是為了解決「碎片化」的問題,而強迫使用者更新呢?若是前者,那無可厚非,若是後者,那是否有些許「微妙」呢?

但不管 Google 的態度是何者?我都會建議「Android」手機的使用者,若手機有更新版發佈,等其穩定後就更新吧;若手機型號太舊,導致該品牌廠商沒有出「更新版本」,那麼若真的系統版本落差太大,又在經濟許可的情況下,不妨就換一支吧。這樣不僅增加手機的穩定性,也增加安全性。

--

--

RICK
RICK

Written by RICK

當遇到重開機無法解決的 BUG 時,那就試試關機吧。

No responses yet