使用WebView.load(data,"text/html", "utf-8")加載顯示亂碼問題分析

本文引用的源碼爲android 4.4.4版本

請尊重博主勞動成果,轉載請標明原文鏈接。

使用WebView的load(data,”text/html”, “utf-8”)加載含有中文的網頁時,頁面上的中文字符顯示爲亂碼。網頁沒有問題,使用PC瀏覽器查看顯示正常。
load方法源碼

    public void loadData(String data, String mimeType, String encoding) {
        checkThread();
        if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadData");
        mProvider.loadData(data, mimeType, encoding);
    }

真正執行加載網頁操作的是mProvider,而它是什麼類型的對象呢?

    private WebViewProvider mProvider;
    ...
    private void ensureProviderCreated() {
        checkThread();
        if (mProvider == null) {
            // As this can get called during the base class constructor chain, pass the minimum
            // number of dependencies here; the rest are deferred to init().
            //調用方法getFactory獲取WebViewFactoryProvider對象,然後使用該對象的createWebView方法創建。
            mProvider = getFactory().createWebView(this, new PrivateAccess());
        }
    }

    //生成WebViewFactoryProvider方法
    private static synchronized WebViewFactoryProvider getFactory() {
        return WebViewFactory.getProvider();
    }

WebViewFactoryProvider是一個接口,那麼就查看WebViewFactory類。WebViewFactory的getProvider方法調用getFactoryClass()獲取到字節碼,然後通過反射創建了WebViewChromiumFactoryProvider對象。

public final class WebViewFactory {
    //類路徑
    private static final String CHROMIUM_WEBVIEW_FACTORY =
            "com.android.webview.chromium.WebViewChromiumFactoryProvider";

    ...

    private static class Preloader {
        static WebViewFactoryProvider sPreloadedProvider;
        //靜態代碼塊,字節碼加載進來便創建了WebViewFactoryProvider對象
        static {
            try {
                //通過反射創建對象
                sPreloadedProvider = getFactoryClass().newInstance();
            } catch (Exception e) {
                Log.w(LOGTAG, "error preloading provider", e);
            }
        }
    }

    …

    static WebViewFactoryProvider getProvider() {
        synchronized (sProviderLock) {
            //存在對象,則返回
            if (sProviderInstance != null) return sProviderInstance;

            Class<WebViewFactoryProvider> providerClass;
            try {
                //獲取字節碼對象
                providerClass = getFactoryClass();
            } catch (ClassNotFoundException e) {
                Log.e(LOGTAG, "error loading provider", e);
                throw new AndroidRuntimeException(e);
            }

            //對象存在且字節碼相同
            if (Preloader.sPreloadedProvider != null &&
                Preloader.sPreloadedProvider.getClass() == providerClass) {
                //賦值
                sProviderInstance = Preloader.sPreloadedProvider;

                return sProviderInstance;
            }


            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
            try {
                //通過字節碼創建對象
                sProviderInstance = providerClass.newInstance();
                if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
                return sProviderInstance;
            } catch (Exception e) {
                Log.e(LOGTAG, "error instantiating provider", e);
                throw new AndroidRuntimeException(e);
            } finally {
                StrictMode.setThreadPolicy(oldPolicy);
            }
        }
    }

    //獲取字節碼的方法
    private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
        return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY);
    }
}

WebViewFactory被加載進來,便創建WebViewChromiumFactoryProvider對象。源碼在frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromiumFactoryProvider.java。
那就看看WebViewChromiumFactoryProvider.loadData方法代碼:

public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
        WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);

        synchronized (mLock) {
            if (mWebViewsToStart != null) {
                mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvc));
            }
        }
        ResourceProvider.registerResources(webView.getContext());
        return wvc;
    }

該方法創建了WebViewChromium對象,並返回了該對象。在ensureProviderCreated方法中創建了該對象,並將其賦值給WebView中的mProvider屬性。也就是說,在WebView中網頁相關的操作都是WebViewChromium真正在執行。

WebViewChromium的源碼在:frameworks/webview/chromium/java/com/android/webview/chromium/WebViewChromium.java。
看看WebViewChromium的loadData方法:

public void loadData(String data, String mimeType, String encoding) {
        loadUrlOnUiThread(LoadUrlParams.createLoadDataParams(
                fixupData(data), fixupMimeType(mimeType), isBase64Encoded(encoding)));
    }

使用LoadUrlParams類的靜態方法createLoadDataParams對傳入的參數做了封裝。
繼續查看LoadUrlParams,源碼在:external/chromium_org/content/public/android/java/src/org/chromium/content/browser/LoadUrlParams.java。

    public static LoadUrlParams createLoadDataParams(
        String data, String mimeType, boolean isBase64Encoded) {
        return createLoadDataParams(data, mimeType, isBase64Encoded, null);
    }

    public static LoadUrlParams createLoadDataParams(
            String data, String mimeType, boolean isBase64Encoded, String charset) {
        StringBuilder dataUrl = new StringBuilder("data:");
        //類型
        dataUrl.append(mimeType);
        //編碼類型
        if (charset != null && !charset.isEmpty()) {
            dataUrl.append(";charset=" + charset);
        }
        //是否爲base64編碼
        if (isBase64Encoded) {
            dataUrl.append(";base64");
        }
        //分割符
        dataUrl.append(",");
        //網頁
        dataUrl.append(data);

        LoadUrlParams params = new LoadUrlParams(dataUrl.toString());
        params.setLoadType(LoadUrlParams.LOAD_TYPE_DATA);
        params.setTransitionType(PageTransitionTypes.PAGE_TRANSITION_TYPED);
        return params;
    }

由以上源碼可知:WebView的loadData方法傳入的參數encoding並沒有被封裝到LoadUrlParams中,所以導致中文顯示亂碼。
調用createLoadDataParams(
String data, String mimeType, boolean isBase64Encoded, String charset)方法肯定是能夠將編碼類型封裝到LoadUrlParams。往回看看,哪裏調用了該方法。WebViewChromium中的方法loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)有調用到。

    public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding,
            String historyUrl) {
        data = fixupData(data);
        mimeType = fixupMimeType(mimeType);
        LoadUrlParams loadUrlParams;
        baseUrl = fixupBase(baseUrl);
        historyUrl = fixupHistory(historyUrl);

        if (baseUrl.startsWith("data:")) {
            // For backwards compatibility with WebViewClassic, we use the value of |encoding|
            // as the charset, as long as it's not "base64".
            boolean isBase64 = isBase64Encoded(encoding);
            //如果是base64編碼:傳入的編碼類型爲null;不是則傳入設置的編碼類型
            loadUrlParams = LoadUrlParams.createLoadDataParamsWithBaseUrl(
                    data, mimeType, isBase64, baseUrl, historyUrl, isBase64 ? null : encoding);
        } else {
            // When loading data with a non-data: base URL, the classic WebView would effectively
            // "dump" that string of data into the WebView without going through regular URL
            // loading steps such as decoding URL-encoded entities. We achieve this same behavior by
            // base64 encoding the data that is passed here and then loading that as a data: URL.
            try {
                //設置爲utf-8編碼
                loadUrlParams = LoadUrlParams.createLoadDataParamsWithBaseUrl(
                        Base64.encodeToString(data.getBytes("utf-8"), Base64.DEFAULT), mimeType,
                        true, baseUrl, historyUrl, "utf-8");
            } catch (java.io.UnsupportedEncodingException e) {
                Log.wtf(TAG, "Unable to load data string " + data, e);
                return;
            }
        }
        loadUrlOnUiThread(loadUrlParams);
    }

WebView中的loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) 方法調用了該方法,所以使用該方法能夠解決亂碼問題。
使用這種方式便可以解決中文亂碼。

loadDataWithBaseURL(null, "html", "text/html", "UTF-8", null);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章