本文引用的源碼爲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);