Second batch of collected issues, mainly covering WebView memory management, cookie synchronization, and other development details.
WebView Memory Leaks#
Avoid declaring WebView directly in XML layout, as the WebView may hold a reference to the Activity’s Context even after the Activity is destroyed, preventing memory release. Correct approaches:
Use ApplicationContext#
1
| WebView webView = new WebView(getApplicationContext());
|
Proper Lifecycle Handling in Fragments#
1
2
3
4
5
6
| @Override
public void onDetach() {
super.onDetach();
webView.removeAllViews();
webView.destroy();
}
|
Process Management via the android:process Attribute#
You can assign different components to separate processes in the manifest:
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>
|
Avoiding Memory Leaks from Static Drawables#
Romain Guy wrote a classic article: Avoid Memory Leaks on Android. While somewhat dated now – Drawable.setCallback() has used WeakReference since Android 4.0.1 – holding Drawables via static fields remains a bad practice.
The Android framework itself clears the previous drawable reference when setting a new background:
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 Parameter Encoding#
When appending parameters to a URL, the values must be URL-encoded. The old NameValuePair approach has been deprecated since API 23. You can implement it yourself:
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();
}
|
The underlying logic is the same as NameValuePair – both do two toString() calls internally.
WebView Cookie Synchronization#
When using OkHttpClient, you need to manually sync cookies to WebView’s CookieManager. I spent some time debugging with packet capture before realizing the issue.
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);
}
}
|
The key insight: CookieManager.setCookie() takes a full URL as its first parameter, not just the 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#