第二篇问题整理,主要涉及 WebView 的内存管理和 Cookie 同步,以及一些其他细节。
WebView 内存泄漏#
不要在 XML 中直接声明 WebView,因为 Activity 销毁后 WebView 仍可能持有 Context 引用,导致内存无法释放。正确的做法:
使用 ApplicationContext#
1
| WebView webView = new WebView(getApplicationContext());
|
在 Fragment 中正确处理生命周期#
1
2
3
4
5
6
| @Override
public void onDetach() {
super.onDetach();
webView.removeAllViews();
webView.destroy();
}
|
进程管理:AndroidManifest 中的 process 属性#
可在清单文件中为不同组件分配独立进程:
1
2
3
4
5
6
7
8
9
10
11
| <application
android:process="com.processkill.p1">
<activity
android:name="com.processkill.A"
android:process="com.processkill.p2">
</activity>
<activity
android:name="com.processkill.B"
android:process="com.processkill.p3">
</activity>
</application>
|
避免静态 Drawable 导致的内存泄漏#
Romain Guy 写过一篇经典文章 Avoid Memory Leaks on Android。虽然现在看来有些过时——从 Android 4.0.1 开始,Drawable.setCallback() 已经改用 WeakReference——但使用 static 关键字持有 Drawable 仍然是不好的实践。
Android 框架内部在设置新背景时也会清理前一个引用:
1
2
3
4
5
6
7
8
| /*
* Regardless of whether we're setting a new background or not, we want
* to clear the previous drawable.
*/
if (mBackground != null) {
mBackground.setCallback(null);
unscheduleDrawable(mBackground);
}
|
WebView URL 参数编码#
在向链接追加参数时,需要对参数值进行编码。以前使用 NameValuePair(API 23 起已标记 @deprecated),也可以自己实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| JSONObject json = new JSONObject();
Iterator<String> keys = json.keys();
StringBuilder stringBuilder = new StringBuilder();
try {
while (keys.hasNext()) {
String key = keys.next();
String value = json.optString(key);
if (value != null) {
stringBuilder.append(URLEncoder.encode(key, "UTF-8"))
.append("=")
.append(URLEncoder.encode(value, "UTF-8"));
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
|
本质与 NameValuePair 相同——后者内部也是两次 toString 操作。
WebView Cookie 同步问题#
使用 OkHttpClient 时需要手动将 Cookie 同步到 WebView 的 CookieManager。抓包发现没有 Cookie,研究文档才发现问题所在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| Uri uri = API.getUri();
HttpUrl httpUrl = new HttpUrl.Builder()
.scheme(uri.getScheme())
.host(uri.getHost())
.build();
OkHttpClient okHttpClient = ClientManager.getInstance();
CookieJar cookieJar = okHttpClient.cookieJar();
List<Cookie> cookies = cookieJar.loadForRequest(httpUrl);
for (Cookie cookie : cookies) {
if (cookie != null) {
String cookieString = cookie.name() + "=" + cookie.value() + "; domain=" + cookie.domain();
cookieManager.setCookie(httpUrl.toString(), cookieString);
}
}
|
关键点在于 CookieManager.setCookie() 方法的第一个参数接收的是 完整 URL,而不是单纯的 Host:
1
2
3
4
5
6
7
8
9
10
| /**
* Sets a cookie for the given URL. Any existing cookie with the same host,
* path and name will be replaced with the new cookie. The cookie being set
* will be ignored if it is expired.
*
* @param url the URL for which the cookie is to be set
* @param value the cookie as a string, using the format of the 'Set-Cookie'
* HTTP response header
*/
public abstract void setCookie(String url, String value);
|
References#