現在公司開發手機 App 案子的時候,我都傾向於用 WebView 來做;這樣做有幾個好處:

  1. 排版容易:說真的,網頁 CSS 的排版發展真的遠比無論是 Android 還是 iOS 原生的排版機制要先進太多了,尤其再加上例如 Bootstrap 之類的 CSS 框架,排起來又快又有比較好的 RWD 效果。
  2. 更新方便:改變設計的時候只需要自己更新站台就好,不用走一次 App 的改版上架、使用者也不用更新 App。當然,我猜應該是有一些原生的排版框架有支援動態更新版面檔案,但是首先我不熟那類作法,再來我也懷疑那樣的更新有辦法做到連路由和畫面互動邏輯都變更的地步;相對地用 WebView 要直接變更這兩點當然不是問題。
  3. 互動好做:可以使用例如 Vue.js 或 Blazor 之類的反應式框架,在互動方面也遠比原生要好做。
  4. 機制完備:可以借用瀏覽器核心已經很完備的機制如資源快取、安全連線、cookie 等等,不用再另外自行處理。

當然 WebView 也是有缺點的,最主要的缺點大抵就是它的效能終究是比原生要差,因此如果想要做很炫的元件視覺效果,它並不是很好的選擇(不過因為我公司做的主要都是電子商務相關的 App,對這方面的需求不大)。其次這樣的 App 若要做到可以離線使用,架構上也必須使用 PWA 的作法,會有一些技術上的門檻;再來它要充分模擬出原生的使用者體驗、在套件的選擇以及 WebView 的底層設置上也是需要稍微下一點功夫的。

其中一個讓我花了一些力氣研究出來的效果就是讓 Android 的 WebView 也能夠使用自訂的系統字型。一些品牌的 Android 手機(例如三星等等)支援一種叫 Flipfont 的機制可以替換掉整個系統的預設字型,但是這個設定並不會直接反應在瀏覽器或者 WebView 之上。結果就是系統換了好看的字型,但是瀏覽器仍舊是普通的黑體,而且任何用 WebView 來做的 App 也是,一看就很突兀。後來我研究出讓 WebView 也能呈現出系統字型的辦法如下。

首先,當系統有支援 Flipfont 的時候,Typeface 類別會多出一個非標準的靜態方法叫作 getFontPathFlipFont()1 可以取得當前使用的字型路徑。應用這個原理,我們寫下如下的 FontUtil 類別:

import android.content.Context;
import android.graphics.Typeface;

import java.io.File;
import java.lang.reflect.Method;

public class FontUtil {

    @SuppressWarnings("JavaReflectionMemberAccess")
    public static File getFlipFont(Context context) {
        try {
            Method m = Typeface.class.getMethod("getFontPathFlipFont", Context.class, int.class);
            String path = m.invoke(null, context, 1).toString();
            if (!path.equals("default")) {
                File file = new File(path + "/DroidSansFallback.ttf");
                if (file.exists()) return file;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

其中我們需要用 reflection 來呼叫 getFontPathFlipFont 方法,因為那是非標準的,Java 編譯器不認得。其次,在我們的 Activity 中,我們做類似下面這樣的設置:

private File flipFont;
private WebView webView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    flipFont = FontUtil.getFlipFont(this);
    ...
    webView.setWebViewClient(new AppWebViewClient());
    ...
}

private class AppWebViewClient extends WebViewClient {

    @Nullable
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        String url = request.getUrl().toString();
        if (url.endsWith("flipfont.ttf") && flipFont != null) {
            try {
                InputStream stream = new FileInputStream(flipFont);
                return new WebResourceResponse("application/x-font-ttf", "UTF-8", stream);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        return super.shouldInterceptRequest(view, request);
    }
}

這個 AppWebViewClient 的作用是當網頁呼叫「flipfont.ttf」這個檔案的時候就把請求攔截掉,把回應替換成稍早取得的檔案的 stream 去給網頁使用。而最後,我們只要在網頁上使用如下的 CSS:

@font-face {
    font-family: Flipfont;
    src: url(flipfont.ttf);
}

body {
    font-family: Flipfont;
}

就可以成功地使用到 WebView 傳回的字型檔了。

上面展示的程式碼是最基本的概念,當然實際上還有其它優化空間,例如反覆呼叫字型的時候直接傳回 304 代碼讓 WebView 使用快取而非重新讀取檔案、或是在網頁裡下一個判斷確定當前環境是否為 Flipfont(例如可以利用 JavaScript interface 跟原生程式碼確認這件事)等等,這些細節就留給讀者了。


  1. 當初要研究出這個還真不是普通的麻煩,因為這是非標準的方法,沒有什麼參考資料可以找,我是反編譯了好幾個跟替換字型有關的 App 研究原始碼才找出這個方法的。 

分享此頁至:
最後修改日期: 2020/05/29

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。