這一篇的技術成份稍微高一點點。要談到的功能,從一開始開發瀏覽器就有想要做,但是一直找不到比較好的實作方式。在經過兩三週忙於其他的功能開發後,終於在這週找到比較恰當的切入點和相關技術的參考,得以完成心目中大致上的效果。
講了一堆廢話,究竟是什麼功能呢?
不知道閱讀模式的人,可以看一下下面的文章介紹。這功能幾年前 Apple 在 Safari 瀏覽器中推出,讓使用者可以更專心地閱讀網頁內容,不被廣告和不必要的元件(標題欄,底部欄,側邊欄位等)干擾。
https://today.line.me/tw/v2/article/9PxxRK
之後,各家瀏覽器大廠也開始推出了類似的功能。 Brave 瀏覽器在推出他們自家的 SpeedReader 功能時,有順便把市面上主要的實作都拿來做比較,有興趣的人可以下載下面的 pdf 檔來了解一下。主要比較了 Readability.js,Safari Reader View, Google Chrome DOM Distiller,BoilerPipe 和他們的 SpeedReader。
https://brave.com/wp-content/uploads/2020/08/speedreader-www19.pdf
對於這功能有了大致的了解後,要來決定一下怎麼開發這功能。第一個想法自然是找 Readability.js 來使用;一來它是 Open Source 的,二來,許多瀏覽器的閱讀模式也是從它延伸而來的。用它的話,網路上可以找到的資源也會比較多一些。所以先上 Github 找了一個早期的版本來用。
https://github.com/Kerrick/readability-js
把 readability.js 放到 Android 的 assets 目錄中,然後利用下面的方式載入檔案,塞到目前的網頁中。由於 readability.js 裡頭已經包含了初始化自己的程式碼,而且會把處理過的內容,直接蓋掉目前的網頁內容,所以只要載入它就等著它把畫面換成比較單純的顯示模式。
原始的網頁 | 套用 Readability.js 的網頁 |
---|---|
這方式實作上雖然很簡單,但是產生出來的效果卻不是很好。第一點是,很多應該不屬於主要內容的部分,還是留在畫面中;第二點是圖片的部分,通常會過大。
字型忽大忽小尚可忽略不去計較,但是該小一點的圖,大得嚇人;和一堆不必要的元素依然存在,這效果很難讓人有想閱讀下去的念頭。
仔細再研究了一下 readability.js 的內容,它在處理 html elements 時,除了會刪除不必要的元素之外,也會把想要留下的元素加上特定的 class 或 id;然後它另外還有一個 readability.css 檔案,應該就是用來規範這些新加的 class 是要怎麼呈現在畫面中的。難怪只有執行 readability.js 的話,跑出來的畫面有點慘不忍睹。
於是接下來的版本,我把 readability.css 也加進去。(也順便把 WebView 從 Java 重構成 Kotlin 檔案,不然改起來有點痛苦)
public void applyReaderMode() {
InputStream jsInput, cssInput;
try {
jsInput = getContext().getAssets().open("readability.js");
byte[] buffer = new byte[jsInput.available()];
jsInput.read(buffer);
jsInput.close();
cssInput = getContext().getAssets().open("readability.css");
byte[] cssBuffer = new byte[cssInput.available()];
cssInput.read(cssBuffer);
cssInput.close();
// String-ify the script byte-array using BASE64 encoding !!!
String encodedJs = Base64.encodeToString(buffer, Base64.NO_WRAP);
String encodedCss = Base64.encodeToString(cssBuffer, Base64.NO_WRAP);
loadUrl("javascript:(function() {" +
"var parent = document.getElementsByTagName('head').item(0);" +
"var script = document.createElement('script');" +
"script.type = 'text/javascript';" +
"script.innerHTML = window.atob('" + encodedJs + "');" +
"parent.appendChild(script)" +
"})()");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
套了之後,效果也沒有因此而改善。所以我又試著去找其他的可能方案。
在開發的過程中,我主要拿來比較閱讀模式效果的 reference app 分別是 Brave Browser 和 Firefox。Brave 在前面的 pdf 中有提到,它們的作法是在畫面還沒有真的繪製之前就可以先處理,速度會比其他的方案快;但缺點是,它的作法相對上也比較複雜,我不見得能夠比照辦理。
所以,我去找了 Firefox App的原始碼來看(早該這麼做了)。原來 Mozilla 也有把它們的原始碼放在 Github 上。而且針對 Readability 的改良版也特地獨立成一個 repository 開發。
https://github.com/mozilla/readability
一開始我很開心地拿了這版本來套用。但又犯了一開始就犯的錯誤。javascript 只處理了資料的去留,但是真正呈現的部分還是需要對應的 css style file 來輔助才行。於是我找到了 Firefox Mobile App 的 repository,也找到了它 reader view 真正實作的地方。
https://github.com/mozilla-mobile/android-components/tree/master/components/feature/readerview
android-components/components/feature/readerview/src/main/assets/extensions/readerview/
目錄下,除了有上述的 readability.js
外,它又包了一層 readerview.js
和輔助的 readerview.css
。這兩者的實作才是真正發揮 Readability 威力的地方。有興趣的人可以進去看一下。大概說就是:readability 把資料處理完變成 article object 後, readerver.js
會拿 article
中的每個資料欄位,一個個貼上特定的 class name,然後在 readerview.css
中,針對這些 class 加上 UI 的呈現方式。
因為 readerview.js
中有很多是跟 firefox App 互動的實作,我不行整個檔案直接拿來套用,所以我是抽取裡頭我需要的程式碼來用而已。抽出來的程式碼都在這兒:
https://github.com/plateaukao/browser/blob/my_version/app/src/main/assets/MozReadability.js#L2261
於是,跟 Firefox App 效果幾乎一樣的閱讀模式完成了!(還有預估閱讀時間要多久,不過這數值感覺不是很準確)
原本的網頁 | 套用後的閱讀模式 |
---|---|
看到右邊的畫面時,有沒有一種很感動的感覺。畫面中的 header, footer,廣告等會吸引眼球注意力的元件都被去除了,只留下中間的內容部分。在一個自製的小 App 中能實作這樣子的功能真的很令人感動。
參考原始碼版本: https://github.com/plateaukao/browser/releases/tag/v8.5.2